feat: Complete zCode CLI X with Telegram bot integration

- 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
This commit is contained in:
admin
2026-05-05 09:01:26 +00:00
Unverified
parent 4a7035dd92
commit 875c7f9b91
24688 changed files with 3224957 additions and 221 deletions

View File

@@ -0,0 +1,7 @@
import type { AnsiCode } from "./tokenize.js";
export declare const endCodesSet: Set<string>;
export declare function getLinkStartCode(url: string, params?: Record<string, string>): string;
export declare function getEndCode(code: string): string;
export declare function ansiCodesToString(codes: AnsiCode[]): string;
/** Check if a code is an intensity code (bold or dim) - these share endCode 22m but can coexist */
export declare function isIntensityCode(code: AnsiCode): boolean;

View File

@@ -0,0 +1,57 @@
import ansiStyles from "ansi-styles";
import { linkCodePrefix, linkCodeSuffix, linkEndCode, linkEndCodeC1ST, linkEndCodeST, } from "./consts.js";
export const endCodesSet = new Set();
const endCodesMap = new Map();
for (const [start, end] of ansiStyles.codes) {
endCodesSet.add(ansiStyles.color.ansi(end));
endCodesMap.set(ansiStyles.color.ansi(start), ansiStyles.color.ansi(end));
}
export function getLinkStartCode(url, params) {
const paramsStr = params
? Object.entries(params)
.map(([k, v]) => `${k}=${v}`)
.join(":")
: "";
return `${linkCodePrefix}${paramsStr};${url}${linkCodeSuffix}`;
}
export function getEndCode(code) {
if (endCodesSet.has(code))
return code;
if (endCodesMap.has(code))
return endCodesMap.get(code);
// We have a few special cases to handle here:
// Links:
if (code.startsWith(linkCodePrefix)) {
if (code.endsWith("\x1B\\"))
return linkEndCodeST;
if (code.endsWith("\x9C"))
return linkEndCodeC1ST;
return linkEndCode; // BEL (\x07)
}
code = code.slice(2);
// 8-bit/24-bit colors:
if (code.startsWith("38")) {
return ansiStyles.color.close;
}
else if (code.startsWith("48")) {
return ansiStyles.bgColor.close;
}
// Otherwise find the reset code in the ansi-styles map
const ret = ansiStyles.codes.get(parseInt(code, 10));
if (ret) {
return ansiStyles.color.ansi(ret);
}
else {
return ansiStyles.reset.open;
}
}
export function ansiCodesToString(codes) {
// Deduplicate ANSI code strings before joining
const deduplicated = new Set(codes.map((code) => code.code));
return [...deduplicated].join("");
}
/** Check if a code is an intensity code (bold or dim) - these share endCode 22m but can coexist */
export function isIntensityCode(code) {
return code.code === ansiStyles.bold.open || code.code === ansiStyles.dim.open;
}
//# sourceMappingURL=ansiCodes.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ansiCodes.js","sourceRoot":"","sources":["../src/ansiCodes.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,17 @@
export declare const CC_BEL: number;
export declare const CC_ESC: number;
export declare const CC_BACKSLASH: number;
export declare const CC_CSI: number;
export declare const CC_OSC: number;
export declare const CC_C1_ST: number;
export declare const CC_0: number;
export declare const CC_9: number;
export declare const CC_SEMI: number;
export declare const CC_M: number;
export declare const ESCAPES: Set<number>;
export declare const linkCodePrefix: string;
export declare const linkCodePrefixCharCodes: number[];
export declare const linkCodeSuffix = "\u0007";
export declare const linkEndCode: string;
export declare const linkEndCodeST: string;
export declare const linkEndCodeC1ST: string;

View File

