- Add full Telegram bot functionality with Z.AI API integration
- Implement 4 tools: Bash, FileEdit, WebSearch, Git
- Add 3 agents: Code Reviewer, Architect, DevOps Engineer
- Add 6 skills for common coding tasks
- Add systemd service file for 24/7 operation
- Add nginx configuration for HTTPS webhook
- Add comprehensive documentation
- Implement WebSocket server for real-time updates
- Add logging system with Winston
- Add environment validation
🤖 zCode CLI X - Agentic coder with Z.AI + Telegram integration
406 lines
16 KiB
JavaScript
406 lines
16 KiB
JavaScript
"use strict";
|
|
// Copyright 2021-2024 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.env = exports.DebugLogBackendBase = exports.placeholder = exports.AdhocDebugLogger = exports.LogSeverity = void 0;
|
|
exports.getNodeBackend = getNodeBackend;
|
|
exports.getDebugBackend = getDebugBackend;
|
|
exports.getStructuredBackend = getStructuredBackend;
|
|
exports.setBackend = setBackend;
|
|
exports.log = log;
|
|
const node_events_1 = require("node:events");
|
|
const process = __importStar(require("node:process"));
|
|
const util = __importStar(require("node:util"));
|
|
const colours_1 = require("./colours");
|
|
// Some functions (as noted) are based on the Node standard library, from
|
|
// the following file:
|
|
//
|
|
// https://github.com/nodejs/node/blob/main/lib/internal/util/debuglog.js
|
|
/**
|
|
* This module defines an ad-hoc debug logger for Google Cloud Platform
|
|
* client libraries in Node. An ad-hoc debug logger is a tool which lets
|
|
* users use an external, unified interface (in this case, environment
|
|
* variables) to determine what logging they want to see at runtime. This
|
|
* isn't necessarily fed into the console, but is meant to be under the
|
|
* control of the user. The kind of logging that will be produced by this
|
|
* is more like "call retry happened", not "event you'd want to record
|
|
* in Cloud Logger".
|
|
*
|
|
* More for Googlers implementing libraries with it:
|
|
* go/cloud-client-logging-design
|
|
*/
|
|
/**
|
|
* Possible log levels. These are a subset of Cloud Observability levels.
|
|
* https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry#LogSeverity
|
|
*/
|
|
var LogSeverity;
|
|
(function (LogSeverity) {
|
|
LogSeverity["DEFAULT"] = "DEFAULT";
|
|
LogSeverity["DEBUG"] = "DEBUG";
|
|
LogSeverity["INFO"] = "INFO";
|
|
LogSeverity["WARNING"] = "WARNING";
|
|
LogSeverity["ERROR"] = "ERROR";
|
|
})(LogSeverity || (exports.LogSeverity = LogSeverity = {}));
|
|
/**
|
|
* Our logger instance. This actually contains the meat of dealing
|
|
* with log lines, including EventEmitter. This contains the function
|
|
* that will be passed back to users of the package.
|
|
*/
|
|
class AdhocDebugLogger extends node_events_1.EventEmitter {
|
|
/**
|
|
* @param upstream The backend will pass a function that will be
|
|
* called whenever our logger function is invoked.
|
|
*/
|
|
constructor(namespace, upstream) {
|
|
super();
|
|
this.namespace = namespace;
|
|
this.upstream = upstream;
|
|
this.func = Object.assign(this.invoke.bind(this), {
|
|
// Also add an instance pointer back to us.
|
|
instance: this,
|
|
// And pull over the EventEmitter functionality.
|
|
on: (event, listener) => this.on(event, listener),
|
|
});
|
|
// Convenience methods for log levels.
|
|
this.func.debug = (...args) => this.invokeSeverity(LogSeverity.DEBUG, ...args);
|
|
this.func.info = (...args) => this.invokeSeverity(LogSeverity.INFO, ...args);
|
|
this.func.warn = (...args) => this.invokeSeverity(LogSeverity.WARNING, ...args);
|
|
this.func.error = (...args) => this.invokeSeverity(LogSeverity.ERROR, ...args);
|
|
this.func.sublog = (namespace) => log(namespace, this.func);
|
|
}
|
|
invoke(fields, ...args) {
|
|
// Push out any upstream logger first.
|
|
if (this.upstream) {
|
|
this.upstream(fields, ...args);
|
|
}
|
|
// Emit sink events.
|
|
this.emit('log', fields, args);
|
|
}
|
|
invokeSeverity(severity, ...args) {
|
|
this.invoke({ severity }, ...args);
|
|
}
|
|
}
|
|
exports.AdhocDebugLogger = AdhocDebugLogger;
|
|
/**
|
|
* This can be used in place of a real logger while waiting for Promises or disabling logging.
|
|
*/
|
|
exports.placeholder = new AdhocDebugLogger('', () => { }).func;
|
|
/**
|
|
* The base class for debug logging backends. It's possible to use this, but the
|
|
* same non-guarantees above still apply (unstable interface, etc).
|
|
*
|
|
* @private
|
|
* @internal
|
|
*/
|
|
class DebugLogBackendBase {
|
|
constructor() {
|
|
var _a;
|
|
this.cached = new Map();
|
|
this.filters = [];
|
|
this.filtersSet = false;
|
|
// Look for the Node config variable for what systems to enable. We'll store
|
|
// these for the log method below, which will call setFilters() once.
|
|
let nodeFlag = (_a = process.env[exports.env.nodeEnables]) !== null && _a !== void 0 ? _a : '*';
|
|
if (nodeFlag === 'all') {
|
|
nodeFlag = '*';
|
|
}
|
|
this.filters = nodeFlag.split(',');
|
|
}
|
|
log(namespace, fields, ...args) {
|
|
try {
|
|
if (!this.filtersSet) {
|
|
this.setFilters();
|
|
this.filtersSet = true;
|
|
}
|
|
let logger = this.cached.get(namespace);
|
|
if (!logger) {
|
|
logger = this.makeLogger(namespace);
|
|
this.cached.set(namespace, logger);
|
|
}
|
|
logger(fields, ...args);
|
|
}
|
|
catch (e) {
|
|
// Silently ignore all errors; we don't want them to interfere with
|
|
// the user's running app.
|
|
// e;
|
|
console.error(e);
|
|
}
|
|
}
|
|
}
|
|
exports.DebugLogBackendBase = DebugLogBackendBase;
|
|
// The basic backend. This one definitely works, but it's less feature-filled.
|
|
//
|
|
// Rather than using util.debuglog, this implements the same basic logic directly.
|
|
// The reason for this decision is that debuglog checks the value of the
|
|
// NODE_DEBUG environment variable before any user code runs; we therefore
|
|
// can't pipe our own enables into it (and util.debuglog will never print unless
|
|
// the user duplicates it into NODE_DEBUG, which isn't reasonable).
|
|
//
|
|
class NodeBackend extends DebugLogBackendBase {
|
|
constructor() {
|
|
super(...arguments);
|
|
// Default to allowing all systems, since we gate earlier based on whether the
|
|
// variable is empty.
|
|
this.enabledRegexp = /.*/g;
|
|
}
|
|
isEnabled(namespace) {
|
|
return this.enabledRegexp.test(namespace);
|
|
}
|
|
makeLogger(namespace) {
|
|
if (!this.enabledRegexp.test(namespace)) {
|
|
return () => { };
|
|
}
|
|
return (fields, ...args) => {
|
|
var _a;
|
|
// TODO: `fields` needs to be turned into a string here, one way or another.
|
|
const nscolour = `${colours_1.Colours.green}${namespace}${colours_1.Colours.reset}`;
|
|
const pid = `${colours_1.Colours.yellow}${process.pid}${colours_1.Colours.reset}`;
|
|
let level;
|
|
switch (fields.severity) {
|
|
case LogSeverity.ERROR:
|
|
level = `${colours_1.Colours.red}${fields.severity}${colours_1.Colours.reset}`;
|
|
break;
|
|
case LogSeverity.INFO:
|
|
level = `${colours_1.Colours.magenta}${fields.severity}${colours_1.Colours.reset}`;
|
|
break;
|
|
case LogSeverity.WARNING:
|
|
level = `${colours_1.Colours.yellow}${fields.severity}${colours_1.Colours.reset}`;
|
|
break;
|
|
default:
|
|
level = (_a = fields.severity) !== null && _a !== void 0 ? _a : LogSeverity.DEFAULT;
|
|
break;
|
|
}
|
|
const msg = util.formatWithOptions({ colors: colours_1.Colours.enabled }, ...args);
|
|
const filteredFields = Object.assign({}, fields);
|
|
delete filteredFields.severity;
|
|
const fieldsJson = Object.getOwnPropertyNames(filteredFields).length
|
|
? JSON.stringify(filteredFields)
|
|
: '';
|
|
const fieldsColour = fieldsJson
|
|
? `${colours_1.Colours.grey}${fieldsJson}${colours_1.Colours.reset}`
|
|
: '';
|
|
console.error('%s [%s|%s] %s%s', pid, nscolour, level, msg, fieldsJson ? ` ${fieldsColour}` : '');
|
|
};
|
|
}
|
|
// Regexp patterns below are from here:
|
|
// https://github.com/nodejs/node/blob/c0aebed4b3395bd65d54b18d1fd00f071002ac20/lib/internal/util/debuglog.js#L36
|
|
setFilters() {
|
|
const totalFilters = this.filters.join(',');
|
|
const regexp = totalFilters
|
|
.replace(/[|\\{}()[\]^$+?.]/g, '\\$&')
|
|
.replace(/\*/g, '.*')
|
|
.replace(/,/g, '$|^');
|
|
this.enabledRegexp = new RegExp(`^${regexp}$`, 'i');
|
|
}
|
|
}
|
|
/**
|
|
* @returns A backend based on Node util.debuglog; this is the default.
|
|
*/
|
|
function getNodeBackend() {
|
|
return new NodeBackend();
|
|
}
|
|
class DebugBackend extends DebugLogBackendBase {
|
|
constructor(pkg) {
|
|
super();
|
|
this.debugPkg = pkg;
|
|
}
|
|
makeLogger(namespace) {
|
|
const debugLogger = this.debugPkg(namespace);
|
|
return (fields, ...args) => {
|
|
// TODO: `fields` needs to be turned into a string here.
|
|
debugLogger(args[0], ...args.slice(1));
|
|
};
|
|
}
|
|
setFilters() {
|
|
var _a;
|
|
const existingFilters = (_a = process.env['NODE_DEBUG']) !== null && _a !== void 0 ? _a : '';
|
|
process.env['NODE_DEBUG'] = `${existingFilters}${existingFilters ? ',' : ''}${this.filters.join(',')}`;
|
|
}
|
|
}
|
|
/**
|
|
* Creates a "debug" package backend. The user must call require('debug') and pass
|
|
* the resulting object to this function.
|
|
*
|
|
* ```
|
|
* setBackend(getDebugBackend(require('debug')))
|
|
* ```
|
|
*
|
|
* https://www.npmjs.com/package/debug
|
|
*
|
|
* Note: Google does not explicitly endorse or recommend this package; it's just
|
|
* being provided as an option.
|
|
*
|
|
* @returns A backend based on the npm "debug" package.
|
|
*/
|
|
function getDebugBackend(debugPkg) {
|
|
return new DebugBackend(debugPkg);
|
|
}
|
|
/**
|
|
* This pretty much works like the Node logger, but it outputs structured
|
|
* logging JSON matching Google Cloud's ingestion specs. Rather than handling
|
|
* its own output, it wraps another backend. The passed backend must be a subclass
|
|
* of `DebugLogBackendBase` (any of the backends exposed by this package will work).
|
|
*/
|
|
class StructuredBackend extends DebugLogBackendBase {
|
|
constructor(upstream) {
|
|
var _a;
|
|
super();
|
|
this.upstream = (_a = upstream) !== null && _a !== void 0 ? _a : new NodeBackend();
|
|
}
|
|
makeLogger(namespace) {
|
|
const debugLogger = this.upstream.makeLogger(namespace);
|
|
return (fields, ...args) => {
|
|
var _a;
|
|
const severity = (_a = fields.severity) !== null && _a !== void 0 ? _a : LogSeverity.INFO;
|
|
const json = Object.assign({
|
|
severity,
|
|
message: util.format(...args),
|
|
}, fields);
|
|
const jsonString = JSON.stringify(json);
|
|
debugLogger(fields, jsonString);
|
|
};
|
|
}
|
|
setFilters() {
|
|
this.upstream.setFilters();
|
|
}
|
|
}
|
|
/**
|
|
* Creates a "structured logging" backend. This pretty much works like the
|
|
* Node logger, but it outputs structured logging JSON matching Google
|
|
* Cloud's ingestion specs instead of plain text.
|
|
*
|
|
* ```
|
|
* setBackend(getStructuredBackend())
|
|
* ```
|
|
*
|
|
* @param upstream If you want to use something besides the Node backend to
|
|
* write the actual log lines into, pass that here.
|
|
* @returns A backend based on Google Cloud structured logging.
|
|
*/
|
|
function getStructuredBackend(upstream) {
|
|
return new StructuredBackend(upstream);
|
|
}
|
|
/**
|
|
* The environment variables that we standardized on, for all ad-hoc logging.
|
|
*/
|
|
exports.env = {
|
|
/**
|
|
* Filter wildcards specific to the Node syntax, and similar to the built-in
|
|
* utils.debuglog() environment variable. If missing, disables logging.
|
|
*/
|
|
nodeEnables: 'GOOGLE_SDK_NODE_LOGGING',
|
|
};
|
|
// Keep a copy of all namespaced loggers so users can reliably .on() them.
|
|
// Note that these cached functions will need to deal with changes in the backend.
|
|
const loggerCache = new Map();
|
|
// Our current global backend. This might be:
|
|
let cachedBackend = undefined;
|
|
/**
|
|
* Set the backend to use for our log output.
|
|
* - A backend object
|
|
* - null to disable logging
|
|
* - undefined for "nothing yet", defaults to the Node backend
|
|
*
|
|
* @param backend Results from one of the get*Backend() functions.
|
|
*/
|
|
function setBackend(backend) {
|
|
cachedBackend = backend;
|
|
loggerCache.clear();
|
|
}
|
|
/**
|
|
* Creates a logging function. Multiple calls to this with the same namespace
|
|
* will produce the same logger, with the same event emitter hooks.
|
|
*
|
|
* Namespaces can be a simple string ("system" name), or a qualified string
|
|
* (system:subsystem), which can be used for filtering, or for "system:*".
|
|
*
|
|
* @param namespace The namespace, a descriptive text string.
|
|
* @returns A function you can call that works similar to console.log().
|
|
*/
|
|
function log(namespace, parent) {
|
|
// If the enable flag isn't set, do nothing.
|
|
const enablesFlag = process.env[exports.env.nodeEnables];
|
|
if (!enablesFlag) {
|
|
return exports.placeholder;
|
|
}
|
|
// This might happen mostly if the typings are dropped in a user's code,
|
|
// or if they're calling from JavaScript.
|
|
if (!namespace) {
|
|
return exports.placeholder;
|
|
}
|
|
// Handle sub-loggers.
|
|
if (parent) {
|
|
namespace = `${parent.instance.namespace}:${namespace}`;
|
|
}
|
|
// Reuse loggers so things like event sinks are persistent.
|
|
const existing = loggerCache.get(namespace);
|
|
if (existing) {
|
|
return existing.func;
|
|
}
|
|
// Do we have a backend yet?
|
|
if (cachedBackend === null) {
|
|
// Explicitly disabled.
|
|
return exports.placeholder;
|
|
}
|
|
else if (cachedBackend === undefined) {
|
|
// One hasn't been made yet, so default to Node.
|
|
cachedBackend = getNodeBackend();
|
|
}
|
|
// The logger is further wrapped so we can handle the backend changing out.
|
|
const logger = (() => {
|
|
let previousBackend = undefined;
|
|
const newLogger = new AdhocDebugLogger(namespace, (fields, ...args) => {
|
|
if (previousBackend !== cachedBackend) {
|
|
// Did the user pass a custom backend?
|
|
if (cachedBackend === null) {
|
|
// Explicitly disabled.
|
|
return;
|
|
}
|
|
else if (cachedBackend === undefined) {
|
|
// One hasn't been made yet, so default to Node.
|
|
cachedBackend = getNodeBackend();
|
|
}
|
|
previousBackend = cachedBackend;
|
|
}
|
|
cachedBackend === null || cachedBackend === void 0 ? void 0 : cachedBackend.log(namespace, fields, ...args);
|
|
});
|
|
return newLogger;
|
|
})();
|
|
loggerCache.set(namespace, logger);
|
|
return logger.func;
|
|
}
|
|
//# sourceMappingURL=logging-utils.js.map
|