openzca 0.1.36 → 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 -200
  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,110 +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
- const codeIndentMatch = line.match(/^(\s+)(.*)$/);
740
- if (codeIndentMatch) {
741
- const codeIndent = Math.min(5, Math.max(1, Math.floor(codeIndentMatch[1].length / 2)));
742
- lineStyles.push({ lineIndex: i, style: TextStyle.Indent, indentSize: codeIndent });
743
- processedLines.push(codeIndentMatch[2]);
744
- } else {
745
- processedLines.push(line);
746
- }
747
- continue;
748
- }
749
- const headingMatch = line.match(/^(#{1,4})\s(.*)$/);
750
- if (headingMatch) {
751
- const level = headingMatch[1].length;
752
- lineStyles.push({ lineIndex: i, style: TextStyle.Bold });
753
- if (level === 1) lineStyles.push({ lineIndex: i, style: TextStyle.Big });
754
- else if (level === 3 || level === 4) lineStyles.push({ lineIndex: i, style: TextStyle.Small });
755
- processedLines.push(headingMatch[2]);
756
- continue;
757
- }
758
- const bqMatch = line.match(/^(>+)\s?(.*)$/);
759
- if (bqMatch) {
760
- lineStyles.push({ lineIndex: i, style: TextStyle.Indent, indentSize: Math.min(5, bqMatch[1].length) });
761
- line = bqMatch[2];
762
- }
763
- const indentContentMatch = line.match(/^(\s+)(.*)$/);
764
- let indentLevel = 0;
765
- let content = line;
766
- if (indentContentMatch) {
767
- indentLevel = Math.min(5, Math.max(1, Math.floor(indentContentMatch[1].length / 2)));
768
- content = indentContentMatch[2];
769
- }
770
- if (/^[-*+]\s\[[ xX]\]\s/.test(content)) {
771
- if (indentLevel > 0) {
772
- lineStyles.push({ lineIndex: i, style: TextStyle.Indent, indentSize: indentLevel });
773
- }
774
- processedLines.push(content);
775
- continue;
776
- }
777
- const olMatch = content.match(/^(\d+)\.\s(.*)$/);
778
- if (olMatch) {
779
- if (indentLevel > 0) {
780
- lineStyles.push({ lineIndex: i, style: TextStyle.Indent, indentSize: indentLevel });
781
- }
782
- lineStyles.push({ lineIndex: i, style: TextStyle.OrderedList });
783
- processedLines.push(olMatch[2]);
784
- continue;
785
- }
786
- const ulMatch = content.match(/^[-*+]\s(.*)$/);
787
- if (ulMatch) {
788
- if (indentLevel > 0) {
789
- lineStyles.push({ lineIndex: i, style: TextStyle.Indent, indentSize: indentLevel });
790
- }
791
- lineStyles.push({ lineIndex: i, style: TextStyle.UnorderedList });
792
- processedLines.push(ulMatch[1]);
793
- continue;
794
- }
795
- if (indentLevel > 0) {
796
- lineStyles.push({ lineIndex: i, style: TextStyle.Indent, indentSize: indentLevel });
797
- processedLines.push(content);
798
- continue;
799
- }
800
- processedLines.push(line);
801
- }
802
- for (const ci of codeLineIndices) {
803
- if (ci < processedLines.length) {
804
- processedLines[ci] = processedLines[ci].replace(/[*_~{}]/g, (ch) => {
805
- const idx = escapeMap.length;
806
- escapeMap.push(ch);
807
- return `${idx}`;
808
- });
809
- }
810
- }
811
- const inlineInput = processedLines.join("\n");
812
- const colorMap = {
710
+ const tagColorMap = {
813
711
  red: TextStyle.Red,
814
712
  orange: TextStyle.Orange,
815
713
  yellow: TextStyle.Yellow,
@@ -818,113 +716,272 @@ function parseTextStyles(input) {
818
716
  big: TextStyle.Big,
819
717
  underline: TextStyle.Underline
820
718
  };
821
- const tagNames = Object.keys(colorMap).join("|");
822
- const markers = [
823
- // Tags first so inner markdown markers are preserved for subsequent passes
824
- { pattern: new RegExp(`\\{(${tagNames})\\}(.+?)\\{/\\1\\}`, "g"), style: null },
825
- // *** = bold + italic
826
- { pattern: /\*\*\*(.+?)\*\*\*/g, style: TextStyle.Bold, extraStyles: [TextStyle.Italic] },
827
- // ** and __ = bold (standard markdown; __ requires word boundaries)
828
- { pattern: /\*\*(.+?)\*\*/g, style: TextStyle.Bold },
829
- { pattern: /(?<!\w)__(.+?)__(?!\w)/g, style: TextStyle.Bold },
830
- // * and _ = italic (_ requires word boundaries to avoid snake_case)
831
- { pattern: /\*(.+?)\*/g, style: TextStyle.Italic },
832
- { pattern: /(?<!\w)_(.+?)_(?!\w)/g, style: TextStyle.Italic },
833
- // ~~ = strikethrough
834
- { pattern: /~~(.+?)~~/g, style: TextStyle.StrikeThrough }
835
- ];
836
- let segments = [{ text: inlineInput, styles: [] }];
837
- for (const marker of markers) {
838
- const next = [];
839
- for (const seg of segments) {
840
- let lastIndex = 0;
841
- const regex = new RegExp(marker.pattern.source, marker.pattern.flags);
842
- let m;
843
- while ((m = regex.exec(seg.text)) !== null) {
844
- if (m.index > lastIndex) {
845
- next.push({ text: seg.text.slice(lastIndex, m.index), styles: [...seg.styles] });
846
- }
847
- const isTagPattern = marker.style === null;
848
- const innerText = isTagPattern ? m[2] : m[1];
849
- const resolvedStyle = isTagPattern ? colorMap[m[1]] : marker.style;
850
- const combined = [...seg.styles, resolvedStyle];
851
- if (marker.extraStyles) {
852
- combined.push(...marker.extraStyles);
853
- }
854
- next.push({ text: innerText, styles: combined });
855
- lastIndex = regex.lastIndex;
856
- }
857
- if (lastIndex < seg.text.length) {
858
- next.push({ text: seg.text.slice(lastIndex), styles: [...seg.styles] });
859
- } else if (lastIndex === 0) {
860
- next.push(seg);
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 });
861
738
  }
862
739
  }
863
- segments = next;
864
- }
865
- let plainText = "";
866
- for (const seg of segments) {
867
- const start = plainText.length;
868
- plainText += seg.text;
869
- for (const st of seg.styles) {
870
- allStyles.push({ start, len: seg.text.length, st });
871
- }
872
740
  }
873
- const orphanRegex = /\*([^*\n]+?)\*/g;
874
- const orphanMatches = [...plainText.matchAll(orphanRegex)];
875
- for (let oi = orphanMatches.length - 1; oi >= 0; oi--) {
876
- const om = orphanMatches[oi];
877
- const openPos = om.index;
878
- const content = om[1];
879
- const closePos = openPos + content.length + 1;
880
- allStyles.push({ start: openPos + 1, len: content.length, st: TextStyle.Italic });
881
- plainText = plainText.slice(0, closePos) + plainText.slice(closePos + 1);
882
- plainText = plainText.slice(0, openPos) + plainText.slice(openPos + 1);
883
- for (const s of allStyles) {
884
- if (s.start > closePos) s.start--;
885
- else if (s.start + s.len > closePos) s.len--;
886
- if (s.start > openPos) s.start--;
887
- else if (s.start + s.len > openPos) s.len--;
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;
759
+ }
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;
837
+ }
838
+ }
888
839
  }
