openzca 0.1.35 → 0.1.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +257 -168
  2. package/package.json +2 -1
package/dist/cli.js CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  TextStyle,
19
19
  ThreadType
20
20
  } from "zca-js";
21
+ import { marked } from "marked";
21
22
 
22
23
  // src/lib/store.ts
23
24
  import fs from "fs/promises";
@@ -706,95 +707,7 @@ function asThreadType(groupFlag) {
706
707
  return groupFlag ? ThreadType.Group : ThreadType.User;
707
708
  }
708
709
  function parseTextStyles(input) {
709
- const allStyles = [];
710
- const codeLineIndices = /* @__PURE__ */ new Set();
711
- {
712
- const rawLines = input.split("\n");
713
- const kept = [];
714
- let inCode = false;
715
- for (const rawLine of rawLines) {
716
- if (/^```/.test(rawLine)) {
717
- inCode = !inCode;
718
- continue;
719
- }
720
- if (inCode) {
721
- codeLineIndices.add(kept.length);
722
- }
723
- kept.push(rawLine);
724
- }
725
- input = kept.join("\n");
726
- }
727
- const escapeMap = [];
728
- const escaped = input.replace(/\\([*_~#\\{}>+\-])/g, (_match, ch) => {
729
- const idx = escapeMap.length;
730
- escapeMap.push(ch);
731
- return `${idx}`;
732
- });
733
- const lines = escaped.split("\n");
734
- const lineStyles = [];
735
- const processedLines = [];
736
- for (let i = 0; i < lines.length; i++) {
737
- let line = lines[i];
738
- if (codeLineIndices.has(i)) {
739
- processedLines.push(line);
740
- continue;
741
- }
742
- const headingMatch = line.match(/^(#{1,4})\s(.*)$/);
743
- if (headingMatch) {
744
- const level = headingMatch[1].length;
745
- lineStyles.push({ lineIndex: i, style: TextStyle.Bold });
746
- if (level === 1) lineStyles.push({ lineIndex: i, style: TextStyle.Big });
747
- else if (level === 3 || level === 4) lineStyles.push({ lineIndex: i, style: TextStyle.Small });
748
- processedLines.push(headingMatch[2]);
749
- continue;
750
- }
751
- const bqMatch = line.match(/^(>+)\s?(.*)$/);
752
- if (bqMatch) {
753
- lineStyles.push({ lineIndex: i, style: TextStyle.Indent, indentSize: bqMatch[1].length });
754
- line = bqMatch[2];
755
- }
756
- const indentContentMatch = line.match(/^(\s+)(.*)$/);
757
- let indentLevel = 0;
758
- let content = line;
759
- if (indentContentMatch) {
760
- indentLevel = Math.max(1, Math.floor(indentContentMatch[1].length / 2));
761
- content = indentContentMatch[2];
762
- }
763
- if (/^[-*+]\s\[[ xX]\]\s/.test(content)) {
764
- processedLines.push(line);
765
- continue;
766
- }
767
- const olMatch = content.match(/^(\d+)\.\s(.*)$/);
768
- if (olMatch) {
769
- if (indentLevel > 0) {
770
- lineStyles.push({ lineIndex: i, style: TextStyle.Indent, indentSize: indentLevel });
771
- }
772
- lineStyles.push({ lineIndex: i, style: TextStyle.OrderedList });
773
- processedLines.push(olMatch[2]);
774
- continue;
775
- }
776
- const ulMatch = content.match(/^[-*+]\s(.*)$/);
777
- if (ulMatch) {
778
- if (indentLevel > 0) {
779
- lineStyles.push({ lineIndex: i, style: TextStyle.Indent, indentSize: indentLevel });
780
- }
781
- lineStyles.push({ lineIndex: i, style: TextStyle.UnorderedList });
782
- processedLines.push(ulMatch[1]);
783
- continue;
784
- }
785
- processedLines.push(line);
786
- }
787
- for (const ci of codeLineIndices) {
788
- if (ci < processedLines.length) {
789
- processedLines[ci] = processedLines[ci].replace(/[*_~{}]/g, (ch) => {
790
- const idx = escapeMap.length;
791
- escapeMap.push(ch);
792
- return `${idx}`;
793
- });
794
- }
795
- }
796
- const inlineInput = processedLines.join("\n");
797
- const colorMap = {
710
+ const tagColorMap = {
798
711
  red: TextStyle.Red,
799
712
  orange: TextStyle.Orange,
800
713
  yellow: TextStyle.Yellow,
@@ -803,96 +716,272 @@ function parseTextStyles(input) {
803
716
  big: TextStyle.Big,
804
717
  underline: TextStyle.Underline
805
718
  };
806
- const tagNames = Object.keys(colorMap).join("|");
807
- const markers = [
808
- // Tags first so inner markdown markers are preserved for subsequent passes
809
- { pattern: new RegExp(`\\{(${tagNames})\\}(.+?)\\{/\\1\\}`, "g"), style: null },
810
- // *** = bold + italic
811
- { pattern: /\*\*\*(.+?)\*\*\*/g, style: TextStyle.Bold, extraStyles: [TextStyle.Italic] },
812
- // ** and __ = bold (standard markdown)
813
- { pattern: /\*\*(.+?)\*\*/g, style: TextStyle.Bold },
814
- { pattern: /__(.+?)__/g, style: TextStyle.Bold },
815
- // * and _ = italic (standard markdown)
816
- { pattern: /\*(.+?)\*/g, style: TextStyle.Italic },
817
- { pattern: /_(.+?)_/g, style: TextStyle.Italic },
818
- // ~~ = strikethrough
819
- { pattern: /~~(.+?)~~/g, style: TextStyle.StrikeThrough }
820
- ];
821
- let segments = [{ text: inlineInput, styles: [] }];
822
- for (const marker of markers) {
823
- const next = [];
824
- for (const seg of segments) {
825
- let lastIndex = 0;
826
- const regex = new RegExp(marker.pattern.source, marker.pattern.flags);
827
- let m;
828
- while ((m = regex.exec(seg.text)) !== null) {
829
- if (m.index > lastIndex) {
830
- next.push({ text: seg.text.slice(lastIndex, m.index), styles: [...seg.styles] });
719
+ const tagNames = Object.keys(tagColorMap).join("|");
720
+ const tagRegex = new RegExp(`\\{(${tagNames})\\}([\\s\\S]+?)\\{/\\1\\}`, "g");
721
+ const tagSlots = [];
722
+ const maskedInput = input.replace(tagRegex, (_m, tagName, inner) => {
723
+ const idx = tagSlots.length;
724
+ const placeholder = `TAG${idx}`;
725
+ tagSlots.push({ placeholder, tagName, innerMd: inner });
726
+ return placeholder;
727
+ });
728
+ const tokens = marked.lexer(maskedInput);
729
+ const styles = [];
730
+ let text = "";
731
+ function emitLineStyles(lineStart, lineLen, lineStyles) {
732
+ if (lineLen <= 0) return;
733
+ for (const ls of lineStyles) {
734
+ if (ls.style === TextStyle.Indent) {
735
+ styles.push({ start: lineStart, len: lineLen, st: ls.style, indentSize: ls.indentSize });
736
+ } else {
737
+ styles.push({ start: lineStart, len: lineLen, st: ls.style });
738
+ }
739
+ }
740
+ }
741
+ function walkInline(tokens2, inheritedStyles) {
742
+ for (const tok of tokens2) {
743
+ switch (tok.type) {
744
+ case "text": {
745
+ const t = tok;
746
+ if (t.tokens && t.tokens.length > 0) {
747
+ walkInline(t.tokens, inheritedStyles);
748
+ } else {
749
+ const start = text.length;
750
+ text += t.text;
751
+ const len = t.text.length;
752
+ if (len > 0) {
753
+ for (const st of inheritedStyles) {
754
+ styles.push({ start, len, st });
755
+ }
756
+ }
757
+ }
758
+ break;
831
759
  }
832
- const isTagPattern = marker.style === null;
833
- const innerText = isTagPattern ? m[2] : m[1];
834
- const resolvedStyle = isTagPattern ? colorMap[m[1]] : marker.style;
835
- const combined = [...seg.styles, resolvedStyle];
836
- if (marker.extraStyles) {
837
- combined.push(...marker.extraStyles);
760
+ case "strong": {
761
+ const t = tok;
762
+ walkInline(t.tokens, [...inheritedStyles, TextStyle.Bold]);
763
+ break;
764
+ }
765
+ case "em": {
766
+ const t = tok;
767
+ walkInline(t.tokens, [...inheritedStyles, TextStyle.Italic]);
768
+ break;
769
+ }
770
+ case "del": {
771
+ const t = tok;
772
+ walkInline(t.tokens, [...inheritedStyles, TextStyle.StrikeThrough]);
773
+ break;
774
+ }
775
+ case "codespan": {
776
+ const t = tok;
777
+ const start = text.length;
778
+ text += t.text;
779
+ const len = t.text.length;
780
+ if (len > 0) {
781
+ for (const st of inheritedStyles) {
782
+ styles.push({ start, len, st });
783
+ }
784
+ }
785
+ break;
786
+ }
787
+ case "escape": {
788
+ const t = tok;
789
+ const start = text.length;
790
+ text += t.text;
791
+ if (t.text.length > 0) {
792
+ for (const st of inheritedStyles) {
793
+ styles.push({ start, len: t.text.length, st });
794
+ }
795
+ }
796
+ break;
797
+ }
798
+ case "link": {
799
+ const t = tok;
800
+ walkInline(t.tokens, inheritedStyles);
801
+ break;
802
+ }
803
+ case "image": {
804
+ const t = tok;
805
+ const start = text.length;
806
+ text += t.text;
807
+ if (t.text.length > 0) {
808
+ for (const st of inheritedStyles) {
809
+ styles.push({ start, len: t.text.length, st });
810
+ }
811
+ }
812
+ break;
813
+ }
814
+ case "br": {
815
+ text += "\n";
816
+ break;
817
+ }
818
+ case "checkbox": {
819
+ const t = tok;
820
+ text += t.checked ? "[x] " : "[ ] ";
821
+ break;
822
+ }
823
+ default: {
824
+ const t = tok;
825
+ if (t.tokens && t.tokens.length > 0) {
826
+ walkInline(t.tokens, inheritedStyles);
827
+ } else if (t.text != null) {
828
+ const start = text.length;
829
+ text += t.text;
830
+ if (t.text.length > 0) {
831
+ for (const st of inheritedStyles) {
832
+ styles.push({ start, len: t.text.length, st });
833
+ }
834
+ }
835
+ }
836
+ break;
838
837
  }
839
- next.push({ text: innerText, styles: combined });
840
- lastIndex = regex.lastIndex;
841
- }
842
- if (lastIndex < seg.text.length) {
843
- next.push({ text: seg.text.slice(lastIndex), styles: [...seg.styles] });
844
- } else if (lastIndex === 0) {
845
- next.push(seg);
846
838
  }
847
839
  }
848
- segments = next;
849
- }
850
- let plainText = "";
851
- for (const seg of segments) {
852
- const start = plainText.length;
853
- plainText += seg.text;
854
- for (const st of seg.styles) {
855
- allStyles.push({ start, len: seg.text.length, st });
856
- }
857
840
  }
858
- if (escapeMap.length > 0) {
859
- const escRegex = /\x01(\d+)\x02/g;
860
- const shifts = [];
861
- let cumDelta = 0;
862
- for (const m of plainText.matchAll(escRegex)) {
863
- const idx = parseInt(m[1], 10);
864
- cumDelta += m[0].length - escapeMap[idx].length;
865
- shifts.push({ pos: m.index + m[0].length, delta: cumDelta });
866
- }
867
- for (const s of allStyles) {
868
- let startDelta = 0;
869
- let endDelta = 0;
870
- const end = s.start + s.len;
871
- for (const sh of shifts) {
872
- if (sh.pos <= s.start) startDelta = sh.delta;
873
- if (sh.pos <= end) endDelta = sh.delta;
841
+ function walkBlock(tokens2, blockLineStyles, depth) {
842
+ for (let ti = 0; ti < tokens2.length; ti++) {
843
+ const tok = tokens2[ti];
844
+ switch (tok.type) {
845
+ case "heading": {
846
+ const t = tok;
847
+ const headingStyles = [{ style: TextStyle.Bold }];
848
+ if (t.depth === 1) headingStyles.push({ style: TextStyle.Big });
849
+ else if (t.depth === 3 || t.depth === 4) headingStyles.push({ style: TextStyle.Small });
850
+ if (t.depth <= 4) {
851
+ const lineStart = text.length;
852
+ walkInline(t.tokens, []);
853
+ const lineLen = text.length - lineStart;
854
+ emitLineStyles(lineStart, lineLen, [...blockLineStyles, ...headingStyles]);
855
+ text += "\n";
856
+ } else {
857
+ const lineStart = text.length;
858
+ text += "#".repeat(t.depth) + " ";
859
+ walkInline(t.tokens, []);
860
+ const lineLen = text.length - lineStart;
861
+ emitLineStyles(lineStart, lineLen, blockLineStyles);
862
+ text += "\n";
863
+ }
864
+ break;
865
+ }
866
+ case "paragraph": {
867
+ const t = tok;
868
+ const lineStart = text.length;
869
+ walkInline(t.tokens, []);
870
+ const lineLen = text.length - lineStart;
871
+ emitLineStyles(lineStart, lineLen, blockLineStyles);
872
+ text += "\n";
873
+ break;
874
+ }
875
+ case "code": {
876
+ const t = tok;
877
+ text += t.text;
878
+ text += "\n";
879
+ break;
880
+ }
881
+ case "blockquote": {
882
+ const t = tok;
883
+ const bqDepth = depth + 1;
884
+ walkBlock(t.tokens, [...blockLineStyles, { style: TextStyle.Indent, indentSize: Math.min(5, bqDepth) }], bqDepth);
885
+ break;
886
+ }
887
+ case "list": {
888
+ const t = tok;
889
+ const listStyle = t.ordered ? TextStyle.OrderedList : TextStyle.UnorderedList;
890
+ for (const item of t.items) {
891
+ if (item.task) {
892
+ const lineStart = text.length;
893
+ text += item.checked ? "[x] " : "[ ] ";
894
+ const nonCheckboxTokens = item.tokens.filter((t2) => t2.type !== "checkbox");
895
+ walkInline(nonCheckboxTokens, []);
896
+ const lineLen = text.length - lineStart;
897
+ emitLineStyles(lineStart, lineLen, blockLineStyles);
898
+ text += "\n";
899
+ } else {
900
+ const hasSubList = item.tokens.some((t2) => t2.type === "list");
901
+ if (hasSubList) {
902
+ const inlineTokens = item.tokens.filter((t2) => t2.type !== "list");
903
+ const subLists = item.tokens.filter((t2) => t2.type === "list");
904
+ if (inlineTokens.length > 0) {
905
+ const lineStart = text.length;
906
+ walkInline(inlineTokens, []);
907
+ const lineLen = text.length - lineStart;
908
+ emitLineStyles(lineStart, lineLen, [...blockLineStyles, { style: listStyle }]);
909
+ text += "\n";
910
+ }
911
+ const subIndent = Math.min(5, depth + 1);
912
+ walkBlock(subLists, [...blockLineStyles, { style: TextStyle.Indent, indentSize: subIndent }], depth + 1);
913
+ } else {
914
+ const lineStart = text.length;
915
+ walkInline(item.tokens, []);
916
+ const lineLen = text.length - lineStart;
917
+ emitLineStyles(lineStart, lineLen, [...blockLineStyles, { style: listStyle }]);
918
+ text += "\n";
919
+ }
920
+ }
921
+ }
922
+ break;
923
+ }
924
+ case "hr": {
925
+ text += "---\n";
926
+ break;
927
+ }
928
+ case "html": {
929
+ const t = tok;
930
+ text += t.text;
931
+ break;
932
+ }
933
+ case "text": {
934
+ const t = tok;
935
+ const lineStart = text.length;
936
+ if (t.tokens && t.tokens.length > 0) {
937
+ walkInline(t.tokens, []);
938
+ } else {
939
+ text += t.text;
940
+ }
941
+ const lineLen = text.length - lineStart;
942
+ emitLineStyles(lineStart, lineLen, blockLineStyles);
943
+ text += "\n";
944
+ break;
945
+ }
946
+ case "space": {
947
+ break;
948
+ }
949
+ default: {
950
+ const t = tok;
951
+ if (t.raw) text += t.raw;
952
+ break;
953
+ }
874
954
  }
875
- s.start -= startDelta;
876
- s.len -= endDelta - startDelta;
877
955
  }
878
- plainText = plainText.replace(escRegex, (_m, idxStr) => escapeMap[parseInt(idxStr, 10)]);
879
956
  }
880
- const finalLines = plainText.split("\n");
881
- let offset = 0;
882
- for (let i = 0; i < finalLines.length; i++) {
883
- const lineLen = finalLines[i].length;
884
- for (const ls of lineStyles) {
885
- if (ls.lineIndex === i && lineLen > 0) {
886
- if (ls.style === TextStyle.Indent) {
887
- allStyles.push({ start: offset, len: lineLen, st: TextStyle.Indent, indentSize: ls.indentSize });
888
- } else {
889
- allStyles.push({ start: offset, len: lineLen, st: ls.style });
890
- }
957
+ walkBlock(tokens, [], 0);
958
+ if (text.endsWith("\n")) {
959
+ text = text.slice(0, -1);
960
+ }
961
+ for (const slot of tagSlots) {
962
+ const phIdx = text.indexOf(slot.placeholder);
963
+ if (phIdx === -1) continue;
964
+ const inner = parseTextStyles(slot.innerMd);
965
+ const replacement = inner.text;
966
+ const phLen = slot.placeholder.length;
967
+ text = text.slice(0, phIdx) + replacement + text.slice(phIdx + phLen);
968
+ const delta = replacement.length - phLen;
969
+ for (const s of styles) {
970
+ if (s.start >= phIdx + phLen) {
971
+ s.start += delta;
972
+ } else if (s.start + s.len > phIdx) {
973
+ s.len += delta;
891
974
  }
892
975
  }
893
- offset += lineLen + 1;
976
+ const tagStyle = tagColorMap[slot.tagName];
977
+ if (tagStyle) {
978
+ styles.push({ start: phIdx, len: replacement.length, st: tagStyle });
979
+ }
980
+ for (const is of inner.styles) {
981
+ styles.push({ start: phIdx + is.start, len: is.len, st: is.st, ..."indentSize" in is ? { indentSize: is.indentSize } : {} });
982
+ }
894
983
  }
895
- return { text: plainText, styles: allStyles };
984
+ return { text, styles };
896
985
  }
897
986
  function parseBooleanFromEnv(name, fallback) {
898
987
  const raw = process.env[name]?.trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzca",
3
- "version": "0.1.35",
3
+ "version": "0.1.37",
4
4
  "description": "Open-source zca-compatible CLI to integrate Zalo with OpenClaw",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,6 +44,7 @@
44
44
  "@types/qrcode-terminal": "^0.12.2",
45
45
  "commander": "^14.0.3",
46
46
  "image-size": "^2.0.2",
47
+ "marked": "^17.0.4",
47
48
  "qrcode-terminal": "^0.12.0",
48
49
  "zca-js": "^2.0.4"
49
50
  },