@@ -0,0 +1,28 @@
// Named ANSI control characters
const BEL = "\x07";
const ESC = "\x1b";
const BACKSLASH = "\\";
const CSI = "[";
const OSC = "]";
const C1_ST = "\x9c";
// Char codes (derived from named characters)
export const CC_BEL = BEL.charCodeAt(0);
export const CC_ESC = ESC.charCodeAt(0);
export const CC_BACKSLASH = BACKSLASH.charCodeAt(0);
export const CC_CSI = CSI.charCodeAt(0);
export const CC_OSC = OSC.charCodeAt(0);
export const CC_C1_ST = C1_ST.charCodeAt(0);
export const CC_0 = "0".charCodeAt(0);
export const CC_9 = "9".charCodeAt(0);
export const CC_SEMI = ";".charCodeAt(0);
export const CC_M = "m".charCodeAt(0);
// Escape code points
export const ESCAPES = new Set([CC_ESC, 0x9b]); // \x1b and \x9b
// OSC 8 hyperlink constants
export const linkCodePrefix = `${ESC}${OSC}8;`;
export const linkCodePrefixCharCodes = linkCodePrefix.split("").map((char) => char.charCodeAt(0));
export const linkCodeSuffix = BEL;
export const linkEndCode = `${ESC}${OSC}8;;${BEL}`;
export const linkEndCodeST = `${ESC}${OSC}8;;${ESC}${BACKSLASH}`;
export const linkEndCodeC1ST = `${ESC}${OSC}8;;${C1_ST}`;
//# sourceMappingURL=consts.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AAAA,gCAAgC;AAChC,MAAM,GAAG,GAAG,MAAM,CAAC;AACnB,MAAM,GAAG,GAAG,MAAM,CAAC;AACnB,MAAM,SAAS,GAAG,IAAI,CAAC;AACvB,MAAM,GAAG,GAAG,GAAG,CAAC;AAChB,MAAM,GAAG,GAAG,GAAG,CAAC;AAChB,MAAM,KAAK,GAAG,MAAM,CAAC;AAErB,6CAA6C;AAC7C,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACxC,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACxC,MAAM,CAAC,MAAM,YAAY,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACpD,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACxC,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACxC,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAC5C,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACtC,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACtC,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;AAEtC,qBAAqB;AACrB,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB;AAEhE,4BAA4B;AAC5B,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC;AAC/C,MAAM,CAAC,MAAM,uBAAuB,GAAG,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAClG,MAAM,CAAC,MAAM,cAAc,GAAG,GAAG,CAAC;AAClC,MAAM,CAAC,MAAM,WAAW,GAAG,GAAG,GAAG,GAAG,GAAG,MAAM,GAAG,EAAE,CAAC;AACnD,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;AACjE,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,GAAG,GAAG,GAAG,MAAM,KAAK,EAAE,CAAC"}

View File

@@ -0,0 +1,6 @@
import type { AnsiCode } from "./tokenize.js";
/**
* Returns the minimum amount of ANSI codes necessary to get from the compound style `from` to `to`.
* Both `from` and `to` are expected to be reduced.
*/
export declare function diffAnsiCodes(from: AnsiCode[], to: AnsiCode[]): AnsiCode[];

View File

@@ -0,0 +1,26 @@
import { isIntensityCode } from "./ansiCodes.js";
import { undoAnsiCodes } from "./undo.js";
/**
* Returns the minimum amount of ANSI codes necessary to get from the compound style `from` to `to`.
* Both `from` and `to` are expected to be reduced.
*/
export function diffAnsiCodes(from, to) {
const endCodesInTo = new Set(to.map((code) => code.endCode));
const startCodesInTo = new Set(to.map((code) => code.code));
const startCodesInFrom = new Set(from.map((code) => code.code));
return [
// Ignore all styles in `from` that are not overwritten or removed by `to`
// Disable all styles in `from` that are removed in `to`
...undoAnsiCodes(from.filter((code) => {
// Special case: Intensity codes (1m, 2m) can coexist (both end with 22m).
// We have to check the start codes for those, otherwise we might miss a reset.
if (isIntensityCode(code)) {
return !startCodesInTo.has(code.code);
}
return !endCodesInTo.has(code.endCode);
})),
// Add all styles in `to` that don't exist in `from`
...to.filter((code) => !startCodesInFrom.has(code.code)),
];
}
//# sourceMappingURL=diff.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"diff.js","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAgB,EAAE,EAAc;IAC7D,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7D,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5D,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAEhE,OAAO;QACN,0EAA0E;QAC1E,wDAAwD;QACxD,GAAG,aAAa,CACf,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;YACpB,0EAA0E;YAC1E,+EAA+E;YAC/E,IAAI,eAAe,CAAC,IAAI,CAAC,EAAE;gBAC1B,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aACtC;YACD,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxC,CAAC,CAAC,CACF;QACD,oDAAoD;QACpD,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KACxD,CAAC;AACH,CAAC"}

View File

@@ -0,0 +1,6 @@
export { ansiCodesToString } from "./ansiCodes.js";
export { diffAnsiCodes } from "./diff.js";
export { reduceAnsiCodes, reduceAnsiCodesIncremental } from "./reduce.js";
export * from "./styledChars.js";
export * from "./tokenize.js";
export { undoAnsiCodes } from "./undo.js";

View File

