/**
* cogserver.js — CogServer TCP client for Node.js
*
* Sends Scheme s-expressions to CogServer and reads results.
* Protocol: connect to TCP port, send "scm\n" to enter Scheme shell,
* send expressions, read until "guile> " prompt.
*
* All functions gracefully return null/false if CogServer is not running.
*/
const net = require('net')
const DEFAULT_PORT = 17001
const DEFAULT_HOST = 'localhost'
const PROMPT_RE = /guile> / // matches inside ANSI-colored output too
const OPENCOG_PROMPT_RE = /opencog> /
const ANSI_RE = /\x1b\[[0-9;]*m/g // strip ANSI color codes
const CONNECT_TIMEOUT = 2000
const EVAL_TIMEOUT = 10000
function stripAnsi (str) {
return str.replace(ANSI_RE, '')
}
/**
* Check if CogServer is listening on the given port.
* Returns true/false, never throws.
*/
async function isCogServerUp (port = DEFAULT_PORT, host = DEFAULT_HOST) {
return new Promise((resolve) => {
const sock = new net.Socket()
sock.setTimeout(CONNECT_TIMEOUT)
sock.once('connect', () => { sock.destroy(); resolve(true) })
sock.once('error', () => { sock.destroy(); resolve(false) })
sock.once('timeout', () => { sock.destroy(); resolve(false) })
sock.connect(port, host)
})
}
/**
* Send a Scheme expression to CogServer and return the result string.
* Returns null if CogServer is not reachable.
*
* @param {string} code - Scheme expression to evaluate
* @param {number} port - CogServer port (default 17001)
* @param {string} host - CogServer host (default localhost)
* @returns {Promise<string|null>} - Result string or null on failure
*/
async function evalScheme (code, port = DEFAULT_PORT, host = DEFAULT_HOST) {
return new Promise((resolve) => {
const sock = new net.Socket()
let buffer = ''
let phase = 'wait-opencog' // wait-opencog → wait-guile → wait-result
let settled = false
const finish = (result) => {
if (settled) return
settled = true
sock.destroy()
resolve(result)
}
sock.setTimeout(EVAL_TIMEOUT)
sock.once('error', () => finish(null))
sock.once('timeout', () => finish(null))
sock.on('data', (chunk) => {
buffer += chunk.toString()
const clean = stripAnsi(buffer)
if (phase === 'wait-opencog' && OPENCOG_PROMPT_RE.test(clean)) {
// Got the initial opencog> prompt — enter Scheme shell
phase = 'wait-guile'
buffer = ''
sock.write('scm\n')
return
}
if (phase === 'wait-guile' && PROMPT_RE.test(clean)) {
// Got the first guile> prompt — send our code
phase = 'wait-result'
buffer = ''
sock.write(code + '\n')
return
}
if (phase === 'wait-result' && PROMPT_RE.test(clean)) {
// Got the guile> prompt after our code — extract result
const cleanBuf = stripAnsi(buffer)
const result = cleanBuf.replace(/guile>\s*/g, '').trim()
finish(result)
}
})
sock.connect(port, host)
})
}
/**
* Send multiple Scheme expressions in one connection.
* More efficient than multiple evalScheme calls.
* Returns array of result strings (null entries on failure).
*/
async function evalSchemeBatch (expressions, port = DEFAULT_PORT, host = DEFAULT_HOST) {
if (!expressions.length) return []
// Wrap all expressions in a begin block
const combined = '(begin\n' + expressions.join('\n') + '\n)'
const result = await evalScheme(combined, port, host)
return result
}
/**
* Send Scheme code without waiting for a meaningful result.
* Useful for bulk atom creation where we don't need the return value.
*/
async function evalSchemeVoid (code, port = DEFAULT_PORT, host = DEFAULT_HOST) {
const result = await evalScheme(code, port, host)
return result !== null
}
module.exports = {
isCogServerUp,
evalScheme,
evalSchemeBatch,
evalSchemeVoid,
DEFAULT_PORT,
DEFAULT_HOST
}