889
840
  }
890
- if (escapeMap.length > 0) {
891
- const escRegex = /\x01(\d+)\x02/g;
892
- const shifts = [];
893
- let cumDelta = 0;
894
- for (const m of plainText.matchAll(escRegex)) {
895
- const idx = parseInt(m[1], 10);
896
- cumDelta += m[0].length - escapeMap[idx].length;
897
- shifts.push({ pos: m.index + m[0].length, delta: cumDelta });
898
- }
899
- for (const s of allStyles) {
900
- let startDelta = 0;
901
- let endDelta = 0;
902
- const end = s.start + s.len;
903
- for (const sh of shifts) {
904
- if (sh.pos <= s.start) startDelta = sh.delta;
905
- 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
+ }
906
954
  }
907
- s.start -= startDelta;
908
- s.len -= endDelta - startDelta;
909
955
  }
910
- plainText = plainText.replace(escRegex, (_m, idxStr) => escapeMap[parseInt(idxStr, 10)]);
911
956
  }
912
- const finalLines = plainText.split("\n");
913
- let offset = 0;
914
- for (let i = 0; i < finalLines.length; i++) {
915
- const lineLen = finalLines[i].length;
916
- for (const ls of lineStyles) {
917
- if (ls.lineIndex === i && lineLen > 0) {
918
- if (ls.style === TextStyle.Indent) {
919
- allStyles.push({ start: offset, len: lineLen, st: TextStyle.Indent, indentSize: ls.indentSize });
920
- } else {
921
- allStyles.push({ start: offset, len: lineLen, st: ls.style });
922
- }
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;
923
974
  }
924
975
  }
925
- 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
+ }
926
983
  }
927
- return { text: plainText, styles: allStyles };
984
+ return { text, styles };
928
985
  }
929
986
  function parseBooleanFromEnv(name, fallback) {
930
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.36",
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
  },