@@ -0,0 +1,7 @@
export { ansiCodesToString } from "./ansiCodes.js";
export { diffAnsiCodes } from "./diff.js";
export { reduceAnsiCodes, reduceAnsiCodesIncremental } from "./reduce.js";
export * from "./styledChars.js";
export * from "./tokenize.js";
export { undoAnsiCodes } from "./undo.js";
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAC1E,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC"}

View File

@@ -0,0 +1,5 @@
import type { AnsiCode } from "./tokenize.js";
/** Reduces the given array of ANSI codes to the minimum necessary to render with the same style */
export declare function reduceAnsiCodes(codes: AnsiCode[]): AnsiCode[];
/** Like {@link reduceAnsiCodes}, but assumes that `codes` is already reduced. Further reductions are only done for the items in `newCodes`. */
export declare function reduceAnsiCodesIncremental(codes: AnsiCode[], newCodes: AnsiCode[]): AnsiCode[];

View File

@@ -0,0 +1,37 @@
import ansiStyles from "ansi-styles";
import { endCodesSet, isIntensityCode } from "./ansiCodes.js";
/** Reduces the given array of ANSI codes to the minimum necessary to render with the same style */
export function reduceAnsiCodes(codes) {
return reduceAnsiCodesIncremental([], codes);
}
/** Like {@link reduceAnsiCodes}, but assumes that `codes` is already reduced. Further reductions are only done for the items in `newCodes`. */
export function reduceAnsiCodesIncremental(codes, newCodes) {
let ret = [...codes];
for (const code of newCodes) {
if (code.code === ansiStyles.reset.open) {
// Reset code, disable all codes
ret = [];
}
else if (endCodesSet.has(code.code)) {
// This is an end code, disable all matching start codes
ret = ret.filter((retCode) => retCode.endCode !== code.code);
}
else {
// This is a start code. Remove codes it "overrides", then add it.
// If a new code has the same endCode, it "overrides" existing ones.
// Special case: Intensity codes (1m, 2m) can coexist (both end with 22m).
// We only add those if the exact same code is not already present.
if (isIntensityCode(code)) {
if (!ret.find((retCode) => retCode.code === code.code && retCode.endCode === code.endCode)) {
ret.push(code);
}
}
else {
ret = ret.filter((retCode) => retCode.endCode !== code.endCode);
ret.push(code);
}
}
}
return ret;
}
//# sourceMappingURL=reduce.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"reduce.js","sourceRoot":"","sources":["../src/reduce.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAG9D,mGAAmG;AACnG,MAAM,UAAU,eAAe,CAAC,KAAiB;IAChD,OAAO,0BAA0B,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;AAC9C,CAAC;AAED,+IAA+I;AAC/I,MAAM,UAAU,0BAA0B,CAAC,KAAiB,EAAE,QAAoB;IACjF,IAAI,GAAG,GAAe,CAAC,GAAG,KAAK,CAAC,CAAC;IACjC,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE;QAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE;YACxC,gCAAgC;YAChC,GAAG,GAAG,EAAE,CAAC;SACT;aAAM,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YACtC,wDAAwD;YACxD,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC;SAC7D;aAAM;YACN,kEAAkE;YAClE,oEAAoE;YACpE,0EAA0E;YAC1E,mEAAmE;YACnE,IAAI,eAAe,CAAC,IAAI,CAAC,EAAE;gBAC1B,IACC,CAAC,GAAG,CAAC,IAAI,CACR,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,CAC3E,EACA;oBACD,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;iBACf;aACD;iBAAM;gBACN,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,CAAC,CAAC;gBAChE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aACf;SACD;KACD;IACD,OAAO,GAAG,CAAC;AACZ,CAAC"}

View File

@@ -0,0 +1,6 @@
import type { AnsiCode, Char, Token } from "./tokenize.js";
export interface StyledChar extends Char {
styles: AnsiCode[];
}
export declare function styledCharsFromTokens(tokens: Token[]): StyledChar[];
export declare function styledCharsToString(chars: StyledChar[]): string;

View File

