oft-agt 0.5.14 → 0.5.16
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/cli.mjs +6 -2
- package/compiler.mjs +2 -0
- package/npm-shrinkwrap.json +17 -17
- package/package.json +1 -1
- package/runtime/api-request.mjs +9 -2
- package/runtime/presence.mjs +14 -0
- package/runtime/version.mjs +1 -0
- package/templates/bot.mjs +7 -1
- package/version.mjs +1 -1
- package/README.md +0 -36
package/cli.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
import { spawn } from 'node:child_process';
|
|
12
12
|
import WebSocket from 'ws';
|
|
13
13
|
import {secretOperation,readApiSecret} from './runtime/api-secrets.mjs';
|
|
14
|
+
import {validatePresence} from './runtime/presence.mjs';
|
|
14
15
|
import {requestApi} from './runtime/api-request.mjs';
|
|
15
16
|
import { writeBot, compile } from './compiler.mjs';
|
|
16
17
|
import { acceptSecret } from './secret.mjs';
|
|
@@ -29,6 +30,7 @@ let child;
|
|
|
29
30
|
let active;
|
|
30
31
|
let usage;
|
|
31
32
|
let usageReady = true;
|
|
33
|
+
async function runtimeVersion(){try{const state=JSON.parse(await readFile(path.join(home,'active.json'),'utf8'));if(!/^[a-f0-9-]{36}$/.test(state.id))return null;const meta=JSON.parse(await readFile(path.join(home,'releases',state.id,'runtime-version.json'),'utf8'));return /^\d+\.\d+\.\d+$/.test(meta.version)?meta.version:null;}catch{return null;}}
|
|
32
34
|
function watchUsage(process) { process.on('message', message => { try { usage?.record(message); } catch { console.error('Mesure statistique ignorée.'); } }); return process; }
|
|
33
35
|
async function exists(p) { try { await access(p); return true; } catch { return false; } }
|
|
34
36
|
async function save(p, data) { await writeFile(p + '.tmp', typeof data === 'string' ? data : JSON.stringify(data), { mode: 0o600 }); await rename(p + '.tmp', p); }
|
|
@@ -160,7 +162,7 @@ async function start(config) {
|
|
|
160
162
|
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 = ''; }
|
|
161
163
|
} catch {}
|
|
162
164
|
}
|
|
163
|
-
send({ type: 'hello', agentVersion: AGENT_VERSION, updatesVersion: 1, officialUpdatesVersion: 1, protocol: 2, errorBranch: true, diagnosticsVersion: 2, optionsVersion: 1, conditionsVersion: 1, eventsVersion: 1, apiVersion: 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 });
|
|
165
|
+
send({ type: 'hello', agentVersion: AGENT_VERSION, runtimeVersion:await runtimeVersion(), updatesVersion: 1, officialUpdatesVersion: 1, protocol: 2, errorBranch: true, diagnosticsVersion: 2, optionsVersion: 1, conditionsVersion: 1, eventsVersion: 1, apiVersion: 1, presenceVersion: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 });
|
|
164
166
|
if (process.connected) process.send?.({ type: 'agent-ready' }, () => {});
|
|
165
167
|
await usage.flush(send).catch(() => console.error('Statistiques locales indisponibles.'));
|
|
166
168
|
usageTimer = setInterval(() => usage.flush(send).catch(() => console.error('Statistiques locales indisponibles.')), 60000);
|
|
@@ -195,11 +197,13 @@ async function start(config) {
|
|
|
195
197
|
if (message.command === 'stop') { await save(path.join(home, 'paused.json'), {}); await stopBot(); await usage.flush(send).catch(() => {}); send({ type: 'runtime-status', state: 'stopped' }); }
|
|
196
198
|
else if (message.command === 'restart') { await restartBot(config); if (await exists(path.join(home, 'paused.json'))) await unlink(path.join(home, 'paused.json')); send({ type: 'runtime-status', state: 'running' }); }
|
|
197
199
|
} catch { send({ type: 'runtime-status', state: 'failed' }); }
|
|
200
|
+
} else if(message.type==='presence'){
|
|
201
|
+
try{const presence=validatePresence(message.config);await save(path.join(home,'presence.json'),presence);if(child?.connected)child.send({type:'presence',config:presence});}catch{console.error('Presence invalide.');}return;
|
|
198
202
|
} else if(message.type==='api-tool'){try{const result=message.payload.op==='test'?await requestApi(message.payload.config,{resolveSecret:name=>readApiSecret(name,home)}):await secretOperation(home,config.agentId,message.payload);const encoded=Buffer.from(JSON.stringify({result})).toString('base64');if(encoded.length>560000)throw Error('Réponse trop volumineuse.');const total=Math.ceil(encoded.length/1400);for(let index=0;index<total;index++)send({type:'api-result',id:message.id,index,total,chunk:encoded.slice(index*1400,(index+1)*1400)});}catch{send({type:'api-result',id:message.id,error:'Opération API impossible. Vérifiez la requête et les secrets.'});}return;}
|
|
199
203
|
if (message.type === 'deploy') {
|
|
200
204
|
console.log(`[${new Date().toISOString()}] Nouveau déploiement reçu.`);
|
|
201
205
|
if (await exists(path.join(home, 'paused.json'))) await unlink(path.join(home, 'paused.json'));
|
|
202
|
-
try { await deploy(config, message, state => { send({ type: 'deployment-status', id: message.id, state }); console.log(({ received: 'Déploiement validé.', installing: 'Préparation du bot.', running: 'Déploiement terminé. Bot démarré.', failed: 'Échec du déploiement.' })[state] || 'État du déploiement modifié.'); }); }
|
|
206
|
+
try { await deploy(config, message, async state => { send({ type: 'deployment-status', id: message.id, state, runtimeVersion:state==='running'?await runtimeVersion():null }); console.log(({ received: 'Déploiement validé.', installing: 'Préparation du bot.', running: 'Déploiement terminé. Bot démarré.', failed: 'Échec du déploiement.' })[state] || 'État du déploiement modifié.'); }); }
|
|
203
207
|
catch { send({ type: 'deployment-status', id: message.id, state: 'failed' }); console.error('Déploiement refusé ou échoué.'); }
|
|
204
208
|
}
|
|
205
209
|
}).catch(() => console.error('Message refusé.'));
|
package/compiler.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {RUNTIME_VERSION} from './runtime/version.mjs';
|
|
2
3
|
import { readFile, writeFile, mkdir, cp, access } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -16,6 +17,7 @@ export async function writeBot(input, outputDirectory) {
|
|
|
16
17
|
await mkdir(output, { recursive: false });
|
|
17
18
|
await cp(path.join(ROOT, 'runtime'), path.join(output, 'runtime'), { recursive: true });
|
|
18
19
|
await cp(path.join(ROOT, 'templates/bot.mjs'), path.join(output, 'bot.mjs'));
|
|
20
|
+
await writeFile(path.join(output,'runtime-version.json'),JSON.stringify({version:RUNTIME_VERSION}));
|
|
19
21
|
await writeFile(path.join(output, 'plan.json'), JSON.stringify(plan, null, 2) + '\n');
|
|
20
22
|
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
23
|
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');
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oft-agt",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.16",
|
|
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.16",
|
|
10
|
+
"license": "UNLICENSED",
|
|
10
11
|
"dependencies": {
|
|
11
12
|
"discord.js": "^14.14.0",
|
|
12
13
|
"ws": "^8.18.0"
|
|
@@ -17,19 +18,18 @@
|
|
|
17
18
|
},
|
|
18
19
|
"engines": {
|
|
19
20
|
"node": ">=22.5"
|
|
20
|
-
}
|
|
21
|
-
"license": "UNLICENSED"
|
|
21
|
+
}
|
|
22
22
|
},
|
|
23
23
|
"node_modules/@discordjs/builders": {
|
|
24
|
-
"version": "1.
|
|
25
|
-
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.
|
|
26
|
-
"integrity": "sha512-
|
|
24
|
+
"version": "1.15.0",
|
|
25
|
+
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.15.0.tgz",
|
|
26
|
+
"integrity": "sha512-AifaDWTw6WUDhKBn8E6mwwoyjDAcfsYq+ZE0nKSu1dMH1NLR22D3hGkNx+ctEv8NqHEHdAZnP5REteHDmn1lkA==",
|
|
27
27
|
"license": "Apache-2.0",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@discordjs/formatters": "^0.6.2",
|
|
30
30
|
"@discordjs/util": "^1.2.0",
|
|
31
31
|
"@sapphire/shapeshift": "^4.0.0",
|
|
32
|
-
"discord-api-types": "^0.38.
|
|
32
|
+
"discord-api-types": "^0.38.50",
|
|
33
33
|
"fast-deep-equal": "^3.1.3",
|
|
34
34
|
"ts-mixer": "^6.0.4",
|
|
35
35
|
"tslib": "^2.6.3"
|
|
@@ -218,19 +218,19 @@
|
|
|
218
218
|
"license": "MIT"
|
|
219
219
|
},
|
|
220
220
|
"node_modules/discord.js": {
|
|
221
|
-
"version": "14.
|
|
222
|
-
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.
|
|
223
|
-
"integrity": "sha512-
|
|
221
|
+
"version": "14.28.1",
|
|
222
|
+
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.28.1.tgz",
|
|
223
|
+
"integrity": "sha512-Je5OcqgurnIUQK4vR+gQnmzOf/lNQsD6d/UkV8ZOj+bFa5zJXPyiGXlbywp2EagwshVT18y7YNQNSRd3FjYkDw==",
|
|
224
224
|
"license": "Apache-2.0",
|
|
225
225
|
"dependencies": {
|
|
226
|
-
"@discordjs/builders": "^1.
|
|
226
|
+
"@discordjs/builders": "^1.15.0",
|
|
227
227
|
"@discordjs/collection": "1.5.3",
|
|
228
228
|
"@discordjs/formatters": "^0.6.2",
|
|
229
|
-
"@discordjs/rest": "^2.6.
|
|
229
|
+
"@discordjs/rest": "^2.6.3",
|
|
230
230
|
"@discordjs/util": "^1.2.0",
|
|
231
231
|
"@discordjs/ws": "^1.2.3",
|
|
232
232
|
"@sapphire/snowflake": "3.5.5",
|
|
233
|
-
"discord-api-types": "^0.38.
|
|
233
|
+
"discord-api-types": "^0.38.50",
|
|
234
234
|
"fast-deep-equal": "3.1.3",
|
|
235
235
|
"lodash.snakecase": "4.1.1",
|
|
236
236
|
"magic-bytes.js": "^1.13.0",
|
|
@@ -296,9 +296,9 @@
|
|
|
296
296
|
"license": "MIT"
|
|
297
297
|
},
|
|
298
298
|
"node_modules/ws": {
|
|
299
|
-
"version": "8.22.
|
|
300
|
-
"resolved": "https://registry.npmjs.org/ws/-/ws-8.22.
|
|
301
|
-
"integrity": "sha512-
|
|
299
|
+
"version": "8.22.1",
|
|
300
|
+
"resolved": "https://registry.npmjs.org/ws/-/ws-8.22.1.tgz",
|
|
301
|
+
"integrity": "sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==",
|
|
302
302
|
"license": "MIT",
|
|
303
303
|
"engines": {
|
|
304
304
|
"node": ">=10.0.0"
|
package/package.json
CHANGED
package/runtime/api-request.mjs
CHANGED
|
@@ -4,13 +4,20 @@ import https from 'node:https';
|
|
|
4
4
|
import {lookup} from 'node:dns';
|
|
5
5
|
import {isIP} from 'node:net';
|
|
6
6
|
import {validateApiRequest} from './api-config.mjs';
|
|
7
|
-
export function publicAddress(ip){
|
|
7
|
+
export function publicAddress(ip){
|
|
8
|
+
if(isIP(ip)===6){const parts=ip.toLowerCase().split(':'),first=parseInt(parts[0],16),second=parseInt(parts[1]||'0',16);return first>=0x2000&&first<=0x3fff&&first!==0x2002&&first!==0x3fff&&!(first===0x2001&&(second<0x200||second===0xdb8));}
|
|
9
|
+
if(isIP(ip)!==4)return false;
|
|
10
|
+
const [a,b,c]=ip.split('.').map(Number);
|
|
11
|
+
return a>0&&a<224&&![10,127].includes(a)&&!(a===169&&b===254)&&!(a===172&&b>=16&&b<=31)&&!(a===192&&(b===168||b===0&&(c===0||c===2)||b===88&&c===99))&&!(a===100&&b>=64&&b<=127)&&!(a===198&&(b===18||b===19||b===51&&c===100))&&!(a===203&&b===0&&c===113);
|
|
12
|
+
}
|
|
8
13
|
export async function requestApi(raw,{resolveSecret=async()=>{throw Error('Secret indisponible.');},signal}={}){
|
|
9
14
|
const resolvedSecrets=[];const c=validateApiRequest(raw),replace=async s=>{let out=s;for(const m of s.matchAll(/\{secret\.([A-Za-z][A-Za-z0-9_]*)\}/g)){const value=await resolveSecret(m[1]);resolvedSecrets.push(value);out=out.replaceAll(m[0],value);}return out;};
|
|
10
15
|
const url=new URL(await replace(c.url));if(url.username||url.password||!['http:','https:'].includes(url.protocol))throw Error('URL refusée.');
|
|
11
16
|
for(const p of c.params)if(p.key)url.searchParams.append(p.key,await replace(p.value));
|
|
12
17
|
const headers={'User-Agent':'Oftone-Agent/1'};for(const h of c.headers)if(h.key){if(/^(host|content-length|connection|transfer-encoding|upgrade)$/i.test(h.key)||/[\r\n]/.test(h.key))throw Error('En-tête refusé.');headers[h.key]=await replace(h.value);}
|
|
13
|
-
|
|
18
|
+
let body;let bodyJson;try{bodyJson=JSON.parse(c.body);}catch{}
|
|
19
|
+
if(bodyJson!==undefined){const resolve=async value=>typeof value==='string'?replace(value):Array.isArray(value)?Promise.all(value.map(resolve)):value&&typeof value==='object'?Object.fromEntries(await Promise.all(Object.entries(value).map(async([k,v])=>[k,await resolve(v)]))):value;body=JSON.stringify(await resolve(bodyJson));}else body=await replace(c.body);
|
|
20
|
+
if(Buffer.byteLength(body)>65536)throw Error('Corps trop volumineux.');if(resolvedSecrets.length&&url.protocol!=='https:')throw Error('HTTPS requis pour utiliser un secret.');
|
|
14
21
|
const result=await new Promise((resolve,reject)=>{
|
|
15
22
|
if(isIP(url.hostname.replaceAll(/[\[\]]/g,''))&&!publicAddress(url.hostname.replaceAll(/[\[\]]/g,'')))return reject(Error('Adresse locale refusée.'));
|
|
16
23
|
const req=(url.protocol==='https:'?https:http).request(url,{method:c.method,headers,signal:signal?AbortSignal.any([signal,AbortSignal.timeout(c.timeout)]):AbortSignal.timeout(c.timeout),lookup:(host,options,cb)=>lookup(host,{all:true},(err,addresses)=>{if(err||!addresses.length||addresses.some(a=>!publicAddress(a.address)))return cb(Error('Adresse réseau refusée.'));const a=addresses[0];cb(null,options.all?[a]:a.address,a.family);})},res=>{let size=0;const chunks=[];res.on('data',chunk=>{size+=chunk.length;if(size>262144){req.destroy(Error('Réponse trop volumineuse.'));return;}chunks.push(chunk);});res.on('end',()=>{const text=Buffer.concat(chunks).toString('utf8');let response;try{response=JSON.parse(text);}catch{response=text;}resolve({status:res.statusCode,statusText:res.statusMessage||'',response});});res.on('error',reject);});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DEFAULT_PRESENCE = Object.freeze({status:'online',type:'none',name:'',url:''});
|
|
2
|
+
export function validatePresence(value) {
|
|
3
|
+
if(!value||typeof value!=='object'||Array.isArray(value))throw Error('Présence invalide.');
|
|
4
|
+
const {status,type,name='',url=''}=value;
|
|
5
|
+
if(!['online','idle','dnd','invisible'].includes(status)||!['none','playing','streaming','listening','watching','competing','custom'].includes(type))throw Error('Statut ou activité invalide.');
|
|
6
|
+
if(typeof name!=='string'||name.trim().length>128||typeof url!=='string'||url.length>512)throw Error('Activité invalide.');
|
|
7
|
+
if(type!=='none'&&!name.trim())throw Error('Indiquez le texte de l’activité.');
|
|
8
|
+
if(type==='streaming'&&!/^https:\/\/(www\.)?(twitch\.tv\/[^\s]+|youtube\.com\/(watch\?v=|live\/)[^\s]+)$/.test(url))throw Error('Lien Twitch ou YouTube HTTPS requis.');
|
|
9
|
+
return {status,type,name:type==='none'?'':name.trim(),url:type==='streaming'?url:''};
|
|
10
|
+
}
|
|
11
|
+
export function discordPresence(config){
|
|
12
|
+
const c=validatePresence(config),types={playing:0,streaming:1,listening:2,watching:3,custom:4,competing:5};
|
|
13
|
+
return {status:c.status,afk:c.status==='idle',activities:c.type==='none'?[]:[{name:c.type==='custom'?'Custom Status':c.name,type:types[c.type],...(c.type==='custom'?{state:c.name}:{}),...(c.url?{url:c.url}:{})}]};
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const RUNTIME_VERSION = '0.5.16';
|
package/templates/bot.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import {DEFAULT_PRESENCE,discordPresence} from './runtime/presence.mjs';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { EVENTS } from './runtime/events.mjs';
|
|
2
4
|
import { registerEvents } from './runtime/event-runtime.mjs';
|
|
3
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -29,7 +31,11 @@ if (process.argv.includes('--deploy')) {
|
|
|
29
31
|
const client = new Client({ intents:[...intentNames].map(name=>GatewayIntentBits[name]), partials:[Partials.Message,Partials.Channel,Partials.Reaction,Partials.User,Partials.GuildMember] });
|
|
30
32
|
registerEvents(client,eventGraphs,variableOptions);
|
|
31
33
|
const commands = new Map(plan.commands.filter(command=>!command.trigger).map(command => [command.name, command]));
|
|
32
|
-
|
|
34
|
+
let presenceConfig=DEFAULT_PRESENCE;
|
|
35
|
+
try{presenceConfig=JSON.parse(await readFile(path.join(process.env.OFTONE_API_HOME||'.','presence.json'),'utf8'));}catch{}
|
|
36
|
+
const applyPresence=config=>{client.user.setPresence(discordPresence(config));};
|
|
37
|
+
process.on('message',message=>{if(message?.type==='presence')try{discordPresence(message.config);presenceConfig=message.config;if(client.isReady())applyPresence(presenceConfig);}catch{}});
|
|
38
|
+
client.once('ready', () => {applyPresence(presenceConfig);console.log(`Bot connecté : ${client.user.tag}`);});
|
|
33
39
|
client.on('interactionCreate', async interaction => {
|
|
34
40
|
if(interaction.isButton?.()) {
|
|
35
41
|
try{await executeButtonInteraction(interaction,client,{...variableOptions,onExecution: sample => { if(process.connected&&process.send)try{process.send({type:'usage',hour:Math.floor(Date.now()/3600000)*3600000,...sample},()=>{});}catch{} }});}catch(error){
|
package/version.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_VERSION = '0.5.
|
|
1
|
+
export const AGENT_VERSION = '0.5.16';
|
package/README.md
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
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.
|