UNPKG

shell-mirror

Version:

Access your Mac shell from any device securely. Perfect for mobile coding with Claude Code CLI, Gemini CLI, and any shell tool.

404 lines (338 loc) 9.01 kB
'use strict' var Progress = require('are-we-there-yet') var Gauge = require('gauge') var EE = require('events').EventEmitter var log = exports = module.exports = new EE() var util = require('util') var setBlocking = require('set-blocking') var consoleControl = require('console-control-strings') setBlocking(true) var stream = process.stderr Object.defineProperty(log, 'stream', { set: function (newStream) { stream = newStream if (this.gauge) { this.gauge.setWriteTo(stream, stream) } }, get: function () { return stream }, }) // by default, decide based on tty-ness. var colorEnabled log.useColor = function () { return colorEnabled != null ? colorEnabled : stream.isTTY } log.enableColor = function () { colorEnabled = true this.gauge.setTheme({hasColor: colorEnabled, hasUnicode: unicodeEnabled}) } log.disableColor = function () { colorEnabled = false this.gauge.setTheme({hasColor: colorEnabled, hasUnicode: unicodeEnabled}) } // default level log.level = 'info' log.gauge = new Gauge(stream, { enabled: false, // no progress bars unless asked theme: {hasColor: log.useColor()}, template: [ {type: 'progressbar', length: 20}, {type: 'activityIndicator', kerning: 1, length: 1}, {type: 'section', default: ''}, ':', {type: 'logline', kerning: 1, default: ''}, ], }) log.tracker = new Progress.TrackerGroup() // we track this separately as we may need to temporarily disable the // display of the status bar for our own loggy purposes. log.progressEnabled = log.gauge.isEnabled() var unicodeEnabled log.enableUnicode = function () { unicodeEnabled = true this.gauge.setTheme({hasColor: this.useColor(), hasUnicode: unicodeEnabled}) } log.disableUnicode = function () { unicodeEnabled = false this.gauge.setTheme({hasColor: this.useColor(), hasUnicode: unicodeEnabled}) } log.setGaugeThemeset = function (themes) { this.gauge.setThemeset(themes) } log.setGaugeTemplate = function (template) { this.gauge.setTemplate(template) } log.enableProgress = function () { if (this.progressEnabled) { return } this.progressEnabled = true this.tracker.on('change', this.showProgress) if (this._paused) { return } this.gauge.enable() } log.disableProgress = function () { if (!this.progressEnabled) { return } this.progressEnabled = false this.tracker.removeListener('change', this.showProgress) this.gauge.disable() } var trackerConstructors = ['newGroup', 'newItem', 'newStream'] var mixinLog = function (tracker) { // mixin the public methods from log into the tracker // (except: conflicts and one's we handle specially) Object.keys(log).forEach(function (P) { if (P[0] === '_') { return } if (trackerConstructors.filter(function (C) { return C === P }).length) { return } if (tracker[P]) { return } if (typeof log[P] !== 'function') { return } var func = log[P] tracker[P] = function () { return func.apply(log, arguments) } }) // if the new tracker is a group, make sure any subtrackers get // mixed in too if (tracker instanceof Progress.TrackerGroup) { trackerConstructors.forEach(function (C) { var func = tracker[C] tracker[C] = function () { return mixinLog(func.apply(tracker, arguments)) } }) } return tracker } // Add tracker constructors to the top level log object trackerConstructors.forEach(function (C) { log[C] = function () { return mixinLog(this.tracker[C].apply(this.tracker, arguments)) } }) log.clearProgress = function (cb) { if (!this.progressEnabled) { return cb && process.nextTick(cb) } this.gauge.hide(cb) } log.showProgress = function (name, completed) { if (!this.progressEnabled) { return } var values = {} if (name) { values.section = name } var last = log.record[log.record.length - 1] if (last) { values.subsection = last.prefix var disp = log.disp[last.level] || last.level var logline = this._format(disp, log.style[last.level]) if (last.prefix) { logline += ' ' + this._format(last.prefix, this.prefixStyle) } logline += ' ' + last.message.split(/\r?\n/)[0] values.logline = logline } values.completed = completed || this.tracker.completed() this.gauge.show(values) }.bind(log) // bind for use in tracker's on-change listener // temporarily stop emitting, but don't drop log.pause = function () { this._paused = true if (this.progressEnabled) { this.gauge.disable() } } log.resume = function () { if (!this._paused) { return } this._paused = false var b = this._buffer this._buffer = [] b.forEach(function (m) { this.emitLog(m) }, this) if (this.progressEnabled) { this.gauge.enable() } } log._buffer = [] var id = 0 log.record = [] log.maxRecordSize = 10000 log.log = function (lvl, prefix, message) { var l = this.levels[lvl] if (l === undefined) { return this.emit('error', new Error(util.format( 'Undefined log level: %j', lvl))) } var a = new Array(arguments.length - 2) var stack = null for (var i = 2; i < arguments.length; i++) { var arg = a[i - 2] = arguments[i] // resolve stack traces to a plain string. if (typeof arg === 'object' && arg instanceof Error && arg.stack) { Object.defineProperty(arg, 'stack', { value: stack = arg.stack + '', enumerable: true, writable: true, }) } } if (stack) { a.unshift(stack + '\n') } message = util.format.apply(util, a) var m = { id: id++, level: lvl, prefix: String(prefix || ''), message: message, messageRaw: a, } this.emit('log', m) this.emit('log.' + lvl, m) if (m.prefix) { this.emit(m.prefix, m) } this.record.push(m) var mrs = this.maxRecordSize var n = this.record.length - mrs if (n > mrs / 10) { var newSize = Math.floor(mrs * 0.9) this.record = this.record.slice(-1 * newSize) } this.emitLog(m) }.bind(log) log.emitLog = function (m) { if (this._paused) { this._buffer.push(m) return } if (this.progressEnabled) { this.gauge.pulse(m.prefix) } var l = this.levels[m.level] if (l === undefined) { return } if (l < this.levels[this.level]) { return } if (l > 0 && !isFinite(l)) { return } // If 'disp' is null or undefined, use the lvl as a default // Allows: '', 0 as valid disp var disp = log.disp[m.level] != null ? log.disp[m.level] : m.level this.clearProgress() m.message.split(/\r?\n/).forEach(function (line) { if (this.heading) { this.write(this.heading, this.headingStyle) this.write(' ') } this.write(disp, log.style[m.level]) var p = m.prefix || '' if (p) { this.write(' ') } this.write(p, this.prefixStyle) this.write(' ' + line + '\n') }, this) this.showProgress() } log._format = function (msg, style) { if (!stream) { return } var output = '' if (this.useColor()) { style = style || {} var settings = [] if (style.fg) { settings.push(style.fg) } if (style.bg) { settings.push('bg' + style.bg[0].toUpperCase() + style.bg.slice(1)) } if (style.bold) { settings.push('bold') } if (style.underline) { settings.push('underline') } if (style.inverse) { settings.push('inverse') } if (settings.length) { output += consoleControl.color(settings) } if (style.beep) { output += consoleControl.beep() } } output += msg if (this.useColor()) { output += consoleControl.color('reset') } return output } log.write = function (msg, style) { if (!stream) { return } stream.write(this._format(msg, style)) } log.addLevel = function (lvl, n, style, disp) { // If 'disp' is null or undefined, use the lvl as a default if (disp == null) { disp = lvl } this.levels[lvl] = n this.style[lvl] = style if (!this[lvl]) { this[lvl] = function () { var a = new Array(arguments.length + 1) a[0] = lvl for (var i = 0; i < arguments.length; i++) { a[i + 1] = arguments[i] } return this.log.apply(this, a) }.bind(this) } this.disp[lvl] = disp } log.prefixStyle = { fg: 'magenta' } log.headingStyle = { fg: 'white', bg: 'black' } log.style = {} log.levels = {} log.disp = {} log.addLevel('silly', -Infinity, { inverse: true }, 'sill') log.addLevel('verbose', 1000, { fg: 'blue', bg: 'black' }, 'verb') log.addLevel('info', 2000, { fg: 'green' }) log.addLevel('timing', 2500, { fg: 'green', bg: 'black' }) log.addLevel('http', 3000, { fg: 'green', bg: 'black' }) log.addLevel('notice', 3500, { fg: 'blue', bg: 'black' }) log.addLevel('warn', 4000, { fg: 'black', bg: 'yellow' }, 'WARN') log.addLevel('error', 5000, { fg: 'red', bg: 'black' }, 'ERR!') log.addLevel('silent', Infinity) // allow 'error' prefix log.on('error', function () {})