@@ -0,0 +1,38 @@
import { ansiCodesToString } from "./ansiCodes.js";
import { diffAnsiCodes } from "./diff.js";
import { reduceAnsiCodesIncremental } from "./reduce.js";
export function styledCharsFromTokens(tokens) {
let codes = [];
const ret = [];
for (const token of tokens) {
if (token.type === "ansi") {
codes = reduceAnsiCodesIncremental(codes, [token]);
}
else if (token.type === "char") {
ret.push({
...token,
styles: [...codes],
});
}
}
return ret;
}
export function styledCharsToString(chars) {
let ret = "";
for (let i = 0; i < chars.length; i++) {
const char = chars[i];
if (i === 0) {
ret += ansiCodesToString(char.styles);
}
else {
ret += ansiCodesToString(diffAnsiCodes(chars[i - 1].styles, char.styles));
}
ret += char.value;
// reset active styles at the end of the string
if (i === chars.length - 1) {
ret += ansiCodesToString(diffAnsiCodes(char.styles, []));
}
}
return ret;
}
//# sourceMappingURL=styledChars.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"styledChars.js","sourceRoot":"","sources":["../src/styledChars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAOzD,MAAM,UAAU,qBAAqB,CAAC,MAAe;IACpD,IAAI,KAAK,GAAe,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE;YAC1B,KAAK,GAAG,0BAA0B,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;SACnD;aAAM,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE;YACjC,GAAG,CAAC,IAAI,CAAC;gBACR,GAAG,KAAK;gBACR,MAAM,EAAE,CAAC,GAAG,KAAK,CAAC;aAClB,CAAC,CAAC;SACH;KACD;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAmB;IACtD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,CAAC,KAAK,CAAC,EAAE;YACZ,GAAG,IAAI,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;SACtC;aAAM;YACN,GAAG,IAAI,iBAAiB,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;SAC1E;QACD,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC;QAClB,+CAA+C;QAC/C,IAAI,CAAC,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;YAC3B,GAAG,IAAI,iBAAiB,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC;SACzD;KACD;IACD,OAAO,GAAG,CAAC;AACZ,CAAC"}

View File

@@ -0,0 +1,16 @@
export interface AnsiCode {
type: "ansi";
code: string;
endCode: string;
}
export interface ControlCode {
type: "control";
code: string;
}
export interface Char {
type: "char";
value: string;
fullWidth: boolean;
}
export type Token = AnsiCode | ControlCode | Char;
export declare function tokenize(str: string, endChar?: number): Token[];

View File

