peely 0.9.4 → 0.9.6
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 +11 -1
- package/cli.js +45 -197
- package/demo.gif +0 -0
- package/package.json +4 -1
- package/src/ai/index.js +46 -11
- package/src/ai/providers/copilot.js +21 -29
- package/src/ai/providers/ollama.js +2 -3
- package/src/ai/providers/openai.js +15 -6
- package/src/daemon/client.js +7 -0
- package/src/daemon/server.js +4 -4
- package/src/interfaces/discord/index.js +17 -4
- package/src/interfaces/index.js +2 -28
- package/src/interfaces/terminal/commands.js +292 -0
- package/src/interfaces/terminal/index.js +24 -159
- package/src/plugins/index.js +12 -1
- package/src/plugins/plugins/create_plugin.js +52 -4
- package/src/plugins/plugins/math.js +4 -1
- package/src/utils/autoupdateInfo.js +12 -1
- package/src/utils/config.js +9 -3
- package/src/utils/events.js +2 -2
- package/src/utils/paths.js +0 -4
- package/src/utils/settings.js +1 -0
- package/src/interfaces/create_interface.js +0 -319
|
@@ -3,44 +3,18 @@ const chalk = require("chalk");
|
|
|
3
3
|
const config = require("../../utils/config");
|
|
4
4
|
const ai = require("../../ai");
|
|
5
5
|
const memory = require("../../utils/memory");
|
|
6
|
-
const PATHS = require("../../utils/paths");
|
|
7
6
|
const ora = require("ora");
|
|
7
|
+
const cmds = require("./commands");
|
|
8
8
|
|
|
9
9
|
// ── State ── (loaded from disk)
|
|
10
10
|
const conversationHistory = memory.load("terminal");
|
|
11
11
|
let running = true;
|
|
12
12
|
|
|
13
13
|
// ── UI helpers ──
|
|
14
|
-
const LOGO =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const p = path.join(__dirname, "..", "..", "assets", "ascii-logo.txt");
|
|
19
|
-
const art = fs.readFileSync(p, "utf8");
|
|
20
|
-
return art
|
|
21
|
-
.split("\n")
|
|
22
|
-
.map((line) => (line.trim() === "" ? "" : chalk.magenta(" " + line)))
|
|
23
|
-
.join("\n")
|
|
24
|
-
+ "\n\n" + chalk.bold.white(" 🍌 peely") + " " + chalk.dim("— interactive mode") + "\n";
|
|
25
|
-
} catch (err) {
|
|
26
|
-
// Fallback banner
|
|
27
|
-
return `\n${chalk.magenta(" ____ _ _ _ ")}\n${chalk.magenta(" | _ \\ ___ __ _(_) (_) ___ ")}\n${chalk.magenta(" | |_) / _ \\/ _\\` | | | |/ _ \\ ")}\n${chalk.magenta(" | __/ __/ (_| | | | | (_) |")}\n${chalk.magenta(" |_| \\___|\\__, |_|_|_|\\___/ ")}\n${chalk.magenta(" |___/ ")}\n\n${chalk.bold.white(" 🍌 peely")} ${chalk.dim("— interactive mode")}\n`;
|
|
28
|
-
}
|
|
29
|
-
})();
|
|
30
|
-
|
|
31
|
-
const HELP_TEXT = `
|
|
32
|
-
${chalk.bold(" Commands:")}
|
|
33
|
-
${chalk.cyan("/help")} Show this help
|
|
34
|
-
${chalk.cyan("/clear")} Clear conversation history
|
|
35
|
-
${chalk.cyan("/model")} Switch AI model
|
|
36
|
-
${chalk.cyan("/settings")} Edit config, tokens & API keys
|
|
37
|
-
${chalk.cyan("/timers")} Show active timers
|
|
38
|
-
${chalk.cyan("/plugins")} List loaded plugins
|
|
39
|
-
${chalk.cyan("/interfaces")} List & create interfaces
|
|
40
|
-
${chalk.cyan("/pair discord <code>")} Pair a Discord account
|
|
41
|
-
${chalk.cyan("/status")} Show system status
|
|
42
|
-
${chalk.cyan("/exit")} Exit peely
|
|
43
|
-
`;
|
|
14
|
+
const LOGO = cmds.interactiveLogo;
|
|
15
|
+
const { printStatus, pairDiscord } = cmds;
|
|
16
|
+
|
|
17
|
+
const HELP_TEXT = cmds.buildSlashHelp();
|
|
44
18
|
|
|
45
19
|
const printSeparator = () => {
|
|
46
20
|
console.log(chalk.dim(" " + "─".repeat(50)));
|
|
@@ -84,56 +58,23 @@ const handleSlashCommand = async (input, rl) => {
|
|
|
84
58
|
return true;
|
|
85
59
|
|
|
86
60
|
case "model":
|
|
87
|
-
return { clack: true, run:
|
|
88
|
-
await ai.chooseModel();
|
|
89
|
-
}};
|
|
90
|
-
|
|
91
|
-
case "status": {
|
|
92
|
-
const model = config.get("ai.model") || chalk.dim("not set");
|
|
93
|
-
const discord = config.get("interfaces.discord.token")
|
|
94
|
-
? chalk.green("configured")
|
|
95
|
-
: chalk.red("not set");
|
|
96
|
-
const github = config.get("github.token")
|
|
97
|
-
? chalk.green("authorized")
|
|
98
|
-
: chalk.red("not set");
|
|
99
|
-
const msgs = conversationHistory.length;
|
|
61
|
+
return { clack: true, run: () => cmds.chooseModel() };
|
|
100
62
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
console.log(` AI Model: ${model}`);
|
|
104
|
-
console.log(` GitHub: ${github}`);
|
|
105
|
-
console.log(` Discord: ${discord}`);
|
|
106
|
-
console.log(` Messages: ${msgs}`);
|
|
107
|
-
console.log();
|
|
63
|
+
case "status":
|
|
64
|
+
printStatus({ messageCount: conversationHistory.length });
|
|
108
65
|
return true;
|
|
109
|
-
}
|
|
110
66
|
|
|
111
67
|
case "pair": {
|
|
112
68
|
if (parts[1] === "discord" && parts[2]) {
|
|
113
69
|
const code = parts[2].toUpperCase();
|
|
114
70
|
try {
|
|
115
|
-
const
|
|
116
|
-
const db = new QuickDB({
|
|
117
|
-
driver: new SqliteDriver(PATHS.quickDb),
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const userId = await db.get(`pairCode_${code}`);
|
|
121
|
-
if (!userId) {
|
|
122
|
-
console.log();
|
|
123
|
-
printError(`Invalid or expired pair code: ${code}`);
|
|
124
|
-
console.log();
|
|
125
|
-
return true;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
await db.set(`paired_${userId}`, true);
|
|
129
|
-
await db.delete(`pairCode_${code}`);
|
|
130
|
-
config.set("interfaces.discord.pairedUsers", [
|
|
131
|
-
...(config.get("interfaces.discord.pairedUsers") || []),
|
|
132
|
-
userId,
|
|
133
|
-
]);
|
|
134
|
-
|
|
71
|
+
const result = await pairDiscord(code);
|
|
135
72
|
console.log();
|
|
136
|
-
|
|
73
|
+
if (result.ok) {
|
|
74
|
+
printSuccess(`Paired Discord user ${result.userId}!`);
|
|
75
|
+
} else {
|
|
76
|
+
printError(result.error);
|
|
77
|
+
}
|
|
137
78
|
console.log();
|
|
138
79
|
} catch (err) {
|
|
139
80
|
console.log();
|
|
@@ -157,99 +98,17 @@ const handleSlashCommand = async (input, rl) => {
|
|
|
157
98
|
return true;
|
|
158
99
|
|
|
159
100
|
case "timers": {
|
|
160
|
-
|
|
161
|
-
const scheduled = events.listScheduled();
|
|
162
|
-
console.log();
|
|
163
|
-
if (scheduled.length === 0) {
|
|
164
|
-
console.log(chalk.dim(" No active timers."));
|
|
165
|
-
} else {
|
|
166
|
-
console.log(chalk.bold(" Active timers:"));
|
|
167
|
-
for (const t of scheduled) {
|
|
168
|
-
const secs = Math.ceil(t.remainingMs / 1000);
|
|
169
|
-
const desc = t.meta?.task || t.meta?.message || t.id;
|
|
170
|
-
console.log(` ⏱️ ${t.id} — ${secs}s left — ${desc}`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
console.log();
|
|
101
|
+
cmds.printTimers();
|
|
174
102
|
return true;
|
|
175
103
|
}
|
|
176
104
|
|
|
177
105
|
case "plugins": {
|
|
178
|
-
|
|
179
|
-
console.log();
|
|
180
|
-
console.log(chalk.bold(" Loaded plugins:"));
|
|
181
|
-
for (const p of pluginModule.plugins) {
|
|
182
|
-
const toolCount = p.tools ? Object.keys(p.tools).length : 0;
|
|
183
|
-
console.log(` • ${chalk.cyan(p.name)} — ${toolCount} tool(s) — ${p.description || ""}`);
|
|
184
|
-
}
|
|
185
|
-
console.log();
|
|
106
|
+
cmds.printPlugins();
|
|
186
107
|
return true;
|
|
187
108
|
}
|
|
188
109
|
|
|
189
|
-
case "interfaces":
|
|
190
|
-
case "interface": {
|
|
191
|
-
// @clack/prompts wizard needs exclusive stdin
|
|
192
|
-
return { clack: true, run: async () => {
|
|
193
|
-
const { listInterfaces, createInterface, deleteInterface } = require("../create_interface");
|
|
194
|
-
const { select, isCancel, log: cLog } = require("@clack/prompts");
|
|
195
|
-
|
|
196
|
-
const all = listInterfaces();
|
|
197
|
-
console.log();
|
|
198
|
-
console.log(chalk.bold(" Interfaces:"));
|
|
199
|
-
for (const iface of all) {
|
|
200
|
-
const tag = iface.type === "built-in"
|
|
201
|
-
? chalk.dim("[built-in]")
|
|
202
|
-
: chalk.cyan("[custom]");
|
|
203
|
-
console.log(` ${tag} ${chalk.bold(iface.name)} \u2014 ${iface.description}`);
|
|
204
|
-
}
|
|
205
|
-
console.log();
|
|
206
|
-
|
|
207
|
-
const action = await select({
|
|
208
|
-
message: "What would you like to do?",
|
|
209
|
-
options: [
|
|
210
|
-
{ value: "create", label: "\uD83D\uDD27 Create new interface" },
|
|
211
|
-
{ value: "delete", label: "\uD83D\uDDD1\uFE0F Delete a custom interface",
|
|
212
|
-
hint: all.filter(i => i.type === "custom").length === 0 ? "none yet" : undefined },
|
|
213
|
-
{ value: "back", label: "\u2190 Back" },
|
|
214
|
-
],
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
if (isCancel(action) || action === "back") return;
|
|
218
|
-
|
|
219
|
-
if (action === "create") {
|
|
220
|
-
await createInterface();
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (action === "delete") {
|
|
225
|
-
const customs = all.filter(i => i.type === "custom");
|
|
226
|
-
if (customs.length === 0) {
|
|
227
|
-
cLog.warn("No custom interfaces to delete.");
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
const which = await select({
|
|
231
|
-
message: "Which interface to delete?",
|
|
232
|
-
options: [
|
|
233
|
-
...customs.map(i => ({ value: i.name, label: i.name, hint: i.description })),
|
|
234
|
-
{ value: "back", label: "\u2190 Back" },
|
|
235
|
-
],
|
|
236
|
-
});
|
|
237
|
-
if (isCancel(which) || which === "back") return;
|
|
238
|
-
if (deleteInterface(which)) {
|
|
239
|
-
cLog.success(`Deleted "${which}".`);
|
|
240
|
-
} else {
|
|
241
|
-
cLog.error(`Interface "${which}" not found.`);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
110
|
case "settings": {
|
|
248
|
-
|
|
249
|
-
return { clack: true, run: async () => {
|
|
250
|
-
const { settingsMenu } = require("../../utils/settings");
|
|
251
|
-
await settingsMenu();
|
|
252
|
-
}};
|
|
111
|
+
return { clack: true, run: () => cmds.openSettings() };
|
|
253
112
|
}
|
|
254
113
|
|
|
255
114
|
default:
|
|
@@ -276,6 +135,12 @@ const handleChat = async (input) => {
|
|
|
276
135
|
|
|
277
136
|
const reply = response.content || "...";
|
|
278
137
|
conversationHistory.push({ role: "assistant", content: reply });
|
|
138
|
+
|
|
139
|
+
// Keep history manageable (same limits as daemon/discord)
|
|
140
|
+
if (conversationHistory.length > 80) {
|
|
141
|
+
conversationHistory.splice(0, conversationHistory.length - 60);
|
|
142
|
+
}
|
|
143
|
+
|
|
279
144
|
memory.save("terminal", conversationHistory);
|
|
280
145
|
|
|
281
146
|
console.log();
|
|
@@ -419,4 +284,4 @@ const start = async () => {
|
|
|
419
284
|
});
|
|
420
285
|
};
|
|
421
286
|
|
|
422
|
-
module.exports = { start };
|
|
287
|
+
module.exports = { start, ...cmds };
|
package/src/plugins/index.js
CHANGED
|
@@ -12,7 +12,18 @@ if (!fs.existsSync(CUSTOM_DIR)) fs.mkdirSync(CUSTOM_DIR, { recursive: true });
|
|
|
12
12
|
const builtinList = fs.readdirSync(BUILTIN_DIR).filter((f) => f.endsWith(".js"));
|
|
13
13
|
const plugins = builtinList.map((f) => {
|
|
14
14
|
const plugin = require(`./plugins/${f}`);
|
|
15
|
-
if (plugin.initialize)
|
|
15
|
+
if (plugin.initialize) {
|
|
16
|
+
try {
|
|
17
|
+
const result = plugin.initialize();
|
|
18
|
+
if (result && typeof result.catch === "function") {
|
|
19
|
+
result.catch((err) =>
|
|
20
|
+
console.error(`⚠️ Plugin ${f} init error:`, err.message)
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error(`⚠️ Plugin ${f} init error:`, err.message);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
16
27
|
return plugin;
|
|
17
28
|
});
|
|
18
29
|
|
|
@@ -9,6 +9,9 @@ const CUSTOM_DIR = PATHS.customPlugins;
|
|
|
9
9
|
if (!fs.existsSync(CUSTOM_DIR)) fs.mkdirSync(CUSTOM_DIR, { recursive: true });
|
|
10
10
|
|
|
11
11
|
// ── Agent system prompt ──
|
|
12
|
+
// __PEELY_ROOT__ is replaced at runtime with the actual package root path
|
|
13
|
+
const PEELY_ROOT = path.resolve(__dirname, "..", "..", "..");
|
|
14
|
+
|
|
12
15
|
const AGENT_PROMPT = `You are a plugin code generator for peely (a Node.js AI assistant).
|
|
13
16
|
|
|
14
17
|
Your job: generate a complete, working plugin file based on the user's description.
|
|
@@ -40,8 +43,8 @@ Plugins can trigger the AI to perform any task using ai.invoke(task).
|
|
|
40
43
|
This is the preferred way to make things happen — describe WHAT, let the AI figure out HOW.
|
|
41
44
|
|
|
42
45
|
\`\`\`js
|
|
43
|
-
//
|
|
44
|
-
const ai = require("
|
|
46
|
+
// ALWAYS use this exact require path — it works from any plugin location:
|
|
47
|
+
const ai = require("${PEELY_ROOT.replace(/\\/g, "/")}/src/ai");
|
|
45
48
|
await ai.invoke("send a Discord DM to username saying hello");
|
|
46
49
|
await ai.invoke("search for weather in Warsaw and tell me");
|
|
47
50
|
\`\`\`
|
|
@@ -52,7 +55,7 @@ Use ai.invoke() when:
|
|
|
52
55
|
- You want the AI to decide which tools to use based on context
|
|
53
56
|
|
|
54
57
|
AVAILABLE UTILITIES:
|
|
55
|
-
- const { events } = require("
|
|
58
|
+
- const { events } = require("${PEELY_ROOT.replace(/\\/g, "/")}/src/utils/events") — event bus
|
|
56
59
|
events.on("eventName", callback) / events.emit("eventName", data)
|
|
57
60
|
events.scheduleTimeout(id, ms, callback, meta) / events.cancelTimeout(id)
|
|
58
61
|
|
|
@@ -67,7 +70,32 @@ RULES:
|
|
|
67
70
|
8. Plugin name must be lowercase with underscores, no spaces.
|
|
68
71
|
9. Keep it simple and focused. One plugin = one domain of functionality.
|
|
69
72
|
10. Do NOT include any text outside the JavaScript code.
|
|
70
|
-
11. When the plugin needs to trigger actions, USE ai.invoke() instead of hardcoding logic
|
|
73
|
+
11. When the plugin needs to trigger actions, USE ai.invoke() instead of hardcoding logic.
|
|
74
|
+
12. For AI/utility requires, ALWAYS use the absolute paths shown above. NEVER use relative paths like "../../ai".`;
|
|
75
|
+
|
|
76
|
+
// ── Dangerous pattern scanner for generated code ──
|
|
77
|
+
const DANGEROUS_PATTERNS = [
|
|
78
|
+
/\bchild_process\b/,
|
|
79
|
+
/\bexec\s*\(/,
|
|
80
|
+
/\bexecSync\s*\(/,
|
|
81
|
+
/\bspawn\s*\(/,
|
|
82
|
+
/\beval\s*\(/,
|
|
83
|
+
/\bFunction\s*\(/,
|
|
84
|
+
/\bprocess\.env\b/,
|
|
85
|
+
/\bprocess\.exit\b/,
|
|
86
|
+
/\bglobal\./,
|
|
87
|
+
/\brequire\s*\(\s*['"]child_process['"]/,
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const scanForDangerousPatterns = (code) => {
|
|
91
|
+
const matches = [];
|
|
92
|
+
for (const pat of DANGEROUS_PATTERNS) {
|
|
93
|
+
if (pat.test(code)) {
|
|
94
|
+
matches.push(pat.source);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return matches;
|
|
98
|
+
};
|
|
71
99
|
|
|
72
100
|
// ── The actual tool functions ──
|
|
73
101
|
|
|
@@ -116,6 +144,16 @@ const createPlugin = async (name, description) => {
|
|
|
116
144
|
return `ERROR: Generated code has a syntax error: ${err.message}. Try again with a simpler description.`;
|
|
117
145
|
}
|
|
118
146
|
|
|
147
|
+
// Scan for dangerous patterns (security check)
|
|
148
|
+
const dangers = scanForDangerousPatterns(code);
|
|
149
|
+
if (dangers.length > 0) {
|
|
150
|
+
return (
|
|
151
|
+
`\u26a0\ufe0f BLOCKED: Generated plugin code contains potentially dangerous patterns:\n` +
|
|
152
|
+
dangers.map((d) => ` \u2022 ${d}`).join("\n") +
|
|
153
|
+
`\n\nThe code was NOT saved. Try a safer description or review the generated code manually.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
119
157
|
fs.writeFileSync(filePath, code, "utf-8");
|
|
120
158
|
|
|
121
159
|
// Hot-reload: register the new plugin into the live tool registry
|
|
@@ -177,6 +215,16 @@ const editPlugin = async (name, changes) => {
|
|
|
177
215
|
return `ERROR: Updated code has a syntax error: ${err.message}. Try again.`;
|
|
178
216
|
}
|
|
179
217
|
|
|
218
|
+
// Scan for dangerous patterns (security check)
|
|
219
|
+
const dangers = scanForDangerousPatterns(code);
|
|
220
|
+
if (dangers.length > 0) {
|
|
221
|
+
return (
|
|
222
|
+
`\u26a0\ufe0f BLOCKED: Updated plugin code contains potentially dangerous patterns:\n` +
|
|
223
|
+
dangers.map((d) => ` \u2022 ${d}`).join("\n") +
|
|
224
|
+
`\n\nThe code was NOT saved. Try a safer modification or review the generated code manually.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
180
228
|
// Backup old version
|
|
181
229
|
const backupPath = filePath + ".bak";
|
|
182
230
|
fs.copyFileSync(filePath, backupPath);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
const add = (a, b) => a + b;
|
|
2
2
|
const subtract = (a, b) => a - b;
|
|
3
3
|
const multiply = (a, b) => a * b;
|
|
4
|
-
const divide = (a, b) =>
|
|
4
|
+
const divide = (a, b) => {
|
|
5
|
+
if (b === 0) return "Error: division by zero";
|
|
6
|
+
return a / b;
|
|
7
|
+
};
|
|
5
8
|
|
|
6
9
|
module.exports = {
|
|
7
10
|
name: "math",
|
|
@@ -27,6 +27,17 @@ const center = (text, width) => {
|
|
|
27
27
|
return " ".repeat(left) + text + " ".repeat(right);
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
/** Compare two semver strings. Returns true if a > b. */
|
|
31
|
+
const semverGt = (a, b) => {
|
|
32
|
+
const pa = a.split(".").map(Number);
|
|
33
|
+
const pb = b.split(".").map(Number);
|
|
34
|
+
for (let i = 0; i < 3; i++) {
|
|
35
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return true;
|
|
36
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return false;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
};
|
|
40
|
+
|
|
30
41
|
const checkUpdate = async () => {
|
|
31
42
|
const newest = await axios
|
|
32
43
|
.get(NPM_URL)
|
|
@@ -36,7 +47,7 @@ const checkUpdate = async () => {
|
|
|
36
47
|
if (!newest) return;
|
|
37
48
|
|
|
38
49
|
const pkgVersion = require("../../package.json").version;
|
|
39
|
-
if (newest
|
|
50
|
+
if (!semverGt(newest, pkgVersion)) return;
|
|
40
51
|
|
|
41
52
|
console.log("");
|
|
42
53
|
const title = chalk.bold("🚀 Peely Update Available");
|
package/src/utils/config.js
CHANGED
|
@@ -4,20 +4,26 @@ const PATHS = require("./paths");
|
|
|
4
4
|
const CONFIG_PATH = PATHS.config;
|
|
5
5
|
|
|
6
6
|
if (!fs.existsSync(CONFIG_PATH)) {
|
|
7
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify({}, null, 4));
|
|
7
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify({}, null, 4), { mode: 0o600 });
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
// In-memory cache to avoid redundant synchronous disk I/O
|
|
11
|
+
let _configCache = null;
|
|
12
|
+
|
|
10
13
|
const readConfig = () => {
|
|
14
|
+
if (_configCache) return _configCache;
|
|
11
15
|
try {
|
|
12
16
|
const raw = fs.readFileSync(CONFIG_PATH, "utf8");
|
|
13
|
-
|
|
17
|
+
_configCache = JSON.parse(raw || "{}");
|
|
18
|
+
return _configCache;
|
|
14
19
|
} catch (err) {
|
|
15
20
|
return {};
|
|
16
21
|
}
|
|
17
22
|
};
|
|
18
23
|
|
|
19
24
|
const writeConfig = (cfg) => {
|
|
20
|
-
|
|
25
|
+
_configCache = cfg;
|
|
26
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 4), { mode: 0o600 });
|
|
21
27
|
};
|
|
22
28
|
|
|
23
29
|
const get = (key) => {
|
package/src/utils/events.js
CHANGED
|
@@ -16,7 +16,7 @@ const EventEmitter = require("events");
|
|
|
16
16
|
* and when the event fires it triggers the callback (e.g. send a Discord message).
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
class
|
|
19
|
+
class PeelyEvents extends EventEmitter {
|
|
20
20
|
constructor() {
|
|
21
21
|
super();
|
|
22
22
|
this.setMaxListeners(100);
|
|
@@ -66,6 +66,6 @@ class peelyEvents extends EventEmitter {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
const events = new
|
|
69
|
+
const events = new PeelyEvents();
|
|
70
70
|
|
|
71
71
|
module.exports = { events };
|
package/src/utils/paths.js
CHANGED
|
@@ -17,7 +17,6 @@ const dirs = [
|
|
|
17
17
|
path.join(DATA_HOME, "data"),
|
|
18
18
|
path.join(DATA_HOME, "data", "conversations"),
|
|
19
19
|
path.join(DATA_HOME, "plugins", "custom"),
|
|
20
|
-
path.join(DATA_HOME, "interfaces", "custom"),
|
|
21
20
|
];
|
|
22
21
|
|
|
23
22
|
for (const d of dirs) {
|
|
@@ -55,9 +54,6 @@ const PATHS = {
|
|
|
55
54
|
|
|
56
55
|
/** plugins/custom/ directory for user-created plugins */
|
|
57
56
|
customPlugins: path.join(DATA_HOME, "plugins", "custom"),
|
|
58
|
-
|
|
59
|
-
/** interfaces/custom/ directory for user-created interfaces */
|
|
60
|
-
customInterfaces: path.join(DATA_HOME, "interfaces", "custom"),
|
|
61
57
|
};
|
|
62
58
|
|
|
63
59
|
// ── One-time migration from old (in-project) layout ──
|
package/src/utils/settings.js
CHANGED