@tasksai/install 0.1.11 → 0.1.13
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.
- package/package.json +5 -3
- package/runtime/server.py +250 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tasksai/install",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Shared TasksAI MCP installer CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"src/",
|
|
11
|
-
"runtime/",
|
|
11
|
+
"runtime/server.py",
|
|
12
|
+
"runtime/requirements.txt",
|
|
12
13
|
"README.md"
|
|
13
14
|
],
|
|
14
15
|
"keywords": [
|
|
@@ -29,6 +30,7 @@
|
|
|
29
30
|
"access": "public"
|
|
30
31
|
},
|
|
31
32
|
"scripts": {
|
|
32
|
-
"check": "node --check src/index.js"
|
|
33
|
+
"check": "node --check src/index.js",
|
|
34
|
+
"test": "python3 -m unittest discover -s tests"
|
|
33
35
|
}
|
|
34
36
|
}
|
package/runtime/server.py
CHANGED
|
@@ -92,7 +92,7 @@ if not LICENSE_KEY:
|
|
|
92
92
|
print("Find your key in your purchase confirmation email.", file=sys.stderr, flush=True)
|
|
93
93
|
sys.exit(1)
|
|
94
94
|
|
|
95
|
-
SERVER_VERSION = "2.
|
|
95
|
+
SERVER_VERSION = "2.3.0"
|
|
96
96
|
|
|
97
97
|
AUTH_HEADERS = {
|
|
98
98
|
"Authorization": f"Bearer {LICENSE_KEY}",
|
|
@@ -630,25 +630,202 @@ def unique_output_path(root, filename, overwrite=False):
|
|
|
630
630
|
raise RuntimeError("Could not choose a unique output filename.")
|
|
631
631
|
|
|
632
632
|
|
|
633
|
+
def markdown_table_row(line):
|
|
634
|
+
"""Return parsed table cells when a line looks like a Markdown table row."""
|
|
635
|
+
stripped = line.strip()
|
|
636
|
+
if "|" not in stripped:
|
|
637
|
+
return None
|
|
638
|
+
if stripped.startswith("|"):
|
|
639
|
+
stripped = stripped[1:]
|
|
640
|
+
if stripped.endswith("|"):
|
|
641
|
+
stripped = stripped[:-1]
|
|
642
|
+
cells = []
|
|
643
|
+
current = []
|
|
644
|
+
escaped = False
|
|
645
|
+
for char in stripped:
|
|
646
|
+
if escaped:
|
|
647
|
+
current.append(char)
|
|
648
|
+
escaped = False
|
|
649
|
+
elif char == "\\":
|
|
650
|
+
escaped = True
|
|
651
|
+
elif char == "|":
|
|
652
|
+
cells.append("".join(current).strip())
|
|
653
|
+
current = []
|
|
654
|
+
else:
|
|
655
|
+
current.append(char)
|
|
656
|
+
cells.append("".join(current).strip())
|
|
657
|
+
return cells if len(cells) > 1 else None
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
def markdown_table_alignments(separator_cells):
|
|
661
|
+
"""Return column alignments if cells are a valid Markdown table separator."""
|
|
662
|
+
alignments = []
|
|
663
|
+
for cell in separator_cells or []:
|
|
664
|
+
compact = cell.replace(" ", "")
|
|
665
|
+
if not re.match(r"^:?-{3,}:?$", compact):
|
|
666
|
+
return None
|
|
667
|
+
if compact.startswith(":") and compact.endswith(":"):
|
|
668
|
+
alignments.append("center")
|
|
669
|
+
elif compact.endswith(":"):
|
|
670
|
+
alignments.append("right")
|
|
671
|
+
else:
|
|
672
|
+
alignments.append("left")
|
|
673
|
+
return alignments
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
def is_markdown_table_start(lines, index):
|
|
677
|
+
"""True when lines[index:index+2] form a Markdown table header."""
|
|
678
|
+
if index + 1 >= len(lines):
|
|
679
|
+
return False
|
|
680
|
+
header = markdown_table_row(lines[index])
|
|
681
|
+
separator = markdown_table_row(lines[index + 1])
|
|
682
|
+
return bool(header and markdown_table_alignments(separator))
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def iter_inline_markdown(text):
|
|
686
|
+
"""Yield (text, marks) spans for common inline Markdown."""
|
|
687
|
+
token_re = re.compile(
|
|
688
|
+
r"(`[^`]+`|\*\*[^*]+\*\*|__[^_]+__|\*[^*\s][^*]*\*|_[^_\s][^_]*_|\[[^\]]+\]\([^)]+\))"
|
|
689
|
+
)
|
|
690
|
+
pos = 0
|
|
691
|
+
for match in token_re.finditer(text):
|
|
692
|
+
if match.start() > pos:
|
|
693
|
+
yield text[pos:match.start()], {}
|
|
694
|
+
token = match.group(0)
|
|
695
|
+
marks = {}
|
|
696
|
+
value = token
|
|
697
|
+
if token.startswith("**") and token.endswith("**"):
|
|
698
|
+
value = token[2:-2]
|
|
699
|
+
marks["bold"] = True
|
|
700
|
+
elif token.startswith("__") and token.endswith("__"):
|
|
701
|
+
value = token[2:-2]
|
|
702
|
+
marks["bold"] = True
|
|
703
|
+
elif token.startswith("`") and token.endswith("`"):
|
|
704
|
+
value = token[1:-1]
|
|
705
|
+
marks["code"] = True
|
|
706
|
+
elif token.startswith("["):
|
|
707
|
+
link = re.match(r"^\[([^\]]+)\]\(([^)]+)\)$", token)
|
|
708
|
+
if link:
|
|
709
|
+
value = link.group(1)
|
|
710
|
+
marks["link"] = link.group(2)
|
|
711
|
+
elif token[0] in {"*", "_"} and token.endswith(token[0]):
|
|
712
|
+
value = token[1:-1]
|
|
713
|
+
marks["italic"] = True
|
|
714
|
+
yield value, marks
|
|
715
|
+
pos = match.end()
|
|
716
|
+
if pos < len(text):
|
|
717
|
+
yield text[pos:], {}
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def add_hyperlink(paragraph, text, url):
|
|
721
|
+
"""Add a clickable hyperlink run when python-docx low-level APIs are available."""
|
|
722
|
+
from docx.oxml import OxmlElement
|
|
723
|
+
from docx.oxml.ns import qn
|
|
724
|
+
|
|
725
|
+
part = paragraph.part
|
|
726
|
+
relationship_id = part.relate_to(
|
|
727
|
+
url,
|
|
728
|
+
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
|
729
|
+
is_external=True,
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
hyperlink = OxmlElement("w:hyperlink")
|
|
733
|
+
hyperlink.set(qn("r:id"), relationship_id)
|
|
734
|
+
run_element = OxmlElement("w:r")
|
|
735
|
+
properties = OxmlElement("w:rPr")
|
|
736
|
+
|
|
737
|
+
color = OxmlElement("w:color")
|
|
738
|
+
color.set(qn("w:val"), "0563C1")
|
|
739
|
+
properties.append(color)
|
|
740
|
+
|
|
741
|
+
underline = OxmlElement("w:u")
|
|
742
|
+
underline.set(qn("w:val"), "single")
|
|
743
|
+
properties.append(underline)
|
|
744
|
+
|
|
745
|
+
run_element.append(properties)
|
|
746
|
+
text_element = OxmlElement("w:t")
|
|
747
|
+
text_element.text = text
|
|
748
|
+
run_element.append(text_element)
|
|
749
|
+
hyperlink.append(run_element)
|
|
750
|
+
paragraph._p.append(hyperlink)
|
|
751
|
+
|
|
752
|
+
|
|
633
753
|
def add_markdown_text(paragraph, text):
|
|
634
|
-
"""Add
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
paragraph.add_run(
|
|
754
|
+
"""Add common inline Markdown formatting to a python-docx paragraph."""
|
|
755
|
+
for value, marks in iter_inline_markdown(text):
|
|
756
|
+
if not value:
|
|
757
|
+
continue
|
|
758
|
+
if marks.get("link"):
|
|
759
|
+
try:
|
|
760
|
+
add_hyperlink(paragraph, value, marks["link"])
|
|
761
|
+
except Exception:
|
|
762
|
+
paragraph.add_run(f"{value} ({marks['link']})")
|
|
763
|
+
continue
|
|
764
|
+
run = paragraph.add_run(value)
|
|
765
|
+
if marks.get("bold"):
|
|
766
|
+
run.bold = True
|
|
767
|
+
if marks.get("italic"):
|
|
768
|
+
run.italic = True
|
|
769
|
+
if marks.get("code"):
|
|
770
|
+
run.font.name = "Courier New"
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
def add_horizontal_rule(paragraph):
|
|
774
|
+
"""Render a horizontal Markdown rule as a Word paragraph border."""
|
|
775
|
+
from docx.oxml import OxmlElement
|
|
776
|
+
from docx.oxml.ns import qn
|
|
777
|
+
|
|
778
|
+
p_pr = paragraph._p.get_or_add_pPr()
|
|
779
|
+
borders = p_pr.first_child_found_in("w:pBdr")
|
|
780
|
+
if borders is None:
|
|
781
|
+
borders = OxmlElement("w:pBdr")
|
|
782
|
+
p_pr.append(borders)
|
|
783
|
+
bottom = OxmlElement("w:bottom")
|
|
784
|
+
bottom.set(qn("w:val"), "single")
|
|
785
|
+
bottom.set(qn("w:sz"), "6")
|
|
786
|
+
bottom.set(qn("w:space"), "1")
|
|
787
|
+
bottom.set(qn("w:color"), "BFBFBF")
|
|
788
|
+
borders.append(bottom)
|
|
789
|
+
|
|
790
|
+
|
|
791
|
+
def add_markdown_table(doc, rows, alignments):
|
|
792
|
+
"""Render a Markdown pipe table as a native Word table."""
|
|
793
|
+
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
|
794
|
+
from docx.shared import Pt
|
|
795
|
+
|
|
796
|
+
if not rows:
|
|
797
|
+
return
|
|
798
|
+
|
|
799
|
+
width = max(len(row) for row in rows)
|
|
800
|
+
normalized = [row + [""] * (width - len(row)) for row in rows]
|
|
801
|
+
table = doc.add_table(rows=len(normalized), cols=width)
|
|
802
|
+
table.style = "Table Grid"
|
|
803
|
+
table.autofit = True
|
|
804
|
+
|
|
805
|
+
for row_index, row in enumerate(normalized):
|
|
806
|
+
for column_index, value in enumerate(row):
|
|
807
|
+
cell = table.cell(row_index, column_index)
|
|
808
|
+
paragraph = cell.paragraphs[0]
|
|
809
|
+
add_markdown_text(paragraph, value)
|
|
810
|
+
if row_index == 0:
|
|
811
|
+
for run in paragraph.runs:
|
|
812
|
+
run.bold = True
|
|
813
|
+
alignment = alignments[column_index] if column_index < len(alignments) else "left"
|
|
814
|
+
if alignment == "right":
|
|
815
|
+
paragraph.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
816
|
+
elif alignment == "center":
|
|
817
|
+
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
818
|
+
for run in paragraph.runs:
|
|
819
|
+
run.font.size = Pt(8 if width > 6 else 9)
|
|
820
|
+
|
|
821
|
+
doc.add_paragraph()
|
|
645
822
|
|
|
646
823
|
|
|
647
824
|
def write_docx(path, title, content_markdown, product_name):
|
|
648
825
|
"""Write a simple, polished DOCX file locally."""
|
|
649
826
|
try:
|
|
650
827
|
from docx import Document
|
|
651
|
-
from docx.shared import Pt
|
|
828
|
+
from docx.shared import Inches, Pt
|
|
652
829
|
except Exception as exc:
|
|
653
830
|
raise RuntimeError(
|
|
654
831
|
"python-docx is not installed. Re-run the official installer or install python-docx in the MCP runtime environment."
|
|
@@ -669,33 +846,86 @@ def write_docx(path, title, content_markdown, product_name):
|
|
|
669
846
|
if text:
|
|
670
847
|
add_markdown_text(doc.add_paragraph(), text)
|
|
671
848
|
|
|
672
|
-
|
|
849
|
+
in_code_block = False
|
|
850
|
+
index = 0
|
|
851
|
+
while index < len(lines):
|
|
852
|
+
raw_line = lines[index]
|
|
673
853
|
line = raw_line.rstrip()
|
|
674
854
|
stripped = line.strip()
|
|
855
|
+
if stripped.startswith("```"):
|
|
856
|
+
flush_paragraph()
|
|
857
|
+
in_code_block = not in_code_block
|
|
858
|
+
index += 1
|
|
859
|
+
continue
|
|
860
|
+
if in_code_block:
|
|
861
|
+
para = doc.add_paragraph()
|
|
862
|
+
run = para.add_run(line)
|
|
863
|
+
run.font.name = "Courier New"
|
|
864
|
+
run.font.size = Pt(9)
|
|
865
|
+
index += 1
|
|
866
|
+
continue
|
|
675
867
|
if not stripped:
|
|
676
868
|
flush_paragraph()
|
|
869
|
+
index += 1
|
|
677
870
|
continue
|
|
678
871
|
|
|
679
872
|
heading = re.match(r"^(#{1,4})\s+(.+)$", stripped)
|
|
680
|
-
bullet = re.match(r"^[
|
|
681
|
-
numbered = re.match(r"
|
|
873
|
+
bullet = re.match(r"^(\s*)[-*+]\s+(.+)$", line)
|
|
874
|
+
numbered = re.match(r"^(\s*)\d+\.\s+(.+)$", line)
|
|
875
|
+
quote = re.match(r"^>\s?(.+)$", stripped)
|
|
682
876
|
|
|
683
|
-
if
|
|
877
|
+
if is_markdown_table_start(lines, index):
|
|
878
|
+
flush_paragraph()
|
|
879
|
+
header = markdown_table_row(lines[index])
|
|
880
|
+
alignments = markdown_table_alignments(markdown_table_row(lines[index + 1]))
|
|
881
|
+
table_rows = [header]
|
|
882
|
+
index += 2
|
|
883
|
+
while index < len(lines):
|
|
884
|
+
row = markdown_table_row(lines[index])
|
|
885
|
+
if not row:
|
|
886
|
+
break
|
|
887
|
+
table_rows.append(row)
|
|
888
|
+
index += 1
|
|
889
|
+
add_markdown_table(doc, table_rows, alignments)
|
|
890
|
+
continue
|
|
891
|
+
elif heading:
|
|
684
892
|
flush_paragraph()
|
|
685
893
|
doc.add_heading(heading.group(2).strip(), level=min(len(heading.group(1)), 4))
|
|
894
|
+
index += 1
|
|
686
895
|
elif bullet:
|
|
687
896
|
flush_paragraph()
|
|
688
897
|
para = doc.add_paragraph(style="List Bullet")
|
|
689
|
-
|
|
898
|
+
indent_level = min(len(bullet.group(1).replace("\t", " ")) // 2, 4)
|
|
899
|
+
para.paragraph_format.left_indent = Inches(0.25 * indent_level)
|
|
900
|
+
text = bullet.group(2).strip()
|
|
901
|
+
checkbox = re.match(r"^\[([ xX])\]\s+(.+)$", text)
|
|
902
|
+
if checkbox:
|
|
903
|
+
text = f"[{checkbox.group(1).lower()}] {checkbox.group(2).strip()}"
|
|
904
|
+
add_markdown_text(para, text)
|
|
905
|
+
index += 1
|
|
690
906
|
elif numbered:
|
|
691
907
|
flush_paragraph()
|
|
692
908
|
para = doc.add_paragraph(style="List Number")
|
|
693
|
-
|
|
909
|
+
indent_level = min(len(numbered.group(1).replace("\t", " ")) // 2, 4)
|
|
910
|
+
para.paragraph_format.left_indent = Inches(0.25 * indent_level)
|
|
911
|
+
add_markdown_text(para, numbered.group(2).strip())
|
|
912
|
+
index += 1
|
|
913
|
+
elif quote:
|
|
914
|
+
flush_paragraph()
|
|
915
|
+
try:
|
|
916
|
+
para = doc.add_paragraph(style="Quote")
|
|
917
|
+
except Exception:
|
|
918
|
+
para = doc.add_paragraph()
|
|
919
|
+
para.paragraph_format.left_indent = Inches(0.25)
|
|
920
|
+
add_markdown_text(para, quote.group(1).strip())
|
|
921
|
+
index += 1
|
|
694
922
|
elif stripped in {"---", "***", "___"}:
|
|
695
923
|
flush_paragraph()
|
|
696
|
-
doc.add_paragraph()
|
|
924
|
+
add_horizontal_rule(doc.add_paragraph())
|
|
925
|
+
index += 1
|
|
697
926
|
else:
|
|
698
927
|
paragraph_buffer.append(stripped)
|
|
928
|
+
index += 1
|
|
699
929
|
|
|
700
930
|
flush_paragraph()
|
|
701
931
|
|