@@ -0,0 +1,194 @@
import isFullwidthCodePoint from "is-fullwidth-code-point";
import { getEndCode } from "./ansiCodes.js";
import { CC_0, CC_9, CC_BEL, CC_BACKSLASH, CC_C1_ST, CC_ESC, CC_M, CC_CSI, CC_OSC, CC_SEMI, ESCAPES, linkCodePrefix, linkCodePrefixCharCodes, } from "./consts.js";
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
function isFullwidthGrapheme(grapheme, baseCodePoint) {
if (isFullwidthCodePoint(baseCodePoint))
return true;
// Variation Selector 16 forces emoji presentation (2 columns wide)
if (grapheme.includes("\uFE0F"))
return true;
// Regional indicator pairs form flag emoji (2 columns wide)
if (baseCodePoint >= 0x1f1e6 && baseCodePoint <= 0x1f1ff)
return true;
return false;
}
// HOT PATH: Use only basic string/char code operations for maximum performance
function parseLinkCode(string, offset) {
string = string.slice(offset);
for (let index = 1; index < linkCodePrefixCharCodes.length; index++) {
if (string.charCodeAt(index) !== linkCodePrefixCharCodes[index]) {
return undefined;
}
}
// Find the semicolon that ends params
const paramsEndIndex = string.indexOf(";", linkCodePrefix.length);
if (paramsEndIndex === -1)
return undefined;
// This is a link code (with or without the URL part). Find the end of it.
const endIndex = findOSCTerminatorIndex(string, paramsEndIndex + 1);
if (endIndex === -1)
return undefined;
return string.slice(0, endIndex + 1);
}
// HOT PATH: Generic fallback for non-link OSC sequences (window title, notifications, etc.)
function parseOSCSequence(string, offset) {
string = string.slice(offset);
// Find the OSC terminator (starting after "ESC ]")
const endIndex = findOSCTerminatorIndex(string, 2);
if (endIndex === -1)
return undefined;
return string.slice(0, endIndex + 1);
}
/**
* Finds the index of the last character of the first OSC terminator at or after startIndex.
* Recognizes BEL (\x07), C1 ST (\x9C), and ESC+backslash (\x1B\x5C).
* Returns -1 if no terminator is found.
*/
function findOSCTerminatorIndex(string, startIndex) {
for (let i = startIndex; i < string.length; i++) {
const ch = string.charCodeAt(i);
if (ch === CC_BEL)
return i;
if (ch === CC_C1_ST)
return i;
if (ch === CC_ESC && i + 1 < string.length && string.charCodeAt(i + 1) === CC_BACKSLASH) {
return i + 1;
}
}
return -1;
}
/**
* Scans through the given string and finds the index of the last character of an SGR sequence
* like `\x1B[38;2;123;123;123m`. This assumes that the string has been checked to start with `\x1B[`.
* Returns -1 if no valid SGR sequence is found.
*/
function findSGRSequenceEndIndex(str) {
for (let index = 2; index < str.length; index++) {
const charCode = str.charCodeAt(index);
// m marks the end of the SGR sequence
if (charCode === CC_M)
return index;
// Digits and semicolons are valid
if (charCode === CC_SEMI)
continue;
if (charCode >= CC_0 && charCode <= CC_9)
continue;
// Everything else is invalid
break;
}
return -1;
}
// HOT PATH: Use only basic string/char code operations for maximum performance
function parseSGRSequence(string, offset) {
string = string.slice(offset);
const endIndex = findSGRSequenceEndIndex(string);
if (endIndex === -1)
return;
return string.slice(0, endIndex + 1);
}
/**
* Splits compound SGR sequences like `\x1B[1;3;31m` into individual components
*/
function splitCompoundSGRSequences(code) {
if (!code.includes(";")) {
// Not a compound code
return [code];
}
const codeParts = code
// Strip off the escape sequences \x1B[ and m
.slice(2, -1)
.split(";");
const ret = [];
for (let i = 0; i < codeParts.length; i++) {
const rawCode = codeParts[i];
// Keep 8-bit and 24-bit color codes (containing multiple ";") together
if (rawCode === "38" || rawCode === "48") {
if (i + 2 < codeParts.length && codeParts[i + 1] === "5") {
// 8-bit color, followed by another number
ret.push(codeParts.slice(i, i + 3).join(";"));
i += 2;
continue;
}
else if (i + 4 < codeParts.length && codeParts[i + 1] === "2") {
// 24-bit color, followed by three numbers
ret.push(codeParts.slice(i, i + 5).join(";"));
i += 4;
continue;
}
}
// Not a (valid) 8/24-bit color code, push as is
ret.push(rawCode);
}
return ret.map((part) => `\x1b[${part}m`);
}
export function tokenize(str, endChar = Number.POSITIVE_INFINITY) {
const ret = [];
let visible = 0;
let codeEndIndex = 0;
for (const { segment, index } of segmenter.segment(str)) {
// Skip segments consumed as part of an ANSI sequence
if (index < codeEndIndex)
continue;
const codePoint = segment.codePointAt(0);
if (ESCAPES.has(codePoint)) {
let code;
// Peek the next code point to determine the type of ANSI sequence
const nextCodePoint = str.codePointAt(index + 1);
if (nextCodePoint === CC_OSC) {
// ] = operating system commands
code = parseLinkCode(str, index);
if (code) {
// OSC 8 hyperlinks are paired codes with an endCode
ret.push({
type: "ansi",
code: code,
endCode: getEndCode(code),
});
}
else {
// Other OSC sequences (window title, etc.) are self-contained
// control codes with no endCode.
code = parseOSCSequence(str, index);
if (code) {
ret.push({
type: "control",
code: code,
});
}
}
}
else if (nextCodePoint === CC_CSI) {
// [ = control sequence introducer, like SGR sequences [...m
code = parseSGRSequence(str, index);
if (code) {
// Split compound codes into individual tokens
const codes = splitCompoundSGRSequences(code);
for (const individualCode of codes) {
ret.push({
type: "ansi",
code: individualCode,
endCode: getEndCode(individualCode),
});
}
}
}
if (code) {
codeEndIndex = index + code.length;
continue;
}
}
const fullWidth = isFullwidthGrapheme(segment, codePoint);
ret.push({
type: "char",
value: segment,
fullWidth,
});
visible += fullWidth ? 2 : 1;
if (visible >= endChar) {
break;
}
}
return ret;
}
//# sourceMappingURL=tokenize.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
import type { AnsiCode } from "./tokenize.js";
/** Returns the combination of ANSI codes needed to undo the given ANSI codes */
export declare function undoAnsiCodes(codes: AnsiCode[]): AnsiCode[];

View File

@@ -0,0 +1,11 @@
import { reduceAnsiCodes } from "./reduce.js";
/** Returns the combination of ANSI codes needed to undo the given ANSI codes */
export function undoAnsiCodes(codes) {
return reduceAnsiCodes(codes)
.reverse()
.map((code) => ({
...code,
code: code.endCode,
}));
}
//# sourceMappingURL=undo.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"undo.js","sourceRoot":"","sources":["../src/undo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,gFAAgF;AAChF,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC9C,OAAO,eAAe,CAAC,KAAK,CAAC;SAC3B,OAAO,EAAE;SACT,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACf,GAAG,IAAI;QACP,IAAI,EAAE,IAAI,CAAC,OAAO;KAClB,CAAC,CAAC,CAAC;AACN,CAAC"}