botql 1.1.2 → 1.2.2
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/LICENSE +30 -10
- package/Parser.js +5 -1
- package/RAG.js +1 -572
- package/README.md +44 -18
- package/Terminal.js +96 -0
- package/botql.browser.js +248 -526
- package/botql.js +4 -4
- package/createBot.js +88 -0
- package/package.json +3 -2
- package/Bootstrap.js +0 -74
package/botql.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*botql.js v1.
|
|
3
|
-
*Adilson C.
|
|
4
|
-
*
|
|
2
|
+
*botql.min.js v1.1.3 © 2026/09
|
|
3
|
+
*Adilson C. Rafael
|
|
4
|
+
*adilsonrafael847@gmail.com, for more info.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
"use strict";const{Parser:e}=require("./Parser.js"),{MemoryDatabase:t}=require("./Database.js"),{createDefaultFileSystem:s}=require("./FileSystem.js"),{KnowledgeCache:i}=require("./RAG.js"),WAITING_TEXTO_PADRAO="Pensando...",WAITING_SEGUNDOS_PADRAO=3;class BotQLInterpreter{constructor(e={}){this.db=e.db||new t,this.onReply=e.onReply||null,this.onForward=e.onForward||null,this.onSend=e.onSend||null,this.signalParser=e.signalParser||(e=>e),this.onThinking=e.onThinking||null,this.botName=null,this.platform=null,this.connections=[],this.handlers={START:[],MESSAGE:[],SIGNAL:[]},this.running=!1,this.responseConnections=new Set,this.onResponse=e.onResponse||null,this.defaultMessageConfig=null,this.nativeFuncs=new Map,this.registerFunction("NOW",()=>new Date().toISOString()),this.fileSystem=e.fileSystem||s();let n="undefined"!=typeof process&&process.cwd?process.cwd():"";this._basePathStack=[e.basePath||n],this._importedFiles=new Set,this._rootBasePath=e.basePath||n,this._keywordsFileCache=new Map,this._replyFileCache=new Map,this.envValues=new Map,this.knowledgeCache=new i(this.fileSystem)}static fromSource(t,s={}){let i=new e(t).parseProgram(),n=new BotQLInterpreter(s);return n.load(i),n}static fromFile(e,t={}){let i=t.fileSystem||s(),n=i.resolve("",e),r=i.readFile(n);return BotQLInterpreter.fromSource(r,{...t,fileSystem:i,basePath:i.dirname(n)})}registerFunction(e,t){this.nativeFuncs.set(e,t)}load(e,t=this._basePathStack[this._basePathStack.length-1]){let s=this._flattenImports(e.body,t);for(let i of s)switch(i.type){case"CreateBot":this.botName=i.name;break;case"Platform":this.platform=i.value;break;case"Connect":this.connections.push({service:i.service,kind:i.kind,credential:i.credential});break;case"ConnectResponse":if(!this.envValues.has(i.alias))throw Error(`runtime error: CONNECT RESPONSE failed, alias "${i.alias}" was not imported (use IMPORT {..., N} AS ${i.alias})`);this.responseConnections.add(i.alias);break;case"CreateTable":if(this.db.createTable(i.name,i.columns,i.preventDefault),i.defaultMessage){let n="Context"===i.name?"client":(i.columns.find(e=>"client"===e.name||"sender"===e.name)||{}).name;if(!n)throw Error(`runtime error: DEFAULT MESSAGE on table "${i.name}" requires a "client" or "sender" column`);this.defaultMessageConfig={table:i.name,senderColumn:n,...i.defaultMessage}}break;case"On":this.handlers[i.event].push(i);break;case"RunBot":this.running=!0;break;default:throw Error(`runtime error: unsupported top-level statement: ${i.type}`)}}_flattenImports(t,s){let i=[],n=[];for(let r of t){if("Import"!==r.type){n.push(r);continue}if(null!==r.index&&void 0!==r.index){let a=this.evalExpr(r.index,this.createContext()),l=this._loadIndexedFile(r.path,s),o=l.get(Number(a));if(void 0===o)throw Error(`runtime error: IMPORT failed, entry ${a} not found in "${r.path}"`);let h=r.alias||r.path.replace(/\.[^.]+$/,"");this.envValues.set(h,o);continue}let u=this.fileSystem.resolve(s,r.path);if(this._importedFiles.has(u))continue;if(!this.fileSystem.exists(u))throw Error(`runtime error: IMPORT failed, file not found: "${u}"`);this._importedFiles.add(u);let c=this.fileSystem.readFile(u),f=new e(c).parseProgram(),d=this._flattenImports(f.body,this.fileSystem.dirname(u));i.push(...d)}return[...i,...n]}createContext(e={}){return{vars:{client:e.client||null,message:e.message||null,lastMsg:null,SIGNAL:null},rawSignal:e.rawSignal||null,lastInsertId:null}}async start(){for(let e of this.handlers.START){let t=this.createContext();await this.run(e.body,t)}}async receiveMessage(e,t){let s=this.createContext({client:e,message:t}),i=await this.greetIfNew(e);if(i)return s;for(let n of this.handlers.MESSAGE)await this.run(n.body,s);return s}async greetIfNew(e){if(!this.defaultMessageConfig)return!1;let{table:t,senderColumn:s}=this.defaultMessageConfig,i=this.db.getRows(t).some(t=>t[s]===e);if(i)return!1;let n=this.createContext({client:e}),r=this.resolveDefaultMessageText(this.defaultMessageConfig,n);return n.vars.lastMsg=r,this.db.insert(t,[s],[e]),this.onReply&&await this.onReply({target:null,text:r,client:e}),!0}resolveDefaultMessageText(e,t){return e.file?this.resolveReplyFromFile(e.file,e.index,t):String(this.evalExpr(e.value,t))}async receiveSignal(e,t){let s=this.createContext({rawSignal:t});for(let i of this.handlers.SIGNAL)i.source===e&&await this.run(i.body,s);return s}async run(e,t){let s=!1;for(let i=0;i<e.length;i++){let n=e[i];if("When"===n.type){(0===i||"When"!==e[i-1].type)&&(s=!1);let r=n.conditions.some(e=>this.evalCondition(e,t));r&&(await this.run(n.body,t),s=!0);continue}if("Otherwise"===n.type){s||await this.run(n.body,t),s=!1;continue}await this.execStatement(n,t)}}evalCondition(e,t){if("string"!=typeof t.vars.message)return!1;let s=t.vars.message.toLowerCase();if("CONTAINS"===e.op)return s.includes(e.value.toLowerCase());if("CONTAINS_ANY"===e.op)return e.values.some(e=>s.includes(e.toLowerCase()));if("CONTAINS_KEYWORDS_FILE"===e.op){let i=this._loadKeywordsFile(e.path);return i.some(e=>s.includes(e.toLowerCase()))}throw Error(`runtime error: unsupported condition: ${e.op}`)}_loadKeywordsFile(e){if(this._keywordsFileCache.has(e))return this._keywordsFileCache.get(e);let t=this.fileSystem.resolve(this._rootBasePath,e);if(!this.fileSystem.exists(t))throw Error(`runtime error: KEYWORDS failed, file not found: "${t}"`);let s=this.fileSystem.readFile(t),i=s.split("\n").map(e=>e.trim()).filter(e=>e.length>0);return this._keywordsFileCache.set(e,i),i}resolveWaitingConfig(e,t){if(!e)return{text:WAITING_TEXTO_PADRAO,seconds:3};let s=WAITING_TEXTO_PADRAO;if(e.file){let i=this.fileSystem.resolve(this._rootBasePath,e.file);if(!this.fileSystem.exists(i))throw Error(`runtime error: WAITING failed, file not found: "${i}"`);s=this.fileSystem.readFile(i).trim()}let n=3;return e.seconds&&(n=Number(this.evalExpr(e.seconds,t))),{text:s,seconds:n}}resolveReplyFromFile(e,t,s){let i=this.evalExpr(t,s),n=this._loadIndexedFile(e,this._rootBasePath),r=n.get(Number(i));if(void 0===r)throw Error(`runtime error: REPLY failed, entry ${i} not found in "${e}"`);return r}_loadIndexedFile(e,t){if(this._replyFileCache.has(e))return this._replyFileCache.get(e);let s=this.fileSystem.resolve(t,e);if(!this.fileSystem.exists(s))throw Error(`runtime error: file not found: "${s}"`);let i=this.fileSystem.readFile(s),n=this._parseIndexedEntries(i,e);return this._replyFileCache.set(e,n),n}_parseIndexedEntries(e,t){let s=new Map,i=e.length,n=0;for(;n<i;){let r=n,a=r;for(;a<i&&(" "===e[a]||" "===e[a]);)a++;let l=/^\d+/.exec(e.slice(a));if(!l||"-"!==e[a+l[0].length]){let o=e.indexOf("\n",r);n=-1===o?i:o+1;continue}let h=Number(l[0]),u=a+l[0].length+1;if("{"===e[u]){let c=1,f=u+1,d=f;for(;d<i&&c>0;)"{"===e[d]?c++:"}"===e[d]&&c--,d++;if(0!==c)throw Error(`runtime error: unclosed "{" for entry ${h} in "${t}"`);let p=e.slice(f,d-1).trim();s.set(h,p),n=d;continue}let m=e.indexOf("\n",u),w=-1===m?i:m,g=e.slice(u,w).trim();s.set(h,g),n=-1===m?i:m+1}return s}async execStatement(e,t){switch(e.type){case"Reply":{let s=e.file?this.resolveReplyFromFile(e.file,e.index,t):String(await this.evalReplyValue(e.value,t));t.vars.lastMsg=s,this.onReply&&await this.onReply({target:e.target,text:s,client:t.vars.client});break}case"Think":{let i=this.resolveWaitingConfig(e.waiting,t),n=Date.now();this.onThinking&&await this.onThinking({client:t.vars.client,text:i.text});let r=this.fileSystem.resolve(this._rootBasePath,e.file),a=this.knowledgeCache.get(r),l=a.analyze(t.vars.message),o=Date.now()-n,h=1e3*i.seconds-o;if(h>0&&await new Promise(e=>setTimeout(e,h)),"RESPONDER"===l.decision){let u=l.texto;t.vars.lastMsg=u,this.onReply&&await this.onReply({target:null,text:u,client:t.vars.client})}else e.fallback&&await this.execStatement(e.fallback,t);break}case"ForwardTo":{let c=String(this.evalExpr(e.target,t));this.onForward&&await this.onForward({target:c,client:t.vars.client});break}case"ParseSignal":t.vars.SIGNAL=await this.signalParser(t.rawSignal);break;case"SendTo":this.onSend&&await this.onSend({target:e.target,signal:t.vars.SIGNAL});break;case"Insert":this.execInsert(e,t);break;case"Update":this.execUpdate(e,t);break;default:throw Error(`runtime error: unsupported statement inside event: ${e.type}`)}}execInsert(e,t){let s,i;"Context"!==e.table||e.columns&&0!==e.columns.length||e.values?(s=(e.columns||[]).map(e=>e.name),i=(e.values||[]).map(e=>this.evalExpr(e,t))):(s=["client","message","created_at"],i=[t.vars.client,t.vars.message,new Date().toISOString()]),t.lastInsertId=this.db.insert(e.table,s,i)}execUpdate(e,t){let s=this.evalExpr(e.set.value,t),i=null,n=null;e.where&&(i=e.where.left.name,n=this.evalExpr(e.where.right,t)),this.db.update(e.table,e.set.column,s,i,n)}async evalReplyValue(e,t){return"Call"===e.type&&"Response"===e.callee?this.execResponse(e,t):this.evalExpr(e,t)}async execResponse(e,t){if(!this.onResponse)throw Error("runtime error: Response() failed, no AI connected (missing onResponse handler)");let s;if(0===e.args.length){if(0===this.responseConnections.size)throw Error("runtime error: Response() failed, no CONNECT RESPONSE found");if(this.responseConnections.size>1)throw Error(`runtime error: Response() is ambiguous with multiple CONNECT RESPONSE (${[...this.responseConnections].join(", ")}); use Response(alias)`);s=[...this.responseConnections][0]}else{let i=e.args[0];if("Identifier"!==i.type)throw Error("runtime error: Response(alias) expects an alias name, not a string or expression");if(s=i.name,!this.responseConnections.has(s))throw Error(`runtime error: Response(${s}) failed, no CONNECT RESPONSE ${s} found`)}let n=this.envValues.get(s),r=await this.onResponse({alias:s,token:n,message:t.vars.message,client:t.vars.client});return r}evalExpr(e,t){switch(e.type){case"Literal":return e.value;case"Identifier":if("env"===e.name)return Object.fromEntries(this.envValues);if(Object.prototype.hasOwnProperty.call(t.vars,e.name))return t.vars[e.name];if(this.envValues.has(e.name))return this.envValues.get(e.name);throw Error(`runtime error: unknown identifier: "${e.name}"`);case"Member":{let s=this.evalExpr(e.object,t);if(null==s)return;return s[e.property]}case"Call":{if("LAST_INSERT_ID"===e.callee)return t.lastInsertId;let i=this.nativeFuncs.get(e.callee);if(!i)throw Error(`runtime error: unknown function: "${e.callee}"`);let n=e.args.map(e=>this.evalExpr(e,t));return i(...n)}case"Binary":{let r=this.evalExpr(e.left,t),a=this.evalExpr(e.right,t);if("+"===e.op){if("number"==typeof r&&"number"==typeof a)return r+a;return String(r)+String(a)}if("="===e.op)return r===a;throw Error(`runtime error: unsupported operator: "${e.op}"`)}default:throw Error(`runtime error: unsupported expression: ${e.type}`)}}}module.exports={BotQLInterpreter,MemoryDatabase:t};
|
|
7
|
+
"use strict";const{Parser:e}=require("./Parser.js"),{MemoryDatabase:t}=require("./Database.js"),{createDefaultFileSystem:s}=require("./FileSystem.js"),{KnowledgeCache:i}=require("./RAG.js"),WAITING_TEXTO_PADRAO=null,WAITING_SEGUNDOS_PADRAO=3;class BotQLInterpreter{constructor(e={}){this.db=e.db||new t,this.onReply=e.onReply||null,this.onForward=e.onForward||null,this.onSend=e.onSend||null,this.signalParser=e.signalParser||(e=>e),this.onThinking=e.onThinking||null,this.botName=null,this.platform=null,this.connections=[],this.handlers={START:[],MESSAGE:[],SIGNAL:[]},this.running=!1,this.responseConnections=new Set,this._clientesOcupados=new Set,this.onBusy=e.onBusy||null,this.onResponse=e.onResponse||null,this.defaultMessageConfig=null,this.nativeFuncs=new Map,this.registerFunction("NOW",()=>new Date().toISOString()),this.fileSystem=e.fileSystem||s();let n="undefined"!=typeof process&&process.cwd?process.cwd():"";this._basePathStack=[e.basePath||n],this._importedFiles=new Set,this._rootBasePath=e.basePath||n,this._keywordsFileCache=new Map,this._replyFileCache=new Map,this.envValues=new Map,this.knowledgeCache=new i(this.fileSystem)}static fromSource(t,s={}){let i=new e(t).parseProgram(),n=new BotQLInterpreter(s);return n.load(i),n}static fromFile(e,t={}){let i=t.fileSystem||s(),n=i.resolve("",e),r=i.readFile(n);return BotQLInterpreter.fromSource(r,{...t,fileSystem:i,basePath:i.dirname(n)})}registerFunction(e,t){this.nativeFuncs.set(e,t)}load(e,t=this._basePathStack[this._basePathStack.length-1]){let s=this._flattenImports(e.body,t);for(let i of s)switch(i.type){case"CreateBot":this.botName=i.name;break;case"Platform":this.platform=i.value;break;case"Connect":this.connections.push({service:i.service,kind:i.kind,credential:i.credential});break;case"ConnectResponse":if(!this.envValues.has(i.alias))throw Error(`runtime error: CONNECT RESPONSE failed, alias "${i.alias}" was not imported (use IMPORT {..., N} AS ${i.alias})`);this.responseConnections.add(i.alias);break;case"CreateTable":if(this.db.createTable(i.name,i.columns,i.preventDefault),i.defaultMessage){let n="Context"===i.name?"client":(i.columns.find(e=>"client"===e.name||"sender"===e.name)||{}).name;if(!n)throw Error(`runtime error: DEFAULT MESSAGE on table "${i.name}" requires a "client" or "sender" column`);this.defaultMessageConfig={table:i.name,senderColumn:n,...i.defaultMessage}}break;case"On":this.handlers[i.event].push(i);break;case"RunBot":this.running=!0;break;default:throw Error(`runtime error: unsupported top-level statement: ${i.type}`)}}_flattenImports(t,s){let i=[],n=[];for(let r of t){if("Import"!==r.type){n.push(r);continue}if(null!==r.index&&void 0!==r.index){let a=this.evalExpr(r.index,this.createContext()),l=this._loadIndexedFile(r.path,s),o=l.get(Number(a));if(void 0===o)throw Error(`runtime error: IMPORT failed, entry ${a} not found in "${r.path}"`);let h=r.alias||r.path.replace(/\.[^.]+$/,"");this.envValues.set(h,o);continue}let u=this.fileSystem.resolve(s,r.path);if(this._importedFiles.has(u))continue;if(!this.fileSystem.exists(u))throw Error(`runtime error: IMPORT failed, file not found: "${u}"`);this._importedFiles.add(u);let c=this.fileSystem.readFile(u),d=new e(c).parseProgram(),f=this._flattenImports(d.body,this.fileSystem.dirname(u));i.push(...f)}return[...i,...n]}createContext(e={}){return{vars:{client:e.client||null,message:e.message||null,lastMsg:null,SIGNAL:null},rawSignal:e.rawSignal||null,lastInsertId:null}}async start(){for(let e of this.handlers.START){let t=this.createContext();await this.run(e.body,t)}}async receiveMessage(e,t){if(this._clientesOcupados.has(e))return this.onBusy&&await this.onBusy({client:e,message:t}),null;this._clientesOcupados.add(e);try{let s=this.createContext({client:e,message:t}),i=await this.greetIfNew(e);if(i)return s;for(let n of this.handlers.MESSAGE)await this.run(n.body,s);return s}finally{this._clientesOcupados.delete(e)}}async greetIfNew(e){if(!this.defaultMessageConfig)return!1;let{table:t,senderColumn:s}=this.defaultMessageConfig,i=this.db.getRows(t).some(t=>t[s]===e);if(i)return!1;let n=this.createContext({client:e}),r=this.resolveDefaultMessageText(this.defaultMessageConfig,n);return n.vars.lastMsg=r,this.db.insert(t,[s],[e]),this.onReply&&await this.onReply({target:null,text:r,client:e}),!0}resolveDefaultMessageText(e,t){return e.file?this.resolveReplyFromFile(e.file,e.index,t):String(this.evalExpr(e.value,t))}async receiveSignal(e,t){let s=this.createContext({rawSignal:t});for(let i of this.handlers.SIGNAL)i.source===e&&await this.run(i.body,s);return s}async run(e,t){let s=!1;for(let i=0;i<e.length;i++){let n=e[i];if("When"===n.type){(0===i||"When"!==e[i-1].type)&&(s=!1);let r=n.conditions.some(e=>this.evalCondition(e,t));r&&(await this.run(n.body,t),s=!0);continue}if("Otherwise"===n.type){s||await this.run(n.body,t),s=!1;continue}await this.execStatement(n,t)}}evalCondition(e,t){if("string"!=typeof t.vars.message)return!1;let s=t.vars.message.toLowerCase();if("CONTAINS"===e.op)return s.includes(e.value.toLowerCase());if("CONTAINS_ANY"===e.op)return e.values.some(e=>s.includes(e.toLowerCase()));if("CONTAINS_KEYWORDS_FILE"===e.op){let i=this._loadKeywordsFile(e.path);return i.some(e=>s.includes(e.toLowerCase()))}throw Error(`runtime error: unsupported condition: ${e.op}`)}_loadKeywordsFile(e){if(this._keywordsFileCache.has(e))return this._keywordsFileCache.get(e);let t=this.fileSystem.resolve(this._rootBasePath,e);if(!this.fileSystem.exists(t))throw Error(`runtime error: KEYWORDS failed, file not found: "${t}"`);let s=this.fileSystem.readFile(t),i=s.split("\n").map(e=>e.trim()).filter(e=>e.length>0);return this._keywordsFileCache.set(e,i),i}resolveWaitingConfig(e,t){if(!e)return{text:null,seconds:3};let s=null;if(null!==e.text&&void 0!==e.text)s=e.text;else if(e.file){let i=this.fileSystem.resolve(this._rootBasePath,e.file);if(!this.fileSystem.exists(i))throw Error(`runtime error: WAITING failed, file not found: "${i}"`);s=this.fileSystem.readFile(i).trim()}let n=3;return e.seconds&&(n=Number(this.evalExpr(e.seconds,t))),{text:s,seconds:n}}resolveReplyFromFile(e,t,s){let i=this.evalExpr(t,s),n=this._loadIndexedFile(e,this._rootBasePath),r=n.get(Number(i));if(void 0===r)throw Error(`runtime error: REPLY failed, entry ${i} not found in "${e}"`);return r}_loadIndexedFile(e,t){if(this._replyFileCache.has(e))return this._replyFileCache.get(e);let s=this.fileSystem.resolve(t,e);if(!this.fileSystem.exists(s))throw Error(`runtime error: file not found: "${s}"`);let i=this.fileSystem.readFile(s),n=this._parseIndexedEntries(i,e);return this._replyFileCache.set(e,n),n}_parseIndexedEntries(e,t){let s=new Map,i=e.length,n=0;for(;n<i;){let r=n,a=r;for(;a<i&&(" "===e[a]||" "===e[a]);)a++;let l=/^\d+/.exec(e.slice(a));if(!l||"-"!==e[a+l[0].length]){let o=e.indexOf("\n",r);n=-1===o?i:o+1;continue}let h=Number(l[0]),u=a+l[0].length+1;if("{"===e[u]){let c=1,d=u+1,f=d;for(;f<i&&c>0;)"{"===e[f]?c++:"}"===e[f]&&c--,f++;if(0!==c)throw Error(`runtime error: unclosed "{" for entry ${h} in "${t}"`);let p=e.slice(d,f-1).trim();s.set(h,p),n=f;continue}let m=e.indexOf("\n",u),w=-1===m?i:m,g=e.slice(u,w).trim();s.set(h,g),n=-1===m?i:m+1}return s}async execStatement(e,t){switch(e.type){case"Reply":{let s=e.file?this.resolveReplyFromFile(e.file,e.index,t):String(await this.evalReplyValue(e.value,t));t.vars.lastMsg=s,this.onReply&&await this.onReply({target:e.target,text:s,client:t.vars.client});break}case"Waiting":{let i=this.resolveWaitingConfig(e,t);this.onThinking&&await this.onThinking({client:t.vars.client,text:i.text}),await new Promise(e=>setTimeout(e,1e3*i.seconds));break}case"Think":{let n=this.resolveWaitingConfig(e.waiting,t),r=Date.now();this.onThinking&&await this.onThinking({client:t.vars.client,text:n.text});let a=this.fileSystem.resolve(this._rootBasePath,e.file),l=this.knowledgeCache.get(a),o=l.analyze(t.vars.message),h=Date.now()-r,u=1e3*n.seconds-h;if(u>0&&await new Promise(e=>setTimeout(e,u)),"RESPONDER"===o.decision){let c=o.texto;t.vars.lastMsg=c,this.onReply&&await this.onReply({target:null,text:c,client:t.vars.client})}else e.fallback&&await this.execStatement(e.fallback,t);break}case"ForwardTo":{let d=String(this.evalExpr(e.target,t));this.onForward&&await this.onForward({target:d,client:t.vars.client});break}case"ParseSignal":t.vars.SIGNAL=await this.signalParser(t.rawSignal);break;case"SendTo":this.onSend&&await this.onSend({target:e.target,signal:t.vars.SIGNAL});break;case"Insert":this.execInsert(e,t);break;case"Update":this.execUpdate(e,t);break;default:throw Error(`runtime error: unsupported statement inside event: ${e.type}`)}}execInsert(e,t){let s,i;"Context"!==e.table||e.columns&&0!==e.columns.length||e.values?(s=(e.columns||[]).map(e=>e.name),i=(e.values||[]).map(e=>this.evalExpr(e,t))):(s=["client","message","created_at"],i=[t.vars.client,t.vars.message,new Date().toISOString()]),t.lastInsertId=this.db.insert(e.table,s,i)}execUpdate(e,t){let s=this.evalExpr(e.set.value,t),i=null,n=null;e.where&&(i=e.where.left.name,n=this.evalExpr(e.where.right,t)),this.db.update(e.table,e.set.column,s,i,n)}async evalReplyValue(e,t){return"Call"===e.type&&"Response"===e.callee?this.execResponse(e,t):this.evalExpr(e,t)}async execResponse(e,t){if(!this.onResponse)throw Error("runtime error: Response() failed, no AI connected (missing onResponse handler)");let s;if(0===e.args.length){if(0===this.responseConnections.size)throw Error("runtime error: Response() failed, no CONNECT RESPONSE found");if(this.responseConnections.size>1)throw Error(`runtime error: Response() is ambiguous with multiple CONNECT RESPONSE (${[...this.responseConnections].join(", ")}); use Response(alias)`);s=[...this.responseConnections][0]}else{let i=e.args[0];if("Identifier"!==i.type)throw Error("runtime error: Response(alias) expects an alias name, not a string or expression");if(s=i.name,!this.responseConnections.has(s))throw Error(`runtime error: Response(${s}) failed, no CONNECT RESPONSE ${s} found`)}let n=this.envValues.get(s),r=await this.onResponse({alias:s,token:n,message:t.vars.message,client:t.vars.client});return r}evalExpr(e,t){switch(e.type){case"Literal":return e.value;case"Identifier":if("env"===e.name)return Object.fromEntries(this.envValues);if(Object.prototype.hasOwnProperty.call(t.vars,e.name))return t.vars[e.name];if(this.envValues.has(e.name))return this.envValues.get(e.name);throw Error(`runtime error: unknown identifier: "${e.name}"`);case"Member":{let s=this.evalExpr(e.object,t);if(null==s)return;return s[e.property]}case"Call":{if("LAST_INSERT_ID"===e.callee)return t.lastInsertId;let i=this.nativeFuncs.get(e.callee);if(!i)throw Error(`runtime error: unknown function: "${e.callee}"`);let n=e.args.map(e=>this.evalExpr(e,t));return i(...n)}case"Binary":{let r=this.evalExpr(e.left,t),a=this.evalExpr(e.right,t);if("+"===e.op){if("number"==typeof r&&"number"==typeof a)return r+a;return String(r)+String(a)}if("="===e.op)return r===a;throw Error(`runtime error: unsupported operator: "${e.op}"`)}default:throw Error(`runtime error: unsupported expression: ${e.type}`)}}}module.exports={BotQLInterpreter,MemoryDatabase:t};
|
package/createBot.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* createBot.js — ponto de entrada do BotQL.
|
|
5
|
+
*
|
|
6
|
+
* Lê um ficheiro .sql (com suporte a IMPORT), cria o interpreter, liga os
|
|
7
|
+
* connectors reais das plataformas usadas via CONNECT, corre ON START e
|
|
8
|
+
* fica pronto a receber mensagens.
|
|
9
|
+
*
|
|
10
|
+
* Uso:
|
|
11
|
+
* node createBot.js caminho/para/bot.sql
|
|
12
|
+
*
|
|
13
|
+
* Uso programático:
|
|
14
|
+
* const { createBot } = require('./createBot.js');
|
|
15
|
+
* const bot = await createBot('bot.sql');
|
|
16
|
+
* await bot.receiveMessage('+244900000000', 'quero comprar');
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { BotQLInterpreter } = require('./botql.js');
|
|
21
|
+
const { ConnectorRegistry, attachConnectors } = require('./Connectors.js');
|
|
22
|
+
const { SQLiteDatabase } = require('./Database.js');
|
|
23
|
+
const { attachTerminalUI } = require('./Terminal.js');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} sqlFilePath Caminho do ficheiro .sql principal do bot.
|
|
27
|
+
* @param {object} options
|
|
28
|
+
* @param {string} [options.dbPath] Caminho do ficheiro .sqlite. Sem isto,
|
|
29
|
+
* usa MemoryDatabase (sem persistência), que é o omisso do interpreter.
|
|
30
|
+
* @param {string} [options.connectorsPath] Caminho alternativo para o JSON
|
|
31
|
+
* de connectors. Por omissão usa o Connectors.json ao lado deste ficheiro.
|
|
32
|
+
* @param {boolean} [options.autoStart] Corre ON START automaticamente (omissão: true).
|
|
33
|
+
* @param {boolean} [options.terminalUI] Liga o spinner de terminal (Terminal.js)
|
|
34
|
+
* ao onThinking/onReply/onForward/onSend que ainda não tiverem sido definidos
|
|
35
|
+
* pelos connectors reais (omissão: true). Só tem efeito em Node — em
|
|
36
|
+
* browser/WebView não faz nada.
|
|
37
|
+
*/
|
|
38
|
+
async function createBot(sqlFilePath, options = {}) {
|
|
39
|
+
const interpreterOptions = {};
|
|
40
|
+
if (options.dbPath) {
|
|
41
|
+
interpreterOptions.db = new SQLiteDatabase(options.dbPath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const bot = BotQLInterpreter.fromFile(sqlFilePath, interpreterOptions);
|
|
45
|
+
|
|
46
|
+
const registry = new ConnectorRegistry(
|
|
47
|
+
options.connectorsPath || path.join(__dirname, 'Connectors.json')
|
|
48
|
+
);
|
|
49
|
+
attachConnectors(bot, registry);
|
|
50
|
+
|
|
51
|
+
// Depois de attachConnectors, nunca antes: assim o spinner só preenche
|
|
52
|
+
// onReply/onForward/onSend quando NÃO há connector real ligado (ex: bot
|
|
53
|
+
// sem CONNECT nenhum, só a testar localmente) — com connector real, essas
|
|
54
|
+
// respostas já vão para a plataforma verdadeira, e o Terminal.js respeita
|
|
55
|
+
// isso e não lhes toca (ver Terminal.js, só substitui o que estiver null).
|
|
56
|
+
if (options.terminalUI !== false) {
|
|
57
|
+
attachTerminalUI(bot);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (options.autoStart !== false) {
|
|
61
|
+
await bot.start();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return bot;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Execução direta: node createBot.js caminho/bot.sql
|
|
68
|
+
if (require.main === module) {
|
|
69
|
+
const sqlFilePath = process.argv[2];
|
|
70
|
+
if (!sqlFilePath) {
|
|
71
|
+
console.error('Uso: node createBot.js caminho/para/bot.sql');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
createBot(sqlFilePath)
|
|
76
|
+
.then((bot) => {
|
|
77
|
+
console.log(`BotQL: bot "${bot.botName || '(sem nome)'}" iniciado.`);
|
|
78
|
+
if (!bot.running) {
|
|
79
|
+
console.log('Aviso: o ficheiro não tem "RUN BOT" — o bot foi carregado mas não marcado como em execução.');
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
.catch((err) => {
|
|
83
|
+
console.error(err.message);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { createBot };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "botql",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "BotQL is a simple, SQL-inspired rules language for creating bots without writing traditional code.",
|
|
5
5
|
"main": "botql.js",
|
|
6
6
|
"browser": "botql.browser.js",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"botql.js",
|
|
9
9
|
"botql.browser.js",
|
|
10
10
|
"botql-mode.js",
|
|
11
|
-
"
|
|
11
|
+
"createBot.js",
|
|
12
12
|
"Parser.js",
|
|
13
13
|
"Database.js",
|
|
14
14
|
"RAG.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"Connectors.js",
|
|
17
17
|
"Connectors.json",
|
|
18
18
|
"connectors/",
|
|
19
|
+
"Terminal.js",
|
|
19
20
|
"README.md",
|
|
20
21
|
"LICENSE"
|
|
21
22
|
],
|
package/Bootstrap.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Bootstrap.js — ponto de entrada do BotQL.
|
|
5
|
-
*
|
|
6
|
-
* Lê um ficheiro .sql (com suporte a IMPORT), cria o interpreter, liga os
|
|
7
|
-
* connectors reais das plataformas usadas via CONNECT, corre ON START e
|
|
8
|
-
* fica pronto a receber mensagens.
|
|
9
|
-
*
|
|
10
|
-
* Uso:
|
|
11
|
-
* node Bootstrap.js caminho/para/bot.sql
|
|
12
|
-
*
|
|
13
|
-
* Uso programático:
|
|
14
|
-
* const { bootstrap } = require('./Bootstrap.js');
|
|
15
|
-
* const bot = await bootstrap('bot.sql');
|
|
16
|
-
* await bot.receiveMessage('+244900000000', 'quero comprar');
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const path = require('path');
|
|
20
|
-
const { BotQLInterpreter } = require('./botql.js');
|
|
21
|
-
const { ConnectorRegistry, attachConnectors } = require('./Connectors.js');
|
|
22
|
-
const { SQLiteDatabase } = require('./Database.js');
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @param {string} sqlFilePath Caminho do ficheiro .sql principal do bot.
|
|
26
|
-
* @param {object} options
|
|
27
|
-
* @param {string} [options.dbPath] Caminho do ficheiro .sqlite. Sem isto,
|
|
28
|
-
* usa MemoryDatabase (sem persistência), que é o omisso do interpreter.
|
|
29
|
-
* @param {string} [options.connectorsPath] Caminho alternativo para o JSON
|
|
30
|
-
* de connectors. Por omissão usa o Connectors.json ao lado deste ficheiro.
|
|
31
|
-
* @param {boolean} [options.autoStart] Corre ON START automaticamente (omissão: true).
|
|
32
|
-
*/
|
|
33
|
-
async function bootstrap(sqlFilePath, options = {}) {
|
|
34
|
-
const interpreterOptions = {};
|
|
35
|
-
if (options.dbPath) {
|
|
36
|
-
interpreterOptions.db = new SQLiteDatabase(options.dbPath);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const bot = BotQLInterpreter.fromFile(sqlFilePath, interpreterOptions);
|
|
40
|
-
|
|
41
|
-
const registry = new ConnectorRegistry(
|
|
42
|
-
options.connectorsPath || path.join(__dirname, 'Connectors.json')
|
|
43
|
-
);
|
|
44
|
-
attachConnectors(bot, registry);
|
|
45
|
-
|
|
46
|
-
if (options.autoStart !== false) {
|
|
47
|
-
await bot.start();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return bot;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Execução direta: node Bootstrap.js caminho/bot.sql
|
|
54
|
-
if (require.main === module) {
|
|
55
|
-
const sqlFilePath = process.argv[2];
|
|
56
|
-
if (!sqlFilePath) {
|
|
57
|
-
console.error('Uso: node Bootstrap.js caminho/para/bot.sql');
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
bootstrap(sqlFilePath)
|
|
62
|
-
.then((bot) => {
|
|
63
|
-
console.log(`BotQL: bot "${bot.botName || '(sem nome)'}" iniciado.`);
|
|
64
|
-
if (!bot.running) {
|
|
65
|
-
console.log('Aviso: o ficheiro não tem "RUN BOT" — o bot foi carregado mas não marcado como em execução.');
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
.catch((err) => {
|
|
69
|
-
console.error(err.message);
|
|
70
|
-
process.exit(1);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
module.exports = { bootstrap };
|