oft-agt 0.5.0 → 0.5.7
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 +36 -0
- package/cli.mjs +12 -13
- package/compile-plan.mjs +177 -7
- package/compiler.mjs +55 -55
- package/instance.mjs +13 -1
- package/npm-shrinkwrap.json +3 -3
- package/package.json +2 -2
- package/runtime/button-router.mjs +42 -0
- package/runtime/conditions.mjs +14 -1
- package/runtime/context-variables.mjs +14 -0
- package/runtime/custom-variables.mjs +184 -0
- package/runtime/discord-actions.mjs +64 -0
- package/runtime/discord-blocks.mjs +53 -0
- package/runtime/errors.mjs +85 -85
- package/runtime/execute.mjs +143 -20
- package/runtime/functions.mjs +92 -4
- package/runtime/lifecycle.mjs +1 -1
- package/runtime/message-buttons.mjs +17 -0
- package/runtime/message-config.mjs +80 -3
- package/runtime/scheduler.mjs +91 -0
- package/runtime/variable-actions.mjs +42 -0
- package/runtime/variable-store-worker.mjs +69 -0
- package/runtime/variable-store.mjs +26 -0
- package/templates/bot.mjs +17 -6
- package/update.mjs +24 -2
- package/usage.mjs +42 -39
- package/version.mjs +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Oftone Server Agent
|
|
2
|
+
|
|
3
|
+
Agent Node.js 22+ de liaison avec le dashboard. Le relais est hébergé sur Cloudflare Workers avec un Durable Object SQLite ; les bots Discord s’exécutent sur le serveur utilisateur.
|
|
4
|
+
|
|
5
|
+
Générez la commande d’installation depuis **Liaison du serveur** dans le dashboard. Elle utilise un token à usage unique de dix minutes, puis l’agent dispose de sa propre identité. Exécutez-la avec un compte système dédié, sans privilèges administrateur.
|
|
6
|
+
|
|
7
|
+
Les fichiers privés et l’installation persistante se trouvent par défaut dans `~/.oftone-agent`. La variable `OFTONE_AGENT_HOME` permet de choisir un autre dossier, notamment pour héberger un deuxième bot. Ne partagez jamais le dossier privé entre utilisateurs.
|
|
8
|
+
|
|
9
|
+
Après installation, l’agent affiche la commande locale pour redémarrer sans télécharger le paquet :
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
node <dossier-utilisateur>/.oftone-agent/installation/cli.mjs run
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Le processus doit rester actif. Cette version n’installe pas de service système de démarrage automatique. Une coupure du réseau Cloudflare n’arrête pas le bot déjà lancé.
|
|
16
|
+
|
|
17
|
+
Le token Discord est configuré depuis le dashboard après liaison, chiffré pour la clé publique de cet agent et conservé localement sous forme d’enveloppe chiffrée. La clé privée locale permet au processus de le déchiffrer pour lancer le bot. Les permissions du compte système restent essentielles ; sous Windows, vérifier les ACL du dossier.
|
|
18
|
+
|
|
19
|
+
Le bouton **Déployer** transmet un snapshot des commandes enregistrées. L’agent valide ce JSON, le compile avec les fonctions embarquées et démarre une version locale. Aucun JavaScript, script shell ou paquet npm fourni par le JSON n’est exécuté. Les dépendances proviennent du paquet de l’agent, verrouillées par `npm-shrinkwrap.json`.
|
|
20
|
+
|
|
21
|
+
La commande de déploiement remplace les slash commands de l’application Discord : utiliser une application dédiée. Les changements de token et la rotation de l’identité de l’agent restent à compléter. Les processus des bots ne sont pas encore isolés dans des conteneurs.
|
|
22
|
+
|
|
23
|
+
Pour reconstruire le paquet : `npm pack --ignore-scripts`. Le dossier du builder contient `server-link/README.md`, les tests du relais et la configuration Wrangler. Le paquet public est servi par le Worker ; aucun envoi au registre npm n’est nécessaire.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Version 0.5.2
|
|
27
|
+
|
|
28
|
+
Les envois vers un salon vérifient désormais le serveur et les droits de
|
|
29
|
+
l’utilisateur, ainsi que l’appartenance aux fils privés. Le moteur adapte sa
|
|
30
|
+
concurrence à la charge CPU globale du PC, à la mémoire disponible et à sa
|
|
31
|
+
réactivité. Les pauses libèrent une place de traitement ; la file et les
|
|
32
|
+
invocations conservées restent bornées pour protéger l’hébergement.
|
|
33
|
+
|
|
34
|
+
Installation : `npm install -g oft-agt` (Node.js 22 ou supérieur).
|
|
35
|
+
La mise à jour du paquet npm ne remplace pas automatiquement les installations
|
|
36
|
+
persistantes déjà liées dans le dossier privé de l’agent.
|
package/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
import { localStatus, startBackground, stopBackground, managedDaemon, readLogs, removeLocalInstance, ensureLocalLauncher } from './runtime/lifecycle.mjs';
|
|
3
3
|
import { completeCredentialRotation, rotateCredential } from './runtime/agent-auth.mjs';
|
|
4
4
|
import { authorizeDevice } from './runtime/device-login.mjs';
|
|
@@ -13,9 +13,9 @@ import WebSocket from 'ws';
|
|
|
13
13
|
import { writeBot, compile } from './compiler.mjs';
|
|
14
14
|
import { acceptSecret } from './secret.mjs';
|
|
15
15
|
import { createUsageCollector } from './usage.mjs';
|
|
16
|
-
import { instanceDirectory, resolveInstance, listInstances } from './instance.mjs';
|
|
16
|
+
import { instanceDirectory, resolveInstance, listInstances, rememberInstance } from './instance.mjs';
|
|
17
17
|
import { AGENT_VERSION } from './version.mjs';
|
|
18
|
-
import {
|
|
18
|
+
import { installOfficialUpdate } from './update.mjs';
|
|
19
19
|
import { supervise } from './supervisor.mjs';
|
|
20
20
|
|
|
21
21
|
const baseHome = process.env.OFTONE_AGENT_HOME || path.join(homedir(), '.oftone-agent');
|
|
@@ -56,7 +56,7 @@ async function restartBot(config) {
|
|
|
56
56
|
if (!/^[a-f0-9-]{36}$/.test(release.id)) throw new Error('INVALID_RELEASE');
|
|
57
57
|
const secret = await credentials(config);
|
|
58
58
|
await stopBot();
|
|
59
|
-
child = watchUsage(spawn(process.execPath, ['bot.mjs'], { cwd: path.join(home, 'releases', release.id), env: { ...Object.fromEntries(['PATH', 'HOME', 'USERPROFILE', 'SystemRoot', 'WINDIR', 'TEMP', 'TMP'].filter(key => process.env[key]).map(key => [key, process.env[key]])), DISCORD_TOKEN: secret.token, DISCORD_CLIENT_ID: secret.applicationId }, stdio: ['ignore', 'pipe', 'pipe', 'ipc'], windowsHide: true, shell: false }));
|
|
59
|
+
child = watchUsage(spawn(process.execPath, ['bot.mjs'], { cwd: path.join(home, 'releases', release.id), env: { ...Object.fromEntries(['PATH', 'HOME', 'USERPROFILE', 'SystemRoot', 'WINDIR', 'TEMP', 'TMP'].filter(key => process.env[key]).map(key => [key, process.env[key]])), DISCORD_TOKEN: secret.token, DISCORD_CLIENT_ID: secret.applicationId, OFTONE_VARIABLES_PATH: path.join(home, 'data', 'variables.sqlite') }, stdio: ['ignore', 'pipe', 'pipe', 'ipc'], windowsHide: true, shell: false }));
|
|
60
60
|
secret.token = '';
|
|
61
61
|
child.stderr.on('data', () => {});
|
|
62
62
|
await new Promise((resolve, reject) => { const timer = setTimeout(() => { child.kill(); reject(new Error('BOT_START_TIMEOUT')); }, 45000); const fail = () => { clearTimeout(timer); reject(new Error('BOT_START_FAILED')); }; child.once('error', fail); child.once('exit', fail); child.stdout.on('data', bytes => { if (bytes.toString().includes('Bot connecté :')) { clearTimeout(timer); child.off('error', fail); child.off('exit', fail); resolve(); } }); });
|
|
@@ -80,6 +80,7 @@ async function deploy(config, message, notify) {
|
|
|
80
80
|
const env = { ...Object.fromEntries(['PATH', 'HOME', 'USERPROFILE', 'SystemRoot', 'WINDIR', 'TEMP', 'TMP', 'LANG', 'TZ'].filter(key => process.env[key]).map(key => [key, process.env[key]])), DISCORD_TOKEN: secret.token, DISCORD_CLIENT_ID: secret.applicationId };
|
|
81
81
|
// Remove credentials unrelated to running the Discord bot.
|
|
82
82
|
for (const key of Object.keys(env)) if (/OFTONE|AGENT_RELAY|CLOUDFLARE|SESSION_SECRET/.test(key)) delete env[key];
|
|
83
|
+
env.OFTONE_VARIABLES_PATH = path.join(home, 'data', 'variables.sqlite');
|
|
83
84
|
await run(process.execPath, ['bot.mjs', '--deploy'], { cwd: target, env, stdio: 'ignore' });
|
|
84
85
|
const previous = child;
|
|
85
86
|
const previousId = active;
|
|
@@ -136,7 +137,7 @@ async function start(config) {
|
|
|
136
137
|
if (!/^[a-f0-9-]{36}$/.test(previous.id)) throw new Error('État local invalide.');
|
|
137
138
|
const secret = await credentials(config);
|
|
138
139
|
const target = path.join(home, 'releases', previous.id);
|
|
139
|
-
child = watchUsage(spawn(process.execPath, ['bot.mjs'], { cwd: target, env: { PATH: process.env.PATH, DISCORD_TOKEN: secret.token, DISCORD_CLIENT_ID: secret.applicationId }, stdio: ['ignore', 'ignore', 'ignore', 'ipc'], windowsHide: true, shell: false }));
|
|
140
|
+
child = watchUsage(spawn(process.execPath, ['bot.mjs'], { cwd: target, env: { PATH: process.env.PATH, DISCORD_TOKEN: secret.token, DISCORD_CLIENT_ID: secret.applicationId, OFTONE_VARIABLES_PATH: path.join(home, 'data', 'variables.sqlite') }, stdio: ['ignore', 'ignore', 'ignore', 'ipc'], windowsHide: true, shell: false }));
|
|
140
141
|
child.on('error', () => {});
|
|
141
142
|
active = previous.id;
|
|
142
143
|
}
|
|
@@ -156,7 +157,7 @@ async function start(config) {
|
|
|
156
157
|
try { const response = await fetch('https://discord.com/api/v10/users/@me', { headers: { Authorization: 'Bot ' + secret.token }, signal: AbortSignal.timeout(15000) }); if (response.ok) { const user = await response.json(); profile = { id: user.id, username: user.username, avatar: user.avatar }; } } finally { secret.token = ''; }
|
|
157
158
|
} catch {}
|
|
158
159
|
}
|
|
159
|
-
send({ type: 'hello', agentVersion: AGENT_VERSION, updatesVersion: 1, protocol: 2, errorBranch: true, diagnosticsVersion: 2, optionsVersion: 1, conditionsVersion: 1, metricsVersion: usageReady ? 1 : 0, profile, running: Boolean(child && child.exitCode === null && !child.signalCode), hasToken: await exists(secretPath), deploymentId: active });
|
|
160
|
+
send({ type: 'hello', agentVersion: AGENT_VERSION, updatesVersion: 1, officialUpdatesVersion: 1, protocol: 2, errorBranch: true, diagnosticsVersion: 2, optionsVersion: 1, conditionsVersion: 1, complementaryBlocksVersion: 1, discordActionsVersion: 1, messageButtonsVersion: 2, variablesVersion: Number(process.versions.node.split('.')[0]) > 22 || (Number(process.versions.node.split('.')[0]) === 22 && Number(process.versions.node.split('.')[1]) >= 5) ? 1 : 0, metricsVersion: usageReady ? 1 : 0, profile, running: Boolean(child && child.exitCode === null && !child.signalCode), hasToken: await exists(secretPath), deploymentId: active });
|
|
160
161
|
if (process.connected) process.send?.({ type: 'agent-ready' }, () => {});
|
|
161
162
|
await usage.flush(send).catch(() => console.error('Statistiques locales indisponibles.'));
|
|
162
163
|
usageTimer = setInterval(() => usage.flush(send).catch(() => console.error('Statistiques locales indisponibles.')), 60000);
|
|
@@ -170,7 +171,7 @@ async function start(config) {
|
|
|
170
171
|
if (message.type === 'update') {
|
|
171
172
|
console.log(`[${new Date().toISOString()}] Mise à jour de l’agent demandée.`);
|
|
172
173
|
try {
|
|
173
|
-
await
|
|
174
|
+
await installOfficialUpdate(message.version || message.release?.version, { home, currentVersion: AGENT_VERSION, source: path.dirname(fileURLToPath(import.meta.url)), report: state => { send({ type: 'update-status', state }); console.log(({ downloading: 'Téléchargement du paquet signé.', installing: 'Signature vérifiée. Installation de la mise à jour.', restarting: 'Installation vérifiée. Redémarrage de l’agent.' })[state]); } });
|
|
174
175
|
updating = true; stopped = true; shutdownBot = true; socket.close(1000, 'Mise à jour');
|
|
175
176
|
} catch { send({ type: 'update-status', state: 'failed' }); console.error('Mise à jour refusée ou échouée. La version actuelle est conservée.'); }
|
|
176
177
|
} else if (message.type === 'secret') {
|
|
@@ -276,18 +277,16 @@ async function main() {
|
|
|
276
277
|
for (const entry of ['cli.mjs', 'instance.mjs', 'version.mjs', 'release-key.mjs', 'update.mjs', 'supervisor.mjs', 'secret.mjs', 'usage.mjs', 'compiler.mjs', 'compile-plan.mjs', 'package.json', 'npm-shrinkwrap.json', 'runtime', 'templates']) await cp(path.join(source, entry), path.join(install, entry), { recursive: true });
|
|
277
278
|
const modules = path.resolve(path.dirname(require.resolve('discord.js')), '../..');
|
|
278
279
|
await cp(modules, path.join(install, 'node_modules'), { recursive: true, filter: filename => path.resolve(filename) !== path.resolve(source) });
|
|
279
|
-
|
|
280
|
+
await rememberInstance(baseHome, config);
|
|
281
|
+
console.log('Agent lié : ' + config.agentId.slice(0, 8) + '.');
|
|
280
282
|
if (pendingFile) await unlink(pendingFile).catch(() => {});
|
|
281
|
-
console.log('Gestion : npx oft-agt start | stop | restart | status | logs | help');
|
|
282
|
-
console.log('Identifiant local court : ' + config.agentId.slice(0, 8) + ' (option --bot=' + config.agentId.slice(0, 8) + ' si plusieurs agents).');
|
|
283
|
-
console.log('Option globale : npm install -g oft-agt, puis oft-agt help');
|
|
284
|
-
console.log('Documentation : https://doc-builder.oftone.fr/agent');
|
|
285
283
|
if (args.includes('--foreground')) { process.env.OFTONE_FOREGROUND = '1'; return supervise(home, source); }
|
|
286
|
-
await startBackground(home, install); console.log('Agent
|
|
284
|
+
await startBackground(home, install); console.log('Agent démarré.'); return;
|
|
287
285
|
}
|
|
288
286
|
if (['run', 'start', 'stop', 'restart', 'status', 'logs', 'remove', 'token', 'upgrade'].includes(command)) {
|
|
289
287
|
const selector = args.find(arg => arg.startsWith('--bot='))?.slice(6) || args.find(arg => arg.startsWith('--instance='))?.slice('--instance='.length);
|
|
290
288
|
useHome(await resolveInstance(baseHome, path.dirname(fileURLToPath(import.meta.url)), selector));
|
|
289
|
+
if (selector) await rememberInstance(baseHome, JSON.parse(await readFile(configPath, 'utf8')));
|
|
291
290
|
}
|
|
292
291
|
if (command === 'upgrade') {
|
|
293
292
|
await stopBackground(home);
|
package/compile-plan.mjs
CHANGED
|
@@ -1,133 +1,303 @@
|
|
|
1
|
+
import { isVariableBlock, validateVariableBlock, validateVariableDefinitions } from './runtime/custom-variables.mjs';
|
|
2
|
+
import { validateButton } from './runtime/message-buttons.mjs';
|
|
3
|
+
import { isDiscordBlock, validateDiscordBlock } from './runtime/discord-blocks.mjs';
|
|
4
|
+
|
|
1
5
|
import { isCondition, validateCondition } from './runtime/conditions.mjs';
|
|
6
|
+
|
|
2
7
|
import { isOption, compileOptions } from './runtime/command-options.mjs';
|
|
8
|
+
|
|
3
9
|
import { normalizeMessageConfig, messageConfigError } from './runtime/message-config.mjs';
|
|
4
10
|
|
|
11
|
+
|
|
12
|
+
|
|
5
13
|
const ID = /^\d{17,20}$/;
|
|
14
|
+
|
|
6
15
|
const plain = value => value && typeof value === 'object' && !Array.isArray(value);
|
|
16
|
+
|
|
7
17
|
function fail(message) { throw new Error(message); }
|
|
18
|
+
|
|
8
19
|
function text(value, name, max, required = false) {
|
|
20
|
+
|
|
9
21
|
if (typeof value !== 'string' || value.length > max || (required && !value.trim())) fail(`${name} : texte ${required ? 'non vide ' : ''}de ${max} caractères maximum attendu.`);
|
|
22
|
+
|
|
10
23
|
return value;
|
|
24
|
+
|
|
11
25
|
}
|
|
12
26
|
|
|
27
|
+
|
|
28
|
+
|
|
13
29
|
/** Pure JSON -> validated function call plan. No filesystem, network or code evaluation. */
|
|
30
|
+
|
|
14
31
|
export function compile(input) {
|
|
32
|
+
|
|
15
33
|
if (!plain(input) || !Array.isArray(input.commands) || input.commands.length > 100) fail('commands doit être un tableau de 100 commandes maximum.');
|
|
34
|
+
|
|
35
|
+
const variableDefinitions = validateVariableDefinitions(input.variableDefinitions || []);
|
|
36
|
+
const variableDeletions = (input.variableDeletions || []).map(item => { if (!item || typeof item.id !== 'string' || !/^[A-Za-z0-9_-]{1,100}$/.test(item.id) || typeof item.purge !== 'boolean') fail('Suppression de variable invalide.'); return { id: item.id, purge: item.purge }; });
|
|
37
|
+
if (variableDeletions.length > 10000) fail('Trop de suppressions de variables.');
|
|
16
38
|
const names = new Set();
|
|
39
|
+
|
|
17
40
|
const commands = input.commands.map((entry, commandIndex) => {
|
|
41
|
+
|
|
18
42
|
const command = Array.isArray(entry?.blocks) ? entry : entry?.config;
|
|
43
|
+
|
|
19
44
|
if (!plain(command)) fail(`Commande ${commandIndex + 1} : configuration absente.`);
|
|
45
|
+
|
|
20
46
|
const name = text(command.name ?? entry.name, 'Nom de commande', 32, true);
|
|
47
|
+
|
|
21
48
|
if (!/^[a-z0-9_-]{1,32}$/.test(name)) fail(`/${name} : utilisez des minuscules, chiffres, tirets ou underscores.`);
|
|
49
|
+
|
|
22
50
|
if (names.has(name)) fail(`Commande dupliquée : /${name}.`);
|
|
51
|
+
|
|
23
52
|
names.add(name);
|
|
53
|
+
|
|
24
54
|
const description = text(command.description ?? entry.description, `/${name} description`, 100, true);
|
|
55
|
+
|
|
25
56
|
if (!Array.isArray(command.blocks) || !command.blocks.length || command.blocks.length > 500 || !Array.isArray(command.edges)) fail(`/${name} : blocs ou liaisons invalides (500 blocs maximum).`);
|
|
57
|
+
|
|
26
58
|
const blocks = new Map();
|
|
59
|
+
|
|
27
60
|
for (const block of command.blocks) {
|
|
61
|
+
|
|
28
62
|
if (!plain(block) || typeof block.id !== 'string' || !block.id || block.id.length > 128 || blocks.has(block.id)) fail(`/${name} : identifiant de bloc absent, invalide ou dupliqué.`);
|
|
29
|
-
|
|
63
|
+
|
|
64
|
+
if (!isVariableBlock(block.type) && !isDiscordBlock(block.type) && !isOption(block.type) && !isCondition(block.type) && !['root', 'errorRoot', 'conditionBranch', 'messageButton', 'sendMessage', 'wait', 'deleteMessage'].includes(block.type)) fail(`/${name}/${block.id} : type de bloc inconnu : ${block.type}.`);
|
|
65
|
+
|
|
30
66
|
if (!plain(block.config || {})) fail(`/${name}/${block.id} : paramètres invalides.`);
|
|
67
|
+
|
|
31
68
|
blocks.set(block.id, block);
|
|
69
|
+
|
|
32
70
|
}
|
|
71
|
+
|
|
33
72
|
const roots = [...blocks.values()].filter(block => block.type === 'root');
|
|
73
|
+
|
|
34
74
|
if (roots.length !== 1) fail(`/${name} doit contenir exactement un bloc racine.`);
|
|
75
|
+
|
|
35
76
|
const errorRoots = [...blocks.values()].filter(block => block.type === 'errorRoot');
|
|
77
|
+
|
|
36
78
|
if (errorRoots.length > 1) fail(`/${name} : une seule racine d’erreur est autorisée.`);
|
|
79
|
+
|
|
37
80
|
const options = compileOptions(command.blocks);
|
|
81
|
+
|
|
38
82
|
const optionLinks = new Set();
|
|
83
|
+
|
|
39
84
|
const conditionConfigs = new Map([...blocks.values()].filter(block => isCondition(block.type)).map(block => [block.id, validateCondition(block.type, block.config)]));
|
|
85
|
+
|
|
40
86
|
const branchLinks = new Map();
|
|
87
|
+
|
|
88
|
+
const buttonLinks=new Map();
|
|
89
|
+
const buttonBranches=[];
|
|
41
90
|
const next = new Map();
|
|
91
|
+
|
|
42
92
|
const incoming = new Set();
|
|
93
|
+
|
|
43
94
|
for (const edge of command.edges) {
|
|
95
|
+
if (blocks.get(edge?.target)?.type==='messageButton') {
|
|
96
|
+
const child=blocks.get(edge.target);
|
|
97
|
+
if(blocks.get(edge.source)?.type!=='sendMessage'||child.config.parentId!==edge.source||incoming.has(edge.target)) fail('Un bouton reste lié à son message.');
|
|
98
|
+
const list=buttonLinks.get(edge.source)||[];list.push(child);buttonLinks.set(edge.source,list);incoming.add(edge.target);continue;
|
|
99
|
+
}
|
|
100
|
+
if(blocks.get(edge?.source)?.type==='messageButton'&&Number(blocks.get(edge.source).config.style||1)>=5) fail('Un bouton lien ou achat ne déclenche pas de blocs.');
|
|
101
|
+
|
|
44
102
|
if (plain(edge) && isOption(blocks.get(edge.source)?.type)) {
|
|
103
|
+
|
|
45
104
|
if (edge.target !== roots[0].id || optionLinks.has(edge.source)) fail(`/${name} : chaque option doit être liée uniquement à la racine principale.`);
|
|
105
|
+
|
|
46
106
|
optionLinks.add(edge.source); continue;
|
|
107
|
+
|
|
47
108
|
}
|
|
109
|
+
|
|
48
110
|
if (plain(edge) && isOption(blocks.get(edge.target)?.type)) fail(`/${name} : une action ne peut pas être liée à une option.`);
|
|
111
|
+
|
|
49
112
|
if (!plain(edge) || !blocks.has(edge.source) || !blocks.has(edge.target) || ['root', 'errorRoot'].includes(blocks.get(edge.target)?.type)) fail(`/${name} : liaison invalide.`);
|
|
113
|
+
|
|
50
114
|
if (isCondition(blocks.get(edge.source).type)) {
|
|
115
|
+
|
|
51
116
|
const branch = blocks.get(edge.target);
|
|
117
|
+
|
|
52
118
|
const ruleId = branch.config?.ruleId;
|
|
119
|
+
|
|
53
120
|
const allowed = ['else', ...conditionConfigs.get(edge.source).rules.map(rule => rule.id)];
|
|
121
|
+
|
|
54
122
|
const linked = branchLinks.get(edge.source) || new Map();
|
|
123
|
+
|
|
55
124
|
if (branch.type !== 'conditionBranch' || branch.config.parentId !== edge.source || !allowed.includes(ruleId) || linked.has(ruleId) || incoming.has(edge.target)) fail(`/${name} : branche de condition invalide.`);
|
|
125
|
+
|
|
56
126
|
linked.set(ruleId, edge.target); branchLinks.set(edge.source, linked); incoming.add(edge.target); continue;
|
|
127
|
+
|
|
57
128
|
}
|
|
129
|
+
|
|
58
130
|
if (blocks.get(edge.target).type === 'conditionBranch') fail(`/${name} : les branches restent liées à leur condition.`);
|
|
131
|
+
|
|
59
132
|
if (next.has(edge.source)) fail(`/${name}/${edge.source} : plusieurs sorties ne sont pas encore prises en charge. Reliez les actions en séquence.`);
|
|
133
|
+
|
|
60
134
|
if (incoming.has(edge.target)) fail(`/${name}/${edge.target} : plusieurs entrées ne sont pas prises en charge.`);
|
|
135
|
+
|
|
61
136
|
next.set(edge.source, edge.target);
|
|
137
|
+
|
|
62
138
|
incoming.add(edge.target);
|
|
139
|
+
|
|
63
140
|
}
|
|
141
|
+
|
|
64
142
|
if (optionLinks.size !== options.length) fail(`/${name} : toutes les options doivent être liées à la racine.`);
|
|
143
|
+
|
|
65
144
|
for (const [id, config] of conditionConfigs) if (branchLinks.get(id)?.size !== config.rules.length + 1) fail(`/${name} : chaque condition nécessite ses branches et une branche Sinon.`);
|
|
145
|
+
|
|
146
|
+
for(const block of blocks.values()) if(block.type==='messageButton'&&!incoming.has(block.id))fail('Bouton détaché de son message.');
|
|
147
|
+
for(const list of buttonLinks.values()) {
|
|
148
|
+
const rows=[0,0,0,0,0]; if(list.length>25)fail('25 boutons maximum par message.');
|
|
149
|
+
for(const b of list){const c=validateButton(b.config);if(++rows[c.row]>5)fail('5 boutons maximum par ligne.');}
|
|
150
|
+
}
|
|
66
151
|
const visited = new Set(options.map(option => option.id));
|
|
152
|
+
|
|
67
153
|
function compilePath(rootId, inheritedMessages = new Set(), inheritedWait = 0, depth = 0) {
|
|
154
|
+
|
|
68
155
|
if (depth > 32) fail(`/${name} : conditions imbriquées trop profondément (32 maximum).`);
|
|
156
|
+
|
|
69
157
|
const emittedMessages = new Set(inheritedMessages);
|
|
158
|
+
|
|
70
159
|
const calls = [];
|
|
160
|
+
|
|
71
161
|
let current = rootId;
|
|
162
|
+
|
|
72
163
|
let waitBudget = inheritedWait;
|
|
164
|
+
|
|
73
165
|
while (current) {
|
|
166
|
+
|
|
74
167
|
if (visited.has(current)) fail(`/${name} : boucle détectée.`);
|
|
168
|
+
|
|
75
169
|
visited.add(current);
|
|
170
|
+
|
|
76
171
|
const block = blocks.get(current);
|
|
172
|
+
|
|
77
173
|
const config = block.config || {};
|
|
174
|
+
|
|
78
175
|
let args;
|
|
176
|
+
|
|
79
177
|
let fn;
|
|
178
|
+
|
|
80
179
|
if (isCondition(block.type)) {
|
|
180
|
+
|
|
81
181
|
const branches = branchLinks.get(current);
|
|
182
|
+
|
|
82
183
|
const rules = conditionConfigs.get(current).rules;
|
|
184
|
+
|
|
83
185
|
calls.push({ id: current, fn: 'condition', args: { kind: block.type, rules }, branches: rules.map(rule => ({ id: branches.get(rule.id), ruleId: rule.id, calls: compilePath(branches.get(rule.id), emittedMessages, waitBudget, depth + 1) })), otherwise: { id: branches.get('else'), calls: compilePath(branches.get('else'), emittedMessages, waitBudget, depth + 1) } });
|
|
186
|
+
|
|
187
|
+
} else if (isVariableBlock(block.type)) {
|
|
188
|
+
fn = block.type; args = validateVariableBlock(block.type, config, variableDefinitions);
|
|
189
|
+
} else if (isDiscordBlock(block.type)) {
|
|
190
|
+
|
|
191
|
+
fn = block.type; args = validateDiscordBlock(block.type,config);
|
|
192
|
+
|
|
193
|
+
if (block.type === 'createChannel') emittedMessages.add(current);
|
|
194
|
+
|
|
84
195
|
} else if (block.type === 'sendMessage') {
|
|
196
|
+
|
|
85
197
|
text(config.text ?? '', `/${name}/${current} message`, 2000);
|
|
198
|
+
|
|
86
199
|
if (config.embeds !== undefined && (!Array.isArray(config.embeds) || config.embeds.some(embed => !plain(embed)))) fail(`/${name}/${current} : embeds invalides.`);
|
|
200
|
+
|
|
87
201
|
const normal = normalizeMessageConfig(config);
|
|
202
|
+
|
|
88
203
|
if (!['reply', 'channel', 'dm'].includes(normal.sendMode)) fail(`/${name}/${current} : type d’envoi inconnu.`);
|
|
204
|
+
|
|
89
205
|
if (config.sendMode && config.replyMode && !['command', 'block', 'message'].includes(config.replyMode)) fail(`/${name}/${current} : mode de réponse inconnu.`);
|
|
90
|
-
|
|
206
|
+
|
|
207
|
+
if (config.channelMode && !['current', 'id', 'created'].includes(config.channelMode)) fail(`/${name}/${current} : mode de salon inconnu.`);
|
|
208
|
+
|
|
91
209
|
if (config.dmMode && !['author', 'id'].includes(config.dmMode)) fail(`/${name}/${current} : destinataire MP inconnu.`);
|
|
92
|
-
|
|
210
|
+
|
|
211
|
+
if (normal.channelMode === 'created' && (!emittedMessages.has(normal.createdChannelBlockId) || blocks.get(normal.createdChannelBlockId)?.type !== 'createChannel')) fail('Le salon créé doit provenir d’un bloc exécuté avant ce message.');
|
|
212
|
+
|
|
213
|
+
const error = messageConfigError({...normal,buttons:buttonLinks.get(current)}, command.blocks);
|
|
214
|
+
|
|
93
215
|
if (error) fail(`/${name}/${current} : ${error}`);
|
|
216
|
+
|
|
94
217
|
if (normal.sendMode === 'reply' && normal.replyMode === 'block' && !emittedMessages.has(normal.targetBlockId)) fail(`/${name}/${current} : le message cible doit être envoyé avant la réponse.`);
|
|
218
|
+
|
|
95
219
|
// Only accepted arguments are emitted; tokens and unrelated JSON properties are discarded.
|
|
96
|
-
|
|
220
|
+
|
|
221
|
+
args = Object.fromEntries(['text', 'embeds', 'sendMode', 'replyMode', 'targetBlockId', 'messageId', 'channelMode', 'channelId', 'createdChannelBlockId', 'dmMode', 'userId', 'ephemeral'].map(key => [key, normal[key]]));
|
|
222
|
+
|
|
97
223
|
args.embeds = normal.embeds.map(embed => ({
|
|
224
|
+
|
|
98
225
|
...Object.fromEntries(['title', 'description', 'url', 'color', 'timestamp'].filter(key => embed[key] !== undefined).map(key => [key, embed[key]])),
|
|
226
|
+
|
|
99
227
|
...(embed.author ? { author: Object.fromEntries(['name', 'url', 'icon_url'].filter(key => embed.author[key] !== undefined).map(key => [key, embed.author[key]])) } : {}),
|
|
228
|
+
|
|
100
229
|
...(embed.footer ? { footer: Object.fromEntries(['text', 'icon_url'].filter(key => embed.footer[key] !== undefined).map(key => [key, embed.footer[key]])) } : {}),
|
|
230
|
+
|
|
101
231
|
...(embed.image ? { image: { url: embed.image.url } } : {}),
|
|
232
|
+
|
|
102
233
|
...(embed.thumbnail ? { thumbnail: { url: embed.thumbnail.url } } : {}),
|
|
234
|
+
|
|
103
235
|
...(embed.fields ? { fields: embed.fields.map(field => ({ name: field.name, value: field.value, inline: Boolean(field.inline) })) } : {}),
|
|
236
|
+
|
|
104
237
|
}));
|
|
238
|
+
|
|
105
239
|
fn = 'sendMessage';
|
|
240
|
+
|
|
106
241
|
emittedMessages.add(current);
|
|
242
|
+
const children=buttonLinks.get(current)||[];
|
|
243
|
+
if(children.length) args.buttons=children.map(child=>({id:child.id,config:validateButton(child.config)}));
|
|
244
|
+
for(const child of children){
|
|
245
|
+
const config=validateButton(child.config);
|
|
246
|
+
const childCalls=compilePath(child.id,emittedMessages,0,depth+1);
|
|
247
|
+
buttonBranches.push({id:child.id,parentId:current,config,calls:childCalls});
|
|
248
|
+
}
|
|
249
|
+
|
|
107
250
|
} else if (block.type === 'wait') {
|
|
251
|
+
|
|
108
252
|
const seconds = Number(config.seconds);
|
|
253
|
+
|
|
109
254
|
if (!Number.isFinite(seconds) || seconds < 0 || seconds > 300) fail(`/${name}/${current} : délai attendu entre 0 et 300 secondes.`);
|
|
255
|
+
|
|
110
256
|
waitBudget += seconds;
|
|
257
|
+
|
|
111
258
|
if (waitBudget > 840) fail(`/${name} : les délais cumulés dépassent 14 minutes.`);
|
|
259
|
+
|
|
112
260
|
fn = 'wait'; args = { seconds };
|
|
261
|
+
|
|
113
262
|
} else if (block.type === 'deleteMessage') {
|
|
263
|
+
|
|
114
264
|
const deleteMode = config.deleteMode || 'block';
|
|
265
|
+
|
|
115
266
|
if (!['block', 'manual'].includes(deleteMode)) fail(`/${name}/${current} : mode de suppression invalide.`);
|
|
116
|
-
|
|
267
|
+
|
|
268
|
+
if (deleteMode === 'block' && (!emittedMessages.has(config.targetBlockId) || blocks.get(config.targetBlockId)?.type !== 'sendMessage')) fail(`/${name}/${current} : le message à supprimer doit déjà avoir été envoyé.`);
|
|
269
|
+
|
|
117
270
|
if (deleteMode === 'manual' && !ID.test(config.messageId || '') && !/^\{[a-z0-9_-]+_option(?:_id)?\}$/.test(config.messageId || '')) fail(`/${name}/${current} : ID de message invalide.`);
|
|
271
|
+
|
|
118
272
|
fn = 'deleteMessage'; args = deleteMode === 'block' ? { deleteMode, targetBlockId: config.targetBlockId } : { deleteMode, messageId: config.messageId };
|
|
273
|
+
|
|
119
274
|
}
|
|
275
|
+
|
|
120
276
|
if (fn) calls.push({ id: current, fn, args });
|
|
277
|
+
|
|
121
278
|
current = next.get(current);
|
|
279
|
+
|
|
122
280
|
}
|
|
281
|
+
|
|
123
282
|
return calls;
|
|
283
|
+
|
|
124
284
|
}
|
|
285
|
+
|
|
125
286
|
const calls = compilePath(roots[0].id);
|
|
287
|
+
|
|
126
288
|
const errorCalls = errorRoots.length ? compilePath(errorRoots[0].id) : undefined;
|
|
289
|
+
|
|
127
290
|
if (visited.size !== blocks.size) fail(`/${name} : blocs non reliés à une racine ou boucle dans une partie non accessible.`);
|
|
291
|
+
|
|
128
292
|
if (!calls.length || (errorCalls && !errorCalls.length)) fail(`/${name} : ajoutez au moins une action à chaque racine.`);
|
|
129
|
-
|
|
293
|
+
|
|
294
|
+
return { name, description, ...(options.length ? { options } : {}), calls, ...(buttonBranches.length?{buttonBranches}:{}), ...(errorCalls ? { errorCalls } : {}) };
|
|
295
|
+
|
|
130
296
|
});
|
|
131
|
-
|
|
297
|
+
|
|
298
|
+
return JSON.parse(JSON.stringify({ schemaVersion: 1, ...(input.variableDefinitions !== undefined ? { variableDefinitions, variableDeletions } : {}), bot: { name: typeof input.bot?.name === 'string' ? input.bot.name.slice(0, 100) : 'Oftone Bot' }, commands }));
|
|
299
|
+
|
|
132
300
|
}
|
|
133
301
|
|
|
302
|
+
|
|
303
|
+
|
package/compiler.mjs
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { readFile, writeFile, mkdir, cp, access } from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { compile } from './compile-plan.mjs';
|
|
6
|
-
export { compile } from './compile-plan.mjs';
|
|
7
|
-
function fail(message) { throw new Error(message); }
|
|
8
|
-
|
|
9
|
-
const ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
-
/** Emit a standalone Node.js project. Refuse to replace an existing directory. */
|
|
11
|
-
export async function writeBot(input, outputDirectory) {
|
|
12
|
-
const plan = compile(input);
|
|
13
|
-
const output = path.resolve(outputDirectory);
|
|
14
|
-
try { await access(output); fail(`Le dossier de sortie existe déjà : ${output}`); }
|
|
15
|
-
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
16
|
-
await mkdir(output, { recursive: false });
|
|
17
|
-
await cp(path.join(ROOT, 'runtime'), path.join(output, 'runtime'), { recursive: true });
|
|
18
|
-
await cp(path.join(ROOT, 'templates/bot.mjs'), path.join(output, 'bot.mjs'));
|
|
19
|
-
await writeFile(path.join(output, 'plan.json'), JSON.stringify(plan, null, 2) + '\n');
|
|
20
|
-
await writeFile(path.join(output, 'package.json'), JSON.stringify({ name: 'oftone-generated-bot', version: '1.0.0', private: true, type: 'module', engines: { node: '>=22' }, scripts: { start: 'node --env-file=.env bot.mjs', deploy: 'node --env-file=.env bot.mjs --deploy' }, dependencies: { 'discord.js': '^14.14.0' } }, null, 2) + '\n');
|
|
21
|
-
await writeFile(path.join(output, '.env.example'), '# Token saisi uniquement sur votre serveur.\nDISCORD_TOKEN=\n# Application ID Discord (pas l’identifiant du projet Builder).\nDISCORD_CLIENT_ID=\n# Optionnel : serveur de test. Vide = commandes globales.\nDISCORD_GUILD_ID=\n');
|
|
22
|
-
await writeFile(path.join(output, '.gitignore'), '.env\nnode_modules/\n');
|
|
23
|
-
return { output, plan };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function main() {
|
|
27
|
-
const args = process.argv.slice(2);
|
|
28
|
-
if (!args.length || args.includes('--help')) {
|
|
29
|
-
console.log('Oftone Compiler\n node compiler.mjs <builder.json> --out <nouveau-dossier>\n node compiler.mjs --stdin --out <nouveau-dossier>\n node compiler.mjs <builder.json> --check\n\nAucun token requis. --stdin permet une invocation par un futur service.');
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const allowed = new Set(['--stdin', '--check', '--out']);
|
|
33
|
-
for (const arg of args) if (arg.startsWith('--') && !allowed.has(arg)) fail(`Option inconnue : ${arg}`);
|
|
34
|
-
let source;
|
|
35
|
-
if (args[0] === '--stdin') {
|
|
36
|
-
const chunks = [];
|
|
37
|
-
let size = 0;
|
|
38
|
-
for await (const chunk of process.stdin) {
|
|
39
|
-
size += chunk.length;
|
|
40
|
-
if (size > 5 * 1024 * 1024) fail('Le JSON dépasse 5 Mo.');
|
|
41
|
-
chunks.push(chunk);
|
|
42
|
-
}
|
|
43
|
-
source = Buffer.concat(chunks).toString('utf8');
|
|
44
|
-
} else source = await readFile(args[0], 'utf8');
|
|
45
|
-
if (Buffer.byteLength(source) > 5 * 1024 * 1024) fail('Le JSON dépasse 5 Mo.');
|
|
46
|
-
const input = JSON.parse(source);
|
|
47
|
-
if (args.includes('--check')) { const plan = compile(input); console.log(`JSON valide : ${plan.commands.length} commande(s).`); return; }
|
|
48
|
-
const outIndex = args.indexOf('--out');
|
|
49
|
-
if (outIndex < 0 || !args[outIndex + 1] || args[outIndex + 1].startsWith('--')) fail('Précisez --out <nouveau-dossier>.');
|
|
50
|
-
const { output, plan } = await writeBot(input, args[outIndex + 1]);
|
|
51
|
-
console.log(`Bot généré : ${output}\n${plan.commands.length} commande(s). Renseignez .env, puis npm install et npm run deploy avant npm start.`);
|
|
52
|
-
}
|
|
53
|
-
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
54
|
-
main().catch(error => { console.error(`Compilation refusée : ${error.message}`); process.exitCode = 1; });
|
|
55
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile, writeFile, mkdir, cp, access } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { compile } from './compile-plan.mjs';
|
|
6
|
+
export { compile } from './compile-plan.mjs';
|
|
7
|
+
function fail(message) { throw new Error(message); }
|
|
8
|
+
|
|
9
|
+
const ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
/** Emit a standalone Node.js project. Refuse to replace an existing directory. */
|
|
11
|
+
export async function writeBot(input, outputDirectory) {
|
|
12
|
+
const plan = compile(input);
|
|
13
|
+
const output = path.resolve(outputDirectory);
|
|
14
|
+
try { await access(output); fail(`Le dossier de sortie existe déjà : ${output}`); }
|
|
15
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
16
|
+
await mkdir(output, { recursive: false });
|
|
17
|
+
await cp(path.join(ROOT, 'runtime'), path.join(output, 'runtime'), { recursive: true });
|
|
18
|
+
await cp(path.join(ROOT, 'templates/bot.mjs'), path.join(output, 'bot.mjs'));
|
|
19
|
+
await writeFile(path.join(output, 'plan.json'), JSON.stringify(plan, null, 2) + '\n');
|
|
20
|
+
await writeFile(path.join(output, 'package.json'), JSON.stringify({ name: 'oftone-generated-bot', version: '1.0.0', private: true, type: 'module', engines: { node: '>=22.5.0' }, scripts: { start: 'node --env-file=.env bot.mjs', deploy: 'node --env-file=.env bot.mjs --deploy' }, dependencies: { 'discord.js': '^14.14.0' } }, null, 2) + '\n');
|
|
21
|
+
await writeFile(path.join(output, '.env.example'), '# Token saisi uniquement sur votre serveur.\nDISCORD_TOKEN=\n# Application ID Discord (pas l’identifiant du projet Builder).\nDISCORD_CLIENT_ID=\n# Optionnel : serveur de test. Vide = commandes globales.\nDISCORD_GUILD_ID=\n');
|
|
22
|
+
await writeFile(path.join(output, '.gitignore'), '.env\nnode_modules/\ndata/\n');
|
|
23
|
+
return { output, plan };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function main() {
|
|
27
|
+
const args = process.argv.slice(2);
|
|
28
|
+
if (!args.length || args.includes('--help')) {
|
|
29
|
+
console.log('Oftone Compiler\n node compiler.mjs <builder.json> --out <nouveau-dossier>\n node compiler.mjs --stdin --out <nouveau-dossier>\n node compiler.mjs <builder.json> --check\n\nAucun token requis. --stdin permet une invocation par un futur service.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const allowed = new Set(['--stdin', '--check', '--out']);
|
|
33
|
+
for (const arg of args) if (arg.startsWith('--') && !allowed.has(arg)) fail(`Option inconnue : ${arg}`);
|
|
34
|
+
let source;
|
|
35
|
+
if (args[0] === '--stdin') {
|
|
36
|
+
const chunks = [];
|
|
37
|
+
let size = 0;
|
|
38
|
+
for await (const chunk of process.stdin) {
|
|
39
|
+
size += chunk.length;
|
|
40
|
+
if (size > 5 * 1024 * 1024) fail('Le JSON dépasse 5 Mo.');
|
|
41
|
+
chunks.push(chunk);
|
|
42
|
+
}
|
|
43
|
+
source = Buffer.concat(chunks).toString('utf8');
|
|
44
|
+
} else source = await readFile(args[0], 'utf8');
|
|
45
|
+
if (Buffer.byteLength(source) > 5 * 1024 * 1024) fail('Le JSON dépasse 5 Mo.');
|
|
46
|
+
const input = JSON.parse(source);
|
|
47
|
+
if (args.includes('--check')) { const plan = compile(input); console.log(`JSON valide : ${plan.commands.length} commande(s).`); return; }
|
|
48
|
+
const outIndex = args.indexOf('--out');
|
|
49
|
+
if (outIndex < 0 || !args[outIndex + 1] || args[outIndex + 1].startsWith('--')) fail('Précisez --out <nouveau-dossier>.');
|
|
50
|
+
const { output, plan } = await writeBot(input, args[outIndex + 1]);
|
|
51
|
+
console.log(`Bot généré : ${output}\n${plan.commands.length} commande(s). Renseignez .env, puis npm install et npm run deploy avant npm start.`);
|
|
52
|
+
}
|
|
53
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
54
|
+
main().catch(error => { console.error(`Compilation refusée : ${error.message}`); process.exitCode = 1; });
|
|
55
|
+
}
|
package/instance.mjs
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { readFile, access, readdir } from 'node:fs/promises';
|
|
2
|
+
import { readFile, writeFile, rename, access, readdir } from 'node:fs/promises';
|
|
3
|
+
|
|
4
|
+
export async function rememberInstance(base, config) {
|
|
5
|
+
instanceDirectory(base, config.object, config.agentId);
|
|
6
|
+
const file = path.join(base, 'active-instance.json');
|
|
7
|
+
await writeFile(file + '.tmp', JSON.stringify({ object: config.object, agentId: config.agentId }), { mode: 0o600 });
|
|
8
|
+
await rename(file + '.tmp', file);
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
export async function listInstances(base) {
|
|
5
12
|
const result = [];
|
|
@@ -45,5 +52,10 @@ export async function resolveInstance(base, source, selector) {
|
|
|
45
52
|
}
|
|
46
53
|
const instances = await listInstances(base);
|
|
47
54
|
if (instances.length === 1) return instances[0].home;
|
|
55
|
+
try {
|
|
56
|
+
const active = JSON.parse(await readFile(path.join(base, 'active-instance.json'), 'utf8'));
|
|
57
|
+
const match = instances.find(item => item.object === active.object && item.agentId === active.agentId);
|
|
58
|
+
if (match) return match.home;
|
|
59
|
+
} catch {}
|
|
48
60
|
throw new Error('INSTANCE_REQUIRED');
|
|
49
61
|
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oft-agt",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "oft-agt",
|
|
9
|
-
"version": "0.5.
|
|
9
|
+
"version": "0.5.7",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"discord.js": "^14.14.0",
|
|
12
12
|
"ws": "^8.18.0"
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"oftone-agent": "cli.mjs"
|
|
17
17
|
},
|
|
18
18
|
"engines": {
|
|
19
|
-
"node": ">=22"
|
|
19
|
+
"node": ">=22.5"
|
|
20
20
|
},
|
|
21
21
|
"license": "UNLICENSED"
|
|
22
22
|
},
|