skilldrop-cli 0.16.0 → 0.16.1
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 +1 -1
- package/packs/converters/skills/file-to-markdown/manifest.json +1 -1
- package/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +33 -2
- package/packs/converters/skills/md-to-docx/manifest.json +1 -1
- package/packs/converters/skills/md-to-docx/scripts/md_to_docx.py +3 -1
- package/packs/converters/skills/md-to-xlsx/SKILL.md +2 -1
- package/packs/converters/skills/md-to-xlsx/manifest.json +1 -1
- package/packs/converters/skills/md-to-xlsx/scripts/md_to_xlsx.py +18 -4
- package/packs/dev-team/skills/pre-merge-review/manifest.json +1 -1
- package/packs/dev-team/skills/pre-merge-review/scripts/gate.py +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilldrop-cli",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Portable AI-agent skills for the deliverables knowledge workers ship \u2014 diagrams, ADRs, PRDs, runbooks, decks, reviews \u2014 installable into Claude Code, Cursor, Kiro, and any IDE that takes custom instructions. One command per skill or per role-based pack.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Sanjay Ananthanarayan",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "file-to-markdown",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Convert a Word document, PowerPoint deck, Excel workbook, web page, CSV, JSON, text file or PDF into clean Markdown that keeps headings, lists, tables, links, bold and italic, slide titles and speaker notes, and reports everything it dropped (images, charts, comments, formulas, tracked changes), so the content can be read, reviewed or fed to another skill. Use when the user hands over a .docx, .pptx, .xlsx, .html, .csv, .json or .pdf and says \"read this\", \"turn this into markdown\", \"extract the text\", or wants it critiqued or briefed.",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -56,6 +56,7 @@ class Report:
|
|
|
56
56
|
def md_escape(text):
|
|
57
57
|
text = text.replace("\\", "\\\\")
|
|
58
58
|
text = re.sub(r"([*_`\[\]])", r"\\\1", text)
|
|
59
|
+
text = re.sub(r"<(?=[A-Za-z/!])", r"\\<", text) # literal <tag> text must not read as raw HTML
|
|
59
60
|
return text
|
|
60
61
|
|
|
61
62
|
|
|
@@ -330,7 +331,7 @@ def docx_to_md(path, rep):
|
|
|
330
331
|
if tcpr is not None and tcpr.find(W + "gridSpan") is not None:
|
|
331
332
|
row.extend([""] * (int(tcpr.find(W + "gridSpan").get(W + "val", "1")) - 1))
|
|
332
333
|
rows.append(row)
|
|
333
|
-
if
|
|
334
|
+
if len(list(tbl.iter(W + "tbl"))) > 1:
|
|
334
335
|
rep.notes.append("a nested table was flattened into its parent cell")
|
|
335
336
|
rep.keep("tables")
|
|
336
337
|
return md_table(rows)
|
|
@@ -339,6 +340,19 @@ def docx_to_md(path, rep):
|
|
|
339
340
|
return "".join((n.text or "") if n.tag == W + "t" else "\t" for n in p.iter()
|
|
340
341
|
if n.tag in (W + "t", W + "tab"))
|
|
341
342
|
|
|
343
|
+
def style_name(p):
|
|
344
|
+
ppr = p.find(W + "pPr")
|
|
345
|
+
ps = ppr.find(W + "pStyle") if ppr is not None else None
|
|
346
|
+
if ps is None:
|
|
347
|
+
return ""
|
|
348
|
+
sid = ps.get(W + "val", "")
|
|
349
|
+
return styles.get(sid, (sid.lower(), None))[0]
|
|
350
|
+
|
|
351
|
+
def code_text(p):
|
|
352
|
+
"""Text of a code paragraph: line breaks and tabs kept, no inline formatting or escaping."""
|
|
353
|
+
return "".join((n.text or "") if n.tag == W + "t" else "\t" if n.tag == W + "tab" else "\n"
|
|
354
|
+
for n in p.iter() if n.tag in (W + "t", W + "tab", W + "br", W + "cr"))
|
|
355
|
+
|
|
342
356
|
def run_size(p):
|
|
343
357
|
"""(largest font size in half-points, every text run bold?) for unstyled documents."""
|
|
344
358
|
sizes, bold = [0], True
|
|
@@ -402,7 +416,19 @@ def docx_to_md(path, rep):
|
|
|
402
416
|
if not text:
|
|
403
417
|
prev_list = False if not li else prev_list
|
|
404
418
|
continue
|
|
405
|
-
if lvl
|
|
419
|
+
sname = "" if lvl or li else style_name(el)
|
|
420
|
+
if re.match(r"(source ?code|code|html preformatted|preformatted text|macro text)$", sname):
|
|
421
|
+
# one code block may be one paragraph with line breaks or a run of one-line paragraphs
|
|
422
|
+
if blocks and blocks[-1][0] == "code":
|
|
423
|
+
blocks[-1] = ("code", blocks[-1][1] + "\n" + code_text(el))
|
|
424
|
+
else:
|
|
425
|
+
blocks.append(("code", code_text(el)))
|
|
426
|
+
rep.keep("code blocks")
|
|
427
|
+
prev_list = False
|
|
428
|
+
elif "quote" in sname or sname == "block text":
|
|
429
|
+
blocks.append(("block", "> " + text.replace("\n", "\n> ")))
|
|
430
|
+
prev_list = False
|
|
431
|
+
elif lvl:
|
|
406
432
|
rep.keep("headings")
|
|
407
433
|
blocks.append(("block", "#" * lvl + " " + text.replace(" \n", " ")))
|
|
408
434
|
prev_list = False
|
|
@@ -439,6 +465,11 @@ def docx_to_md(path, rep):
|
|
|
439
465
|
for k, (kind, text) in enumerate(blocks):
|
|
440
466
|
if k and not (kind == "list" and blocks[k - 1][0] == "list"):
|
|
441
467
|
out.append("")
|
|
468
|
+
if kind == "code":
|
|
469
|
+
fence = "```"
|
|
470
|
+
while fence in text:
|
|
471
|
+
fence += "`"
|
|
472
|
+
text = fence + "\n" + text + "\n" + fence
|
|
442
473
|
out.append(text)
|
|
443
474
|
return "\n".join(out) + "\n"
|
|
444
475
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "md-to-docx",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Turn a Markdown draft into a Word document (.docx) that someone outside engineering can open, edit and track changes in, with real Heading styles so the navigation pane and table of contents work, live hyperlinks, numbered and bulleted lists, tables, code blocks, embedded images and optional brand fonts and colours, and a warning for everything that could not be carried over. Use when the user wants a Markdown file as a Word doc, says \"send this as a .docx\", \"convert to Word\", \"legal needs it in Word\", or \"make this editable for the client\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -34,6 +34,8 @@ WARNINGS = []
|
|
|
34
34
|
|
|
35
35
|
def warn(line, msg):
|
|
36
36
|
where = "line %d: " % line if line else ""
|
|
37
|
+
if where + msg in WARNINGS: # a big table repeats one warning per cell; say it once
|
|
38
|
+
return
|
|
37
39
|
WARNINGS.append(where + msg)
|
|
38
40
|
print("WARN %s%s" % (where, msg), file=sys.stderr)
|
|
39
41
|
|
|
@@ -942,7 +944,7 @@ class Doc(object):
|
|
|
942
944
|
|
|
943
945
|
|
|
944
946
|
def static_parts(title):
|
|
945
|
-
now = datetime.datetime.
|
|
947
|
+
now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
946
948
|
ct = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
|
947
949
|
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
|
|
948
950
|
'<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
|
|
@@ -78,7 +78,8 @@ nothing. Formulas stay off unless the request says otherwise, noted as an `[assu
|
|
|
78
78
|
| Anything else, including `n/a`, `1.234,50`, `30/09/2026` | Text |
|
|
79
79
|
|
|
80
80
|
In Markdown cells, `**bold**`, `` `code` ``, links and images are reduced to their text, and
|
|
81
|
-
`<br>` becomes a line break inside a wrapped cell. JSON numbers and booleans keep their type
|
|
81
|
+
`<br>` becomes a line break inside a wrapped cell. JSON numbers and booleans keep their type,
|
|
82
|
+
except integers over 15 digits, which become text.
|
|
82
83
|
JSON strings stay text except ISO dates, because whoever produced the JSON chose a string.
|
|
83
84
|
|
|
84
85
|
## Useful references in this skill
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "md-to-xlsx",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Turn the tables in a Markdown file, a CSV or a JSON array into an Excel workbook (.xlsx) that finance, ops or a client can sort, filter and sum, with one sheet per table named from its heading, real numbers, percentages, currency and dates instead of text, a bold frozen header row with filters, sensible column widths, and formulas left inert unless asked for. Use when the user wants a spreadsheet from Markdown tables or data, says \"put this in Excel\", \"convert the CSV to xlsx\", \"give finance a spreadsheet\", or \"turn this JSON into a sheet\".",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": {
|
|
@@ -170,8 +170,11 @@ def type_text(s, formulas, stats, tz_flag, infer_numbers=True):
|
|
|
170
170
|
text = "0"
|
|
171
171
|
return Cell("n", text, fmt, width)
|
|
172
172
|
if RE_SCI.match(s):
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
val = float(s)
|
|
174
|
+
# 1e999 overflows to inf, which is not a number Excel can read: keep it as text
|
|
175
|
+
if val == val and val not in (float("inf"), float("-inf")):
|
|
176
|
+
stats["numbers"] += 1
|
|
177
|
+
return Cell("n", num_text(val), None, width)
|
|
175
178
|
if len(raw) > MAX_CELL:
|
|
176
179
|
stats["truncated"] += 1
|
|
177
180
|
s = s[:MAX_CELL]
|
|
@@ -189,6 +192,10 @@ def type_json(v, formulas, stats, tz_flag):
|
|
|
189
192
|
if v != v or v in (float("inf"), float("-inf")):
|
|
190
193
|
stats["text"] += 1
|
|
191
194
|
return Cell("s", str(v), None, len(str(v)))
|
|
195
|
+
if isinstance(v, int) and len(str(abs(v))) > 15:
|
|
196
|
+
# Excel keeps 15 significant digits; a longer integer is an ID, and rounding it is data loss
|
|
197
|
+
stats["text"] += 1
|
|
198
|
+
return Cell("s", str(v), None, len(str(v)))
|
|
192
199
|
stats["numbers"] += 1
|
|
193
200
|
return Cell("n", num_text(v), None, len(num_text(v)))
|
|
194
201
|
if isinstance(v, (dict, list)):
|
|
@@ -230,6 +237,9 @@ def split_row(line):
|
|
|
230
237
|
return cells
|
|
231
238
|
|
|
232
239
|
|
|
240
|
+
HTML_TAGS = [0] # tags removed by strip_md, reported once per file
|
|
241
|
+
|
|
242
|
+
|
|
233
243
|
def strip_md(s):
|
|
234
244
|
"""Plain text from a Markdown table cell."""
|
|
235
245
|
s = re.sub(r"<br\s*/?>", "\n", s, flags=re.I)
|
|
@@ -240,7 +250,8 @@ def strip_md(s):
|
|
|
240
250
|
s = re.sub(r"(?<![\w*])\*(?!\s)(.+?)(?<!\s)\*(?![\w*])", r"\1", s)
|
|
241
251
|
s = re.sub(r"(?<!\w)_(?!\s)(.+?)(?<!\s)_(?!\w)", r"\1", s)
|
|
242
252
|
s = re.sub(r"~~(.+?)~~", r"\1", s)
|
|
243
|
-
s = re.
|
|
253
|
+
s, n = re.subn(r"</?[A-Za-z][^>]*>", "", s)
|
|
254
|
+
HTML_TAGS[0] += n
|
|
244
255
|
s = re.sub(r"\\([\\`*_{}\[\]()#+\-.!|~])", r"\1", s)
|
|
245
256
|
return s
|
|
246
257
|
|
|
@@ -280,6 +291,9 @@ def read_markdown(text):
|
|
|
280
291
|
tables.append((heading, header, rows, start))
|
|
281
292
|
continue
|
|
282
293
|
i += 1
|
|
294
|
+
if HTML_TAGS[0]:
|
|
295
|
+
warn("%d HTML tag(s) removed from headings and cells (text between them kept); write a literal "
|
|
296
|
+
"<...> as <...> or in backticks to keep it" % HTML_TAGS[0])
|
|
283
297
|
return tables
|
|
284
298
|
|
|
285
299
|
|
|
@@ -533,7 +547,7 @@ def write_xlsx(out, sheets, title, formulas):
|
|
|
533
547
|
'<bookViews><workbookView activeTab="0"/></bookViews><sheets>%s</sheets>'
|
|
534
548
|
'<definedNames>%s</definedNames>%s</workbook>') % (
|
|
535
549
|
"".join(sheet_tags), "".join(names), '<calcPr fullCalcOnLoad="1"/>' if formulas else "")
|
|
536
|
-
now = datetime.datetime.
|
|
550
|
+
now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
537
551
|
core = ('<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
|
|
538
552
|
'<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" '
|
|
539
553
|
'xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" '
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pre-merge-review",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Gate an existing change for merge — run one skill that fires the whole production-readiness pass. First a deterministic mechanical gate (lint + typecheck + tests, via a script whose exit code decides), then the three-reviewer panel dispatched in parallel — devils-advocate (correctness), security-reviewer (exploitability), code-quality (craft) — then a single READY / NOT READY verdict. Use before opening a PR or merging, when you want the full review fleet run on a diff you already have. Do NOT use to implement a feature from a spec (that's feature-implement-loop) or to review a document (that's doc-critique).",
|
|
5
5
|
"entrypoint": "SKILL.md",
|
|
6
6
|
"deps": { "npm": [], "pip": [] },
|
|
@@ -112,7 +112,10 @@ def install_hook(root, cmd, config):
|
|
|
112
112
|
body += "\n" + _hook_block(cmd, config)
|
|
113
113
|
with open(path, "w", encoding="utf-8") as fh:
|
|
114
114
|
fh.write(body)
|
|
115
|
-
|
|
115
|
+
# chmod +x, not a fixed 0o755: add execute only where the umask already granted read, so a
|
|
116
|
+
# restrictive umask (077) keeps the hook private instead of being widened to world-readable.
|
|
117
|
+
mode = os.stat(path).st_mode
|
|
118
|
+
os.chmod(path, mode | ((mode & 0o444) >> 2))
|
|
116
119
|
print(f"gate: installed a blocking pre-commit hook -> {path}")
|
|
117
120
|
print(" a RED gate now blocks `git commit` (any tool). Bypass once with --no-verify; "
|
|
118
121
|
"wire CI required-checks for the un-bypassable backstop.")
|