skilldrop-cli 0.16.35 → 0.16.36
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/README.md +1 -0
- package/agents/README.md +4 -1
- package/agents/docs-reviewer.md +47 -0
- package/agents/finding-adjudicator.md +49 -0
- package/agents/test-reviewer.md +52 -0
- package/catalogue.json +17 -7
- package/model-routing.json +40 -0
- package/package.json +1 -1
- package/packs/ai-engineering/skills/agent-config-review/SKILL.md +116 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/eval_queries.json +12 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/evals.json +75 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/db-migrator.md +8 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/agents/reviewer.md +9 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.json +48 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.claude/settings.local.json +5 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/.mcp.json +22 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/1/package.json +12 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/2/.cursor/mcp.json +17 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/2/config.toml +21 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/my-user-settings.json +11 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/agents/release-notes.md +13 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.claude/settings.json +39 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/.mcp.json +18 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/4/quillstack/package.json +14 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/.github/workflows/codex-triage.yml +29 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/AGENTS.md +16 -0
- package/packs/ai-engineering/skills/agent-config-review/evals/files/5/codex/config.toml +34 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.claude/settings.json +22 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/inputs/tidewater-ingest/.mcp.json +9 -0
- package/packs/ai-engineering/skills/agent-config-review/examples/python-repo-tighten.md +81 -0
- package/packs/ai-engineering/skills/agent-config-review/manifest.json +60 -0
- package/packs/ai-engineering/skills/agent-config-review/reference.md +205 -0
- package/packs/ai-engineering/skills/agent-config-review/scripts/agent_config_scan.py +989 -0
- package/packs/ai-engineering/skills/agent-incident-response/SKILL.md +175 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/eval_queries.json +14 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/evals.json +107 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/agent-session-excerpt.txt +11 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/ci-build-log.txt +24 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/1/incident-notes.md +19 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/mcp-config.json +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/2/session-transcript.txt +8 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/agent-session.log +70 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/4/infra/staging-db.tf +25 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/job.yaml +19 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/jobs/nightly-triage/run.log +13 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/5/usage-export-2026-10-20.csv +13 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/.mcp.json +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/git-log.txt +12 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync-excerpt.txt +30 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/notesync.log +15 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/6/ssh-config.txt +16 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/.github/workflows/ci.yml +28 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/SECURITY.md +8 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/issue-479.md +35 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/package.json +15 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/pr-482.diff +20 -0
- package/packs/ai-engineering/skills/agent-incident-response/evals/files/7/run-log.txt +30 -0
- package/packs/ai-engineering/skills/agent-incident-response/examples/injected-issue-comment.md +57 -0
- package/packs/ai-engineering/skills/agent-incident-response/manifest.json +55 -0
- package/packs/ai-engineering/skills/agent-incident-response/reference.md +152 -0
- package/packs/ai-engineering/skills/agent-incident-response/scripts/find_secret_exposure.py +239 -0
- package/packs/ai-engineering/skills/agent-incident-response/scripts/usage_split.py +194 -0
- package/packs/converters/skills/file-to-markdown/SKILL.md +2 -1
- package/packs/converters/skills/file-to-markdown/evals/eval_queries.json +4 -0
- package/packs/converters/skills/file-to-markdown/manifest.json +2 -2
- package/packs/converters/skills/file-to-markdown/scripts/to_markdown.py +253 -3
- package/packs/converters/skills/md-to-confluence/SKILL.md +139 -0
- package/packs/converters/skills/md-to-confluence/evals/eval_queries.json +42 -0
- package/packs/converters/skills/md-to-confluence/evals/evals.json +89 -0
- package/packs/converters/skills/md-to-confluence/evals/files/1/img/latency-dashboard.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/1/payments-runbook.md +60 -0
- package/packs/converters/skills/md-to-confluence/evals/files/2/deploy-checklist.md +33 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/img/after/screen.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/img/before/screen.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/3/release-notes-4.2.md +49 -0
- package/packs/converters/skills/md-to-confluence/evals/files/4/docs/img/topology.png +0 -0
- package/packs/converters/skills/md-to-confluence/evals/files/4/docs/ingest-pipeline-design.md +65 -0
- package/packs/converters/skills/md-to-confluence/evals/files/5/support/troubleshooting-sso.md +57 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/img/queue-depth.png +0 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.fixed.md +21 -0
- package/packs/converters/skills/md-to-confluence/examples/inputs/queue-runbook.md +21 -0
- package/packs/converters/skills/md-to-confluence/examples/queue-runbook.md +57 -0
- package/packs/converters/skills/md-to-confluence/manifest.json +52 -0
- package/packs/converters/skills/md-to-confluence/reference.md +92 -0
- package/packs/converters/skills/md-to-confluence/scripts/md_to_confluence.py +821 -0
- package/packs/converters/skills/md-to-pptx/SKILL.md +131 -0
- package/packs/converters/skills/md-to-pptx/evals/eval_queries.json +12 -0
- package/packs/converters/skills/md-to-pptx/evals/evals.json +84 -0
- package/packs/converters/skills/md-to-pptx/evals/files/1/img/defect-burndown.png +0 -0
- package/packs/converters/skills/md-to-pptx/evals/files/1/status-report.md +75 -0
- package/packs/converters/skills/md-to-pptx/evals/files/3/brand.json +12 -0
- package/packs/converters/skills/md-to-pptx/evals/files/3/night-shift-induction.md +53 -0
- package/packs/converters/skills/md-to-pptx/evals/files/4/charts/ontime.png +0 -0
- package/packs/converters/skills/md-to-pptx/evals/files/4/qbr-q3.md +85 -0
- package/packs/converters/skills/md-to-pptx/evals/files/5/vendor-sync-notes.md +28 -0
- package/packs/converters/skills/md-to-pptx/examples/support-weekly-input.md +48 -0
- package/packs/converters/skills/md-to-pptx/examples/support-weekly.md +75 -0
- package/packs/converters/skills/md-to-pptx/examples/tickets-by-week.png +0 -0
- package/packs/converters/skills/md-to-pptx/manifest.json +49 -0
- package/packs/converters/skills/md-to-pptx/reference.md +139 -0
- package/packs/converters/skills/md-to-pptx/scripts/md_to_pptx.py +1841 -0
- package/packs/dev-team/loops/modernize/LOOP.md +124 -0
- package/packs/dev-team/loops/modernize/evals/eval_queries.json +9 -0
- package/packs/dev-team/loops/modernize/evals/evals.json +27 -0
- package/packs/dev-team/loops/modernize/loop.json +60 -0
- package/packs/dev-team/skills/behaviour-equivalence/SKILL.md +155 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/eval_queries.json +11 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/evals.json +76 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/cases.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/new.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/notes.md +15 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/old_rerun.jsonl +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v1.py +45 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/pricing_v2.py +65 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/1/rules.json +10 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/3/diff-summary.txt +16 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/diff.txt +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py2.py +16 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/fee_calc_py3.py +24 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/input.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/new_output.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/4/old_output.csv +13 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/TEAM_NOTES.md +19 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/normalisation.yaml +12 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/new_run.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run1.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/evals/files/5/recordings/old_run2.jsonl +8 -0
- package/packs/dev-team/skills/behaviour-equivalence/examples/delivery-date.md +78 -0
- package/packs/dev-team/skills/behaviour-equivalence/manifest.json +33 -0
- package/packs/dev-team/skills/behaviour-equivalence/reference.md +97 -0
- package/packs/dev-team/skills/behaviour-equivalence/scripts/golden_diff.py +524 -0
- package/packs/dev-team/skills/behaviour-equivalence/templates/accepted.json +10 -0
- package/packs/dev-team/skills/behaviour-equivalence/templates/rules.json +7 -0
- package/packs/frontend/pack.json +55 -0
- package/packs/frontend/skills/rendering-strategy/SKILL.md +149 -0
- package/packs/frontend/skills/rendering-strategy/evals/eval_queries.json +38 -0
- package/packs/frontend/skills/rendering-strategy/evals/evals.json +77 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/1/fernhill-app.md +40 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/3/routes.md +33 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/build-notes.txt +5 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/nuxt.config.ts +19 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/pages/recipes/[slug].vue +37 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/4/routes.md +15 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/routes.txt +18 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/hooks.server.ts +14 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/(marketing)/pricing/+page.server.ts +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/src/routes/+layout.server.ts +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/svelte.config.js +10 -0
- package/packs/frontend/skills/rendering-strategy/evals/files/5/traffic.md +18 -0
- package/packs/frontend/skills/rendering-strategy/examples/saas-site.md +42 -0
- package/packs/frontend/skills/rendering-strategy/manifest.json +50 -0
- package/packs/frontend/skills/rendering-strategy/reference.md +116 -0
- package/packs/frontend/skills/rendering-strategy/scripts/render_load.py +181 -0
- package/packs/frontend/skills/web-performance-review/SKILL.md +106 -0
- package/packs/frontend/skills/web-performance-review/evals/eval_queries.json +38 -0
- package/packs/frontend/skills/web-performance-review/evals/evals.json +76 -0
- package/packs/frontend/skills/web-performance-review/evals/files/1/psi.json +1065 -0
- package/packs/frontend/skills/web-performance-review/evals/files/2/after.json +359 -0
- package/packs/frontend/skills/web-performance-review/evals/files/2/before.json +375 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/psi-pdp-mobile-2026-10-19.json +171 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-attribution-oct14-19.csv +6 -0
- package/packs/frontend/skills/web-performance-review/evals/files/4/rum-inp-daily.csv +15 -0
- package/packs/frontend/skills/web-performance-review/evals/files/5/crux-home-phone.json +35 -0
- package/packs/frontend/skills/web-performance-review/evals/files/5/lighthouse-home-mobile.json +173 -0
- package/packs/frontend/skills/web-performance-review/examples/quayside-article-lhr.json +480 -0
- package/packs/frontend/skills/web-performance-review/examples/quayside-article.md +103 -0
- package/packs/frontend/skills/web-performance-review/manifest.json +48 -0
- package/packs/frontend/skills/web-performance-review/reference.md +105 -0
- package/packs/frontend/skills/web-performance-review/scripts/lh_summary.py +743 -0
- package/packs/product-manager/skills/lean-canvas/SKILL.md +162 -0
- package/packs/product-manager/skills/lean-canvas/evals/eval_queries.json +42 -0
- package/packs/product-manager/skills/lean-canvas/evals/evals.json +82 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/1/pawpass-notes.md +54 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/3/lunchloop-pitch.md +30 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/4/clubkit-notes.md +44 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/cost-model.csv +5 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/proposal.md +40 -0
- package/packs/product-manager/skills/lean-canvas/evals/files/5/stakeholder-emails.txt +18 -0
- package/packs/product-manager/skills/lean-canvas/examples/bike-repair.md +72 -0
- package/packs/product-manager/skills/lean-canvas/manifest.json +34 -0
- package/packs/product-manager/skills/lean-canvas/reference.md +102 -0
- package/packs/product-manager/skills/lean-canvas/scripts/unit_economics.py +211 -0
- package/packs/solution-architect/skills/api-contract-draft/SKILL.md +2 -2
- package/packs/solution-architect/skills/api-contract-draft/manifest.json +28 -6
- package/packs/solution-architect/skills/event-contract-draft/SKILL.md +167 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/eval_queries.json +38 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/evals.json +76 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/1/order-events-brief.md +45 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/4/NOTES.md +23 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/4/schemas/shipment-update.json +40 -0
- package/packs/solution-architect/skills/event-contract-draft/evals/files/5/invoice.example.json +25 -0
- package/packs/solution-architect/skills/event-contract-draft/examples/order-service.asyncapi.json +246 -0
- package/packs/solution-architect/skills/event-contract-draft/examples/order-service.md +92 -0
- package/packs/solution-architect/skills/event-contract-draft/manifest.json +25 -0
- package/packs/solution-architect/skills/event-contract-draft/reference.md +113 -0
- package/packs/solution-architect/skills/event-contract-draft/scripts/check_asyncapi.py +502 -0
- package/packs/solution-architect/skills/event-contract-draft/templates/asyncapi-skeleton.json +144 -0
- package/packs/sre-oncall/skills/slo-spec/SKILL.md +161 -0
- package/packs/sre-oncall/skills/slo-spec/evals/eval_queries.json +38 -0
- package/packs/sre-oncall/skills/slo-spec/evals/evals.json +75 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/1/order-service.md +51 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/3/search-slo-status.md +30 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/alerts/payments-api.rules.yaml +16 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/docs/dependencies.md +8 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/4/metrics/summary-90d.md +51 -0
- package/packs/sre-oncall/skills/slo-spec/evals/files/5/ops-notes.md +34 -0
- package/packs/sre-oncall/skills/slo-spec/examples/ticket-booking.md +88 -0
- package/packs/sre-oncall/skills/slo-spec/manifest.json +34 -0
- package/packs/sre-oncall/skills/slo-spec/reference.md +113 -0
- package/packs/sre-oncall/skills/slo-spec/scripts/slo_math.py +449 -0
- package/packs/sre-oncall/skills/slo-spec/templates/slo-spec.md +35 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Find where a leaked key appears in a repository's git history and in log files.
|
|
3
|
+
|
|
4
|
+
Read-only. Never prints the key: every match is shown masked (first 8 characters,
|
|
5
|
+
an ellipsis, the last 4, and the length). Identify the key by a prefix you already
|
|
6
|
+
know (--prefix, optionally with --suffix for the last characters) or by piping the
|
|
7
|
+
full key on stdin (--key-stdin) so it never lands in shell history.
|
|
8
|
+
|
|
9
|
+
python3 find_secret_exposure.py --prefix sk-live-AbCdEf12 --repo . --log ci.txt
|
|
10
|
+
printf '%s' "$LEAKED" | python3 find_secret_exposure.py --key-stdin --repo .
|
|
11
|
+
|
|
12
|
+
Reports: commits that added or removed it (author and commit time, UTC), whether the
|
|
13
|
+
current tree and working copy still hold it, which branches and tags still reach the
|
|
14
|
+
adding commits, commits reachable only from the local reflog, and every log line that
|
|
15
|
+
holds it with the line's own timestamp. Writes only the -o path when given.
|
|
16
|
+
"""
|
|
17
|
+
import argparse
|
|
18
|
+
import datetime as dt
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import subprocess
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
TOKEN_CHARS = r"[A-Za-z0-9_\-+/=.]"
|
|
25
|
+
STAMP = re.compile(
|
|
26
|
+
r"(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}:\d{2})(?:\.\d+)?(Z|[+-]\d{2}:?\d{2})?"
|
|
27
|
+
)
|
|
28
|
+
MIN_PREFIX = 10
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def mask(token):
|
|
32
|
+
if len(token) <= 12:
|
|
33
|
+
return token[:4] + "…" + " (len %d)" % len(token)
|
|
34
|
+
return "%s…%s (len %d)" % (token[:8], token[-4:], len(token))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Needle:
|
|
38
|
+
def __init__(self, prefix=None, suffix=None, full=None):
|
|
39
|
+
self.full = full
|
|
40
|
+
self.prefix = full[:12] if full else prefix
|
|
41
|
+
self.suffix = suffix
|
|
42
|
+
self.rx = re.compile(re.escape(self.prefix) + TOKEN_CHARS + "*")
|
|
43
|
+
self.seen = set()
|
|
44
|
+
|
|
45
|
+
def tokens(self, text):
|
|
46
|
+
"""Masked tokens in text that match the key."""
|
|
47
|
+
out = []
|
|
48
|
+
for m in self.rx.finditer(text):
|
|
49
|
+
tok = m.group(0).rstrip(".=") # trailing punctuation from prose or logs
|
|
50
|
+
if self.full and self.full not in m.group(0):
|
|
51
|
+
continue
|
|
52
|
+
if self.suffix and not tok.endswith(self.suffix):
|
|
53
|
+
continue
|
|
54
|
+
out.append(mask(self.full if self.full else tok))
|
|
55
|
+
self.seen.update(out)
|
|
56
|
+
return out
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def utc(epoch):
|
|
60
|
+
return dt.datetime.fromtimestamp(int(epoch), dt.timezone.utc).strftime("%Y-%m-%d %H:%M:%SZ")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def line_stamp(line):
|
|
64
|
+
m = STAMP.search(line)
|
|
65
|
+
if not m:
|
|
66
|
+
return "no timestamp on the line"
|
|
67
|
+
day, clock, tz = m.groups()
|
|
68
|
+
if not tz:
|
|
69
|
+
return "%s %s (no timezone on the line)" % (day, clock)
|
|
70
|
+
tz = "+00:00" if tz == "Z" else (tz if ":" in tz else tz[:3] + ":" + tz[3:])
|
|
71
|
+
try:
|
|
72
|
+
when = dt.datetime.fromisoformat("%sT%s%s" % (day, clock, tz))
|
|
73
|
+
except ValueError:
|
|
74
|
+
return "%s %s%s (unparsed)" % (day, clock, tz)
|
|
75
|
+
return when.astimezone(dt.timezone.utc).strftime("%Y-%m-%d %H:%M:%SZ")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def git(repo, *args):
|
|
79
|
+
r = subprocess.run(["git", "-C", repo] + list(args), capture_output=True)
|
|
80
|
+
return r.returncode, r.stdout.decode("utf-8", "replace"), r.stderr.decode("utf-8", "replace")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def scan_repo(repo, needle):
|
|
84
|
+
rc, _, err = git(repo, "rev-parse", "--git-dir")
|
|
85
|
+
if rc != 0:
|
|
86
|
+
return None, "not a git repository: %s (%s)" % (repo, err.strip())
|
|
87
|
+
rc, out, err = git(
|
|
88
|
+
repo, "log", "--all", "--reflog", "--no-color", "--no-ext-diff", "-p",
|
|
89
|
+
"-S" + needle.prefix, "--format=%x00COMMIT %H %at %ct",
|
|
90
|
+
)
|
|
91
|
+
if rc != 0:
|
|
92
|
+
return None, "git log failed: %s" % err.strip()
|
|
93
|
+
commits, cur, path = [], None, None
|
|
94
|
+
for line in out.splitlines():
|
|
95
|
+
if line.startswith("\x00COMMIT "):
|
|
96
|
+
sha, at, ct = line.split()[1:4]
|
|
97
|
+
cur = {"sha": sha, "authored": utc(at), "committed": utc(ct), "added": [], "removed": []}
|
|
98
|
+
commits.append(cur)
|
|
99
|
+
elif cur is None:
|
|
100
|
+
continue
|
|
101
|
+
elif line.startswith("diff --git "):
|
|
102
|
+
path = line.split(" b/", 1)[-1]
|
|
103
|
+
elif line.startswith("+") and not line.startswith("+++"):
|
|
104
|
+
if needle.tokens(line):
|
|
105
|
+
cur["added"].append(path)
|
|
106
|
+
elif line.startswith("-") and not line.startswith("---"):
|
|
107
|
+
if needle.tokens(line):
|
|
108
|
+
cur["removed"].append(path)
|
|
109
|
+
commits = [c for c in commits if c["added"] or c["removed"]]
|
|
110
|
+
for c in commits:
|
|
111
|
+
c["added"], c["removed"] = sorted(set(c["added"])), sorted(set(c["removed"]))
|
|
112
|
+
if c["added"]:
|
|
113
|
+
_, b, _ = git(repo, "branch", "-a", "--contains", c["sha"])
|
|
114
|
+
_, t, _ = git(repo, "tag", "--contains", c["sha"])
|
|
115
|
+
c["refs"] = [x.strip("* ").strip() for x in b.splitlines() if x.strip()]
|
|
116
|
+
c["refs"] += ["tag " + x.strip() for x in t.splitlines() if x.strip()]
|
|
117
|
+
present = {}
|
|
118
|
+
for label, args in (("HEAD", ["grep", "-n", "-I", "-F", "-e", needle.prefix, "HEAD", "--"]),
|
|
119
|
+
("working copy", ["grep", "-n", "-I", "-F", "--untracked", "-e", needle.prefix])):
|
|
120
|
+
_, g, _ = git(repo, *args)
|
|
121
|
+
hits = []
|
|
122
|
+
for gl in g.splitlines():
|
|
123
|
+
parts = gl.split(":", 3 if label == "HEAD" else 2)
|
|
124
|
+
loc = ":".join(parts[1:3]) if label == "HEAD" else ":".join(parts[0:2])
|
|
125
|
+
if needle.tokens(gl):
|
|
126
|
+
hits.append(loc)
|
|
127
|
+
present[label] = hits
|
|
128
|
+
return {"commits": commits, "present": present}, None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def scan_log(path, needle):
|
|
132
|
+
hits = []
|
|
133
|
+
with open(path, "r", encoding="utf-8", errors="replace") as fh:
|
|
134
|
+
for n, line in enumerate(fh, 1):
|
|
135
|
+
toks = needle.tokens(line)
|
|
136
|
+
if toks:
|
|
137
|
+
hits.append((n, line_stamp(line), toks[0]))
|
|
138
|
+
return hits
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def report(args, needle, repo_res, repo_err, logs):
|
|
142
|
+
o = ["# Where the key appears", ""]
|
|
143
|
+
if len(needle.seen) > 1:
|
|
144
|
+
o += ["%d different keys match this prefix: %s. Pass --suffix with the leaked key's last "
|
|
145
|
+
"characters to keep only that one." % (len(needle.seen), ", ".join(sorted(needle.seen))), ""]
|
|
146
|
+
if args.repo:
|
|
147
|
+
o.append("## Git history: %s" % args.repo)
|
|
148
|
+
if repo_err:
|
|
149
|
+
o += ["", "Not scanned: " + repo_err]
|
|
150
|
+
else:
|
|
151
|
+
cs = repo_res["commits"]
|
|
152
|
+
adds = [c for c in cs if c["added"]]
|
|
153
|
+
if not cs:
|
|
154
|
+
o += ["", "No commit adds or removes it (searched every branch, tag, stash and the local reflog)."]
|
|
155
|
+
if adds:
|
|
156
|
+
first = min(adds, key=lambda c: c["authored"])
|
|
157
|
+
o += ["", "First commit that adds it: `%s`, authored %s, committed %s. Commit time is "
|
|
158
|
+
"when it was committed locally; when it reached a remote is in the host's audit or push log."
|
|
159
|
+
% (first["sha"][:12], first["authored"], first["committed"])]
|
|
160
|
+
for c in cs:
|
|
161
|
+
o.append("")
|
|
162
|
+
o.append("- `%s` authored %s, committed %s" % (c["sha"][:12], c["authored"], c["committed"]))
|
|
163
|
+
if c["added"]:
|
|
164
|
+
o.append(" - adds it in: %s" % ", ".join(c["added"]))
|
|
165
|
+
o.append(" - still reachable from: %s" % (", ".join(c["refs"]) if c["refs"]
|
|
166
|
+
else "no branch or tag; only the local reflog keeps it"))
|
|
167
|
+
if c["removed"]:
|
|
168
|
+
o.append(" - removes it from: %s (the earlier commits still hold it)" % ", ".join(c["removed"]))
|
|
169
|
+
for label, hits in repo_res["present"].items():
|
|
170
|
+
o.append("")
|
|
171
|
+
o.append("In %s now: %s" % (label, ", ".join(hits) if hits else "not present"))
|
|
172
|
+
o.append("")
|
|
173
|
+
for path, hits, err in logs:
|
|
174
|
+
o.append("## Log: %s" % path)
|
|
175
|
+
o.append("")
|
|
176
|
+
if err:
|
|
177
|
+
o.append("Not scanned: " + err)
|
|
178
|
+
elif not hits:
|
|
179
|
+
o.append("Not found.")
|
|
180
|
+
else:
|
|
181
|
+
for n, stamp, tok in hits:
|
|
182
|
+
o.append("- line %d, %s, %s" % (n, stamp, tok))
|
|
183
|
+
o.append("")
|
|
184
|
+
o += ["## Not searched", "",
|
|
185
|
+
"Forks, other clones, pull-request refs that were never fetched, CI caches and artifacts, "
|
|
186
|
+
"the hosting provider's own copies, chat and ticket systems. A clean result here does not "
|
|
187
|
+
"mean the key is safe: revoke it regardless.", ""]
|
|
188
|
+
return "\n".join(o)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def main(argv=None):
|
|
192
|
+
p = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
|
193
|
+
who = p.add_mutually_exclusive_group(required=True)
|
|
194
|
+
who.add_argument("--prefix", help="the first characters of the key (at least %d)" % MIN_PREFIX)
|
|
195
|
+
who.add_argument("--key-stdin", action="store_true", help="read the full key from stdin")
|
|
196
|
+
p.add_argument("--suffix", help="the last characters of the key, to tell it apart from keys with the same prefix")
|
|
197
|
+
p.add_argument("--repo", help="path to a git repository to search")
|
|
198
|
+
p.add_argument("--log", action="append", default=[], help="a log file to search (repeatable)")
|
|
199
|
+
p.add_argument("-o", "--out", help="write the report here instead of stdout")
|
|
200
|
+
a = p.parse_args(argv)
|
|
201
|
+
if a.key_stdin:
|
|
202
|
+
full = sys.stdin.read().strip()
|
|
203
|
+
if len(full) < MIN_PREFIX:
|
|
204
|
+
p.error("the key on stdin is shorter than %d characters" % MIN_PREFIX)
|
|
205
|
+
needle = Needle(full=full)
|
|
206
|
+
else:
|
|
207
|
+
if len(a.prefix) < MIN_PREFIX:
|
|
208
|
+
p.error("--prefix needs at least %d characters: a short prefix such as a provider's "
|
|
209
|
+
"key-type marker matches every key of that type" % MIN_PREFIX)
|
|
210
|
+
needle = Needle(prefix=a.prefix, suffix=a.suffix)
|
|
211
|
+
if not a.repo and not a.log:
|
|
212
|
+
p.error("give --repo, --log, or both")
|
|
213
|
+
repo_res, repo_err = (None, None)
|
|
214
|
+
if a.repo:
|
|
215
|
+
if not os.path.isdir(a.repo):
|
|
216
|
+
repo_err = "no such directory: %s" % a.repo
|
|
217
|
+
else:
|
|
218
|
+
repo_res, repo_err = scan_repo(a.repo, needle)
|
|
219
|
+
logs = []
|
|
220
|
+
for path in a.log:
|
|
221
|
+
if not os.path.isfile(path):
|
|
222
|
+
logs.append((path, [], "no such file"))
|
|
223
|
+
continue
|
|
224
|
+
try:
|
|
225
|
+
logs.append((path, scan_log(path, needle), None))
|
|
226
|
+
except OSError as e:
|
|
227
|
+
logs.append((path, [], str(e)))
|
|
228
|
+
text = report(a, needle, repo_res, repo_err, logs)
|
|
229
|
+
if a.out:
|
|
230
|
+
with open(a.out, "w", encoding="utf-8") as fh:
|
|
231
|
+
fh.write(text)
|
|
232
|
+
print("wrote %s" % a.out)
|
|
233
|
+
else:
|
|
234
|
+
print(text)
|
|
235
|
+
return 0
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if __name__ == "__main__":
|
|
239
|
+
sys.exit(main())
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Split a provider usage or billing export (CSV) by who spent it.
|
|
3
|
+
|
|
4
|
+
Read-only. Sums cost exactly (decimal, not float) per group of columns, so a bill
|
|
5
|
+
is split from the export rather than estimated. Groups by the key, model and
|
|
6
|
+
user-agent columns it finds (override with --by). Each group shows its total,
|
|
7
|
+
its share of the whole, its row and request counts, and the time of its first and
|
|
8
|
+
last row as written in the export.
|
|
9
|
+
|
|
10
|
+
python3 usage_split.py usage.csv
|
|
11
|
+
python3 usage_split.py usage.csv --since 2026-10-19T20:00 --expect model=model-medium
|
|
12
|
+
python3 usage_split.py usage.csv --by api_key_name,user_agent -o split.txt
|
|
13
|
+
cat usage.csv | python3 usage_split.py -
|
|
14
|
+
|
|
15
|
+
--expect col=value (repeatable) marks every group whose value in that column
|
|
16
|
+
differs as UNEXPECTED: traffic the known job does not explain. --since/--until
|
|
17
|
+
compare the time column as text, so give them in the export's own format.
|
|
18
|
+
Writes only the -o path when given.
|
|
19
|
+
"""
|
|
20
|
+
import argparse
|
|
21
|
+
import csv
|
|
22
|
+
import io
|
|
23
|
+
import sys
|
|
24
|
+
from decimal import Decimal, InvalidOperation
|
|
25
|
+
|
|
26
|
+
COST_HINTS = ("cost_usd", "cost", "amount_usd", "amount", "spend", "usd", "price")
|
|
27
|
+
TIME_HINTS = ("date_hour_utc", "timestamp", "time", "hour", "date", "start_time", "period")
|
|
28
|
+
REQ_HINTS = ("requests", "request_count", "num_requests", "calls")
|
|
29
|
+
GROUP_HINTS = (
|
|
30
|
+
("api_key_name", "api_key", "key_name", "key_id", "key"),
|
|
31
|
+
("model", "model_name", "model_id"),
|
|
32
|
+
("user_agent", "useragent", "ua", "client"),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def find_col(headers, hints, contains=False):
|
|
37
|
+
low = {h.lower().strip(): h for h in headers}
|
|
38
|
+
for hint in hints:
|
|
39
|
+
if hint in low:
|
|
40
|
+
return low[hint]
|
|
41
|
+
if contains:
|
|
42
|
+
for hint in hints:
|
|
43
|
+
for key, orig in low.items():
|
|
44
|
+
if hint in key:
|
|
45
|
+
return orig
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def to_decimal(text):
|
|
50
|
+
cleaned = (text or "").strip().replace("$", "").replace(",", "")
|
|
51
|
+
if cleaned == "":
|
|
52
|
+
return Decimal("0")
|
|
53
|
+
return Decimal(cleaned)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def money(value):
|
|
57
|
+
return "${:,.2f}".format(value)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def main(argv=None):
|
|
61
|
+
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
|
|
62
|
+
ap.add_argument("export", help="CSV usage export path, or - for stdin")
|
|
63
|
+
ap.add_argument("--by", help="comma-separated columns to group by (default: key, model, user agent)")
|
|
64
|
+
ap.add_argument("--cost", help="cost column name (default: auto-detect)")
|
|
65
|
+
ap.add_argument("--time", help="time column name (default: auto-detect)")
|
|
66
|
+
ap.add_argument("--since", help="keep rows whose time is >= this text")
|
|
67
|
+
ap.add_argument("--until", help="keep rows whose time is < this text")
|
|
68
|
+
ap.add_argument("--expect", action="append", default=[], metavar="COL=VALUE",
|
|
69
|
+
help="mark groups whose COL differs from VALUE as UNEXPECTED")
|
|
70
|
+
ap.add_argument("-o", "--output", help="write the report to this path instead of stdout")
|
|
71
|
+
args = ap.parse_args(argv)
|
|
72
|
+
|
|
73
|
+
if args.export == "-":
|
|
74
|
+
text = sys.stdin.read()
|
|
75
|
+
else:
|
|
76
|
+
try:
|
|
77
|
+
with open(args.export, encoding="utf-8", newline="") as fh:
|
|
78
|
+
text = fh.read()
|
|
79
|
+
except OSError as exc:
|
|
80
|
+
print("error: cannot read {}: {}".format(args.export, exc), file=sys.stderr)
|
|
81
|
+
return 2
|
|
82
|
+
text = text.lstrip("")
|
|
83
|
+
reader = csv.DictReader(io.StringIO(text))
|
|
84
|
+
headers = reader.fieldnames or []
|
|
85
|
+
if not headers:
|
|
86
|
+
print("error: the export has no header row", file=sys.stderr)
|
|
87
|
+
return 2
|
|
88
|
+
|
|
89
|
+
cost_col = args.cost or find_col(headers, COST_HINTS, contains=True)
|
|
90
|
+
time_col = args.time or find_col(headers, TIME_HINTS, contains=True)
|
|
91
|
+
req_col = find_col(headers, REQ_HINTS)
|
|
92
|
+
if not cost_col or cost_col not in headers:
|
|
93
|
+
print("error: no cost column found; columns are: {}; pass --cost".format(", ".join(headers)),
|
|
94
|
+
file=sys.stderr)
|
|
95
|
+
return 2
|
|
96
|
+
if args.by:
|
|
97
|
+
group_cols = [c.strip() for c in args.by.split(",") if c.strip()]
|
|
98
|
+
else:
|
|
99
|
+
group_cols = [c for c in (find_col(headers, h) for h in GROUP_HINTS) if c]
|
|
100
|
+
missing = [c for c in group_cols if c not in headers]
|
|
101
|
+
if missing or not group_cols:
|
|
102
|
+
print("error: group columns not in the export: {}; columns are: {}".format(
|
|
103
|
+
", ".join(missing) or "(none found)", ", ".join(headers)), file=sys.stderr)
|
|
104
|
+
return 2
|
|
105
|
+
if (args.since or args.until) and not time_col:
|
|
106
|
+
print("error: --since/--until need a time column; pass --time", file=sys.stderr)
|
|
107
|
+
return 2
|
|
108
|
+
|
|
109
|
+
expects = []
|
|
110
|
+
for item in args.expect:
|
|
111
|
+
if "=" not in item:
|
|
112
|
+
print("error: --expect takes COL=VALUE, got {!r}".format(item), file=sys.stderr)
|
|
113
|
+
return 2
|
|
114
|
+
col, val = item.split("=", 1)
|
|
115
|
+
if col not in group_cols:
|
|
116
|
+
print("error: --expect column {!r} must be one of the group columns: {}".format(
|
|
117
|
+
col, ", ".join(group_cols)), file=sys.stderr)
|
|
118
|
+
return 2
|
|
119
|
+
expects.append((col, val))
|
|
120
|
+
|
|
121
|
+
groups = {}
|
|
122
|
+
total = Decimal("0")
|
|
123
|
+
kept = 0
|
|
124
|
+
bad = []
|
|
125
|
+
for line_no, row in enumerate(reader, start=2):
|
|
126
|
+
when = (row.get(time_col) or "").strip() if time_col else ""
|
|
127
|
+
if args.since and when < args.since:
|
|
128
|
+
continue
|
|
129
|
+
if args.until and when >= args.until:
|
|
130
|
+
continue
|
|
131
|
+
try:
|
|
132
|
+
cost = to_decimal(row.get(cost_col))
|
|
133
|
+
except InvalidOperation:
|
|
134
|
+
bad.append(line_no)
|
|
135
|
+
continue
|
|
136
|
+
key = tuple((row.get(c) or "").strip() for c in group_cols)
|
|
137
|
+
g = groups.setdefault(key, {"cost": Decimal("0"), "rows": 0, "requests": 0,
|
|
138
|
+
"first": None, "last": None, "times": []})
|
|
139
|
+
g["cost"] += cost
|
|
140
|
+
g["rows"] += 1
|
|
141
|
+
if req_col:
|
|
142
|
+
try:
|
|
143
|
+
g["requests"] += int(float((row.get(req_col) or "0").replace(",", "")))
|
|
144
|
+
except ValueError:
|
|
145
|
+
pass
|
|
146
|
+
if when:
|
|
147
|
+
g["times"].append(when)
|
|
148
|
+
g["first"] = when if g["first"] is None or when < g["first"] else g["first"]
|
|
149
|
+
g["last"] = when if g["last"] is None or when > g["last"] else g["last"]
|
|
150
|
+
total += cost
|
|
151
|
+
kept += 1
|
|
152
|
+
|
|
153
|
+
out = []
|
|
154
|
+
window = ""
|
|
155
|
+
if args.since or args.until:
|
|
156
|
+
window = " ({} to {})".format(args.since or "start", args.until or "end")
|
|
157
|
+
out.append("Rows: {}{} Total: {}".format(kept, window, money(total)))
|
|
158
|
+
out.append("Grouped by: {} Cost column: {}{}".format(
|
|
159
|
+
", ".join(group_cols), cost_col, " Time column: " + time_col if time_col else ""))
|
|
160
|
+
out.append("")
|
|
161
|
+
for key, g in sorted(groups.items(), key=lambda kv: kv[1]["cost"], reverse=True):
|
|
162
|
+
share = (g["cost"] / total * 100) if total else Decimal("0")
|
|
163
|
+
label = " | ".join("{}={}".format(c, v) for c, v in zip(group_cols, key))
|
|
164
|
+
flag = ""
|
|
165
|
+
if expects:
|
|
166
|
+
off = [c for c, v in expects if key[group_cols.index(c)] != v]
|
|
167
|
+
if off:
|
|
168
|
+
flag = " UNEXPECTED ({} differs)".format(", ".join(off))
|
|
169
|
+
out.append("{} {} ({:.1f}%){}".format(money(g["cost"]), label, share, flag))
|
|
170
|
+
detail = " rows={}".format(g["rows"])
|
|
171
|
+
if req_col:
|
|
172
|
+
detail += " requests={:,}".format(g["requests"])
|
|
173
|
+
if g["first"]:
|
|
174
|
+
detail += " first={} last={}".format(g["first"], g["last"])
|
|
175
|
+
out.append(detail)
|
|
176
|
+
if g["times"] and len(g["times"]) <= 12:
|
|
177
|
+
out.append(" times: " + ", ".join(sorted(g["times"])))
|
|
178
|
+
if bad:
|
|
179
|
+
out.append("")
|
|
180
|
+
out.append("Skipped rows with an unreadable cost (CSV line numbers): " +
|
|
181
|
+
", ".join(str(n) for n in bad))
|
|
182
|
+
report = "\n".join(out) + "\n"
|
|
183
|
+
|
|
184
|
+
if args.output:
|
|
185
|
+
with open(args.output, "w", encoding="utf-8") as fh:
|
|
186
|
+
fh.write(report)
|
|
187
|
+
print("wrote {}".format(args.output))
|
|
188
|
+
else:
|
|
189
|
+
sys.stdout.write(report)
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
sys.exit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: file-to-markdown
|
|
3
|
-
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.
|
|
3
|
+
description: Convert a Word document, PowerPoint deck, Excel workbook, web page, CSV, JSON, text file, email (.eml or Outlook .msg) 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, .eml, .msg or .pdf and says "read this", "turn this into markdown", "extract the text", or wants it critiqued or briefed.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# file-to-markdown
|
|
@@ -46,6 +46,7 @@ or a rewrite that goes back out through `md-to-docx` or `md-to-html`.
|
|
|
46
46
|
| `.html` | Headings, lists, tables, links, code blocks, quotes; scripts, styles and nav menus dropped |
|
|
47
47
|
| `.csv` / `.tsv` | One table, delimiter detected |
|
|
48
48
|
| `.json` | An array of objects becomes a table; an object becomes a key/value table plus a table per array |
|
|
49
|
+
| `.eml` / `.msg` | Subject as the title; From, To, Cc and the sent time as a table; the plain-text body (the HTML body only when there is no text one); attachments listed by name and size, not converted. Outlook `.msg` is read directly, with nothing to install |
|
|
49
50
|
| `.txt` / `.md` | Passed through |
|
|
50
51
|
| `.pdf` | Through `pdftotext`. Tables from aligned columns (told apart by where each word sits on the page, so two columns set close together don't merge), lists from indentation, short standalone lines as `###` headings (guessed, and said so); a table that runs onto the next page is joined into one, without the header the PDF repeats; pages with side-by-side columns are read in column order with nothing inferred. Scanned pages are read by OCR when `tesseract` is installed. No images |
|
|
51
52
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "file-to-markdown",
|
|
3
|
-
"version": "0.
|
|
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.",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Convert a Word document, PowerPoint deck, Excel workbook, web page, CSV, JSON, text file, email (.eml or Outlook .msg) 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, .eml, .msg 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": {
|
|
7
7
|
"npm": [],
|