mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
Adds 7 optional skills under optional-skills/finance/ adapted from
anthropics/financial-services (Apache-2.0):
excel-author — openpyxl conventions: blue/black/green cells,
formulas over hardcodes, named ranges, balance
checks, sensitivity tables. Ships recalc.py.
pptx-author — python-pptx for model-backed decks (pitch,
IC memo, earnings note) that bind every number
to a source workbook cell.
dcf-model — institutional DCF (49KB skill): projections,
WACC, terminal value, Bear/Base/Bull scenarios,
5x5 sensitivity tables. Ships validate_dcf.py.
comps-analysis — comparable company analysis: operating metrics,
multiples, statistical benchmarking.
lbo-model — leveraged buyout: S&U, debt schedule, cash
sweep, exit multiple, IRR/MOIC sensitivity.
3-statement-model — fully-integrated IS/BS/CF with balance-check
plugs. Ships references/ for formatting,
formulas, SEC filings.
merger-model — accretion/dilution analysis for M&A.
All seven are optional (not active by default). Users install via
'hermes skills install official/finance/<skill>'.
Hermesification:
- Stripped every Office JS / Office Add-in / mcp__office__*
branch — skills assume headless openpyxl only.
- Replaced Cowork MCP data-source instructions with 'MCP first (via
native-mcp), fall back to web_search/web_extract against SEC EDGAR
and user-provided data'.
- Swapped Claude tool references (Bash, Read, Write, Edit, mcp__*)
for Hermes-native equivalents and Python library calls.
- Canonical Hermes frontmatter (name/description/version/author/
license/metadata.hermes.{tags,related_skills}).
- Descriptions tightened to 187-238 chars, trigger-first.
- Attribution preserved: author field credits 'Anthropic (adapted by
Nous Research)', license: Apache-2.0, each SKILL.md links back to
the upstream source directory.
Verification:
- All 7 discovered by OptionalSkillSource with source_id='official'
- Bundle fetch includes support files (scripts, references, troubleshooting)
- related_skills cross-refs all resolve within the bundle
- No Claude product / Cowork / Office JS / /mnt/skills leakage
remains in body text (bounded mentions only in attribution blocks)
Source: https://github.com/anthropics/financial-services (Apache-2.0)
88 lines
2.7 KiB
Python
88 lines
2.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Recalculate an .xlsx file's formulas using LibreOffice headless.
|
|
|
|
Usage: python recalc.py <path.xlsx> [timeout_seconds]
|
|
|
|
openpyxl writes formula strings but does not compute them. Downstream scripts
|
|
that open the file with data_only=True get None for every formula cell until
|
|
something has actually calculated the workbook. Excel does this on open;
|
|
headless pipelines need LibreOffice (or similar) to do it explicitly.
|
|
|
|
Exits 0 on success (workbook recomputed and resaved in place), non-zero on
|
|
failure. Writes status JSON to stdout either way.
|
|
"""
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
|
|
def find_libreoffice() -> str | None:
|
|
for cmd in ("libreoffice", "soffice"):
|
|
path = shutil.which(cmd)
|
|
if path:
|
|
return path
|
|
return None
|
|
|
|
|
|
def recalc(xlsx_path: str, timeout: int = 60) -> dict:
|
|
src = Path(xlsx_path).resolve()
|
|
if not src.exists():
|
|
return {"status": "error", "error": f"File not found: {src}"}
|
|
|
|
lo = find_libreoffice()
|
|
if lo is None:
|
|
return {
|
|
"status": "error",
|
|
"error": "libreoffice not found on PATH — install it or recalc in a real Excel session",
|
|
}
|
|
|
|
with tempfile.TemporaryDirectory() as td:
|
|
try:
|
|
subprocess.run(
|
|
[
|
|
lo,
|
|
"--headless",
|
|
"--calc",
|
|
"--convert-to",
|
|
"xlsx",
|
|
str(src),
|
|
"--outdir",
|
|
td,
|
|
],
|
|
check=True,
|
|
capture_output=True,
|
|
timeout=timeout,
|
|
)
|
|
except subprocess.TimeoutExpired:
|
|
return {"status": "error", "error": f"libreoffice timed out after {timeout}s"}
|
|
except subprocess.CalledProcessError as e:
|
|
return {
|
|
"status": "error",
|
|
"error": f"libreoffice exited {e.returncode}: {e.stderr.decode(errors='replace')[:500]}",
|
|
}
|
|
|
|
produced = Path(td) / src.name
|
|
if not produced.exists():
|
|
return {"status": "error", "error": "libreoffice did not produce output file"}
|
|
|
|
shutil.copy(produced, src)
|
|
|
|
return {"status": "success", "file": str(src)}
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("Usage: python recalc.py <path.xlsx> [timeout_seconds]", file=sys.stderr)
|
|
sys.exit(2)
|
|
timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 60
|
|
result = recalc(sys.argv[1], timeout=timeout)
|
|
print(json.dumps(result, indent=2))
|
|
sys.exit(0 if result["status"] == "success" else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|