Skip to content

Commit

Permalink
feat: new shell adapter
Browse files Browse the repository at this point in the history
BREAKING CHANGE: new shell adapter no longer uses Cline. (#1687)
  • Loading branch information
joeyguerra authored Nov 5, 2023
1 parent 8bb1af2 commit c9f0d10
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 101 deletions.
9 changes: 0 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
"url": "https://github.com/hubotio/hubot.git"
},
"dependencies": {
"cline": "^0.8.2",
"coffeescript": "^2.7.0",
"connect-multiparty": "^2.2.0",
"express": "^4.18.2",
Expand Down
159 changes: 68 additions & 91 deletions src/adapters/shell.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
'use strict'

const fs = require('fs')
const readline = require('readline')
const Stream = require('stream')
const cline = require('cline')

const readline = require('node:readline')
const Adapter = require('../adapter')

const _require = require('../message')

const TextMessage = _require.TextMessage
const { TextMessage } = require('../message')

const historySize = process.env.HUBOT_SHELL_HISTSIZE != null ? parseInt(process.env.HUBOT_SHELL_HISTSIZE) : 1024

const historyPath = '.hubot_history'

const completer = line => {
const completions = '\\q exit \\? help \\c clear'.split(' ')
const hits = completions.filter((c) => c.startsWith(line))
// Show all completions if none found
return [hits.length ? hits : completions, line]
}
const showHelp = () => {
console.log('usage:')
console.log('\\q, exit - close shell and exit')
console.log('\\?, help - show this help')
console.log('\\c, clear - clear screen')
}

const bold = str => `\x1b[1m${str}\x1b[22m`

class Shell extends Adapter {
Expand All @@ -37,32 +44,45 @@ class Shell extends Adapter {
}

async run () {
this.buildCli()
try {
const { readlineInterface, history } = await this.#loadHistory()
this.cli.history(history)
this.cli.interact(`${this.robot.name ?? this.robot.alias}> `)
this.#rl = readlineInterface
this.emit('connected', this)
} catch (error) {
console.log(error)
if (!fs.existsSync(historyPath)) {
fs.writeFileSync(historyPath, '')
}
}

close () {
super.close()
// Getting an error message on GitHubt Actions: error: 'this[#rl].close is not a function'
if (this.#rl?.close) {
this.#rl.close()
const stats = fs.statSync(historyPath)
if (stats.size > historySize) {
fs.unlinkSync(historyPath)
}
this.cli.removeAllListeners()
this.cli.close()
}

buildCli () {
this.cli = cline()

this.cli.command('*', async input => {
this.#rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: `${this.robot.name ?? this.robot.alias}> `,
completer
})
this.#rl.on('line', async (line) => {
const input = line.trim()
switch (input) {
case '\\q':
case 'exit':
this.#rl.close()
break
case '\\?':
case 'help':
showHelp()
break
case '\\c':
case 'clear':
this.#rl.write(null, { ctrl: true, name: 'l' })
this.#rl.prompt()
break
}
const history = fs.readFileSync(historyPath, 'utf-8').split('\n').reverse()
this.#rl.history = history
this.#rl.on('line', line => {
const input = line.trim()
if (input.length === 0) return
fs.appendFile(historyPath, `${input}\n`, err => {
if (err) console.error(err)
})
})
let userId = process.env.HUBOT_SHELL_USER_ID || '1'
if (userId.match(/A\d+z/)) {
userId = parseInt(userId)
Expand All @@ -71,68 +91,25 @@ class Shell extends Adapter {
const userName = process.env.HUBOT_SHELL_USER_NAME || 'Shell'
const user = this.robot.brain.userForId(userId, { name: userName, room: 'Shell' })
await this.receive(new TextMessage(user, input, 'messageId'))
this.#rl.prompt()
}).on('close', () => {
process.exit(0)
})

this.cli.command('history', () => {
Array.from(this.cli.history()).map(item => console.log(item))
})

this.cli.on('history', item => {
if (item.length > 0 && item !== 'exit' && item !== 'history') {
fs.appendFile(historyPath, `${item}\n`, error => {
if (error) {
this.robot.emit('error', error)
}
})
}
})

this.cli.on('close', () => {
let history, i, item, len

history = this.cli.history()

if (history.length <= historySize) {
return
}

const startIndex = history.length - historySize
history = history.reverse().splice(startIndex, historySize)
const fileOpts = {
mode: 0x180
}

const outstream = fs.createWriteStream(historyPath, fileOpts)
for (i = 0, len = history.length; i < len; i++) {
item = history[i]
outstream.write(item + '\n')
}
outstream.end()
})
try {
this.#rl.prompt()
this.emit('connected', this)
} catch (error) {
console.log(error)
}
}

async #loadHistory () {
if (!fs.existsSync(historyPath)) {
return new Error('No history available')
close () {
super.close()
if (this.#rl?.close) {
this.#rl.close()
}
const instream = fs.createReadStream(historyPath)
const outstream = new Stream()
outstream.readable = true
outstream.writable = true
const history = []
const readlineInterface = readline.createInterface({ input: instream, output: outstream, terminal: false })
return new Promise((resolve, reject) => {
readlineInterface.on('line', line => {
line = line.trim()
if (line.length > 0) {
history.push(line)
}
})
readlineInterface.on('close', () => {
resolve({ readlineInterface, history })
})
readlineInterface.on('error', reject)
})
this.cli.removeAllListeners()
this.cli.close()
}
}

Expand Down

0 comments on commit c9f0d10

Please sign in to comment.