office-artifacts
GitHub用于生成DOCX、PPTX、XLSX、PDF等真实办公文件。通过Python库写入制品目录,并显式调用cloud deliver命令交付给用户,支持跨环境适配与格式规范验证。
Trigger Scenarios
Install
npx skills add Prismer-AI/PrismerCloud --skill office-artifacts -g -y
SKILL.md
Frontmatter
{
"name": "office-artifacts",
"scope": "persistence",
"metadata": {
"aliases": [
"document-generation"
],
"nativeReplaces": [
"docx",
"pdf",
"powerpoint",
"xlsx"
]
},
"description": "Generate real DOCX, PPTX, XLSX, PDF, CSV files using python-docx \/ python-pptx \/ openpyxl \/ reportlab by writing them into the dispatch artifacts dir, then explicitly deliver each one with `cloud deliver <abs-path>`. Use whenever the user asks for documents, slides, spreadsheets, reports, or PDFs."
}
Office Artifacts
Format-specific resources
Read resource limits for dependency tiers, unsupported structures, Unicode fonts and optional extractor licensing.
Read only the guide for the requested format: references/docx/GUIDE.md, references/pdf/GUIDE.md, references/powerpoint/GUIDE.md or references/xlsx/GUIDE.md. Each guide's scripts and references are relative to its own directory, not the workspace or this skill root. Use the installed office Python interpreter below. For CJK text, references/document-generation/GUIDE.md is authoritative for fonts and PDF rendering; do not use ReportLab with unsupported CFF Noto collections. Treat upstream installation suggestions as optional dependency checks, not permission to modify the system. Verify generated files by reopening them and inspect rendered pages/slides when a renderer is available; otherwise report that visual verification was not performed. Always follow this entry's Prismer delivery contract instead of upstream host-specific output conventions.
Produce real binary office files (not markdown-as-PDF) by writing each
one into the artifacts dir ($PRISMER_ARTIFACTS_DIR /
<artifacts_dir> from <execution_context>). Prose-only completions are
not valid.
Delivery contract — explicit, two steps (release202/09 P2)
Delivery is explicit. Writing a file into the artifacts dir does NOT
deliver it — there is no auto-scan. You must run cloud deliver for
every file you want the user to receive.
-
Write each deliverable (PDF / DOCX / PPTX / XLSX / CSV / image / archive) into the artifacts dir. Drafts / intermediate scratch →
$PRISMER_SCRATCH_DIR(never delivered). -
Deliver it with the absolute path:
cloud deliver "<artifacts_dir>/report.pdf"This attaches the file to your current reply (动作 A — it rides the same message as your text summary). Run it once per file. The command prints the
assetIdon success.
-
$PRISMER_ARTIFACTS_DIRis injected as an env var by spawn-style adapters (claude-code / codex / openclaw). Hermes does not inject the env var — read the absoluteartifacts/path from the dispatch prompt's[Artifacts directive]block and write there. Either way, pass the absolute path tocloud deliver. -
Hermes also has NO per-dispatch env ids (
PRISMER_RUN_ID/PRISMER_TASK_ID/PRISMER_CONVERSATION_IDare all unset), so plaincloud deliver <path>cannot find the dispatch and exits with an error. On hermes you MUST copy<run_id>(or<task_id>) and<conversation_id>from<execution_context>and pass them as flags:# Hermes — ids come from <execution_context>, not env: cloud deliver "<artifacts_dir>/report.pdf" --run-id "<run_id>" --conversation-id "<conversation_id>"Spawn-adapter agents (claude-code / codex) do not need these flags — their env is already set; plain
cloud deliver "<abs-path>"works there. -
Sending a file as its OWN message (动作 B — separate from your reply, e.g. to drop it into the session standalone): use
cloud file send "$PRISMER_CONVERSATION_ID" "<abs-path>". On hermes the conversation id comes from<execution_context><conversation_id>(pass it as the positional), and you must also add--run-id "<run_id>"so the proxy activates. Prefercloud deliver(动作 A) for the normal "here is the document you asked for" case. -
Attaching a file to a message you ALREADY sent (动作 A2 — "I already replied, now I produced a file I want on THAT message"):
cloud send/cloud file sendreturn amessageId; runcloud attach <messageId> "<abs-path>"to append the file to that existing message (it does NOT start a new one). On hermes also pass--run-id "<run_id>"and--conversation-id "<conversation_id>"(both copied from<execution_context>). Use this only when the reply already exists; for "this reply carries the file" usecloud deliver(动作 A). -
Do NOT run
cloud task completeto deliver a chat reply — the platform closes the turn from your final reply (dispatch-reply path). -
cloud task attach/cloud task completeare OPTIONAL and ONLY for when you are genuinely working a kanban TASK you were assigned (e.g. moving a board card toreview/done). For a normal @-mention chat reply, never touch them —cloud deliveris all you need. -
Rejections are visible: if
cloud deliverprints a non-zero exit / error (bad MIME, renamed extension, file not found), the file was NOT delivered — fix it and re-run. Do not claim a file you failed to deliver.
HARD RULE — never claim a file you didn't actually write
Lie-guard (src/im/ws/handler.ts) as of release201/30 §8 is
warn-only telemetry, not a destructive interceptor — the audit
banner ("Team Manager 声明已生成文件但未真正落盘") that used to render in
red is removed. The detector still logs a [warn] claim/asset disagreement (warn-only, see release201/30 §8) for ops dashboards
when text claims a file but assetIds is empty. That signal helps
investigate broken adapter wiring; it does NOT change the visible
chat. Original 2026-05-22 audit: 25 of 27 office tasks shipped a
claim with zero attached files.
Forbidden unless every product was actually written to
$PRISMER_ARTIFACTS_DIR: "已生成 / 已落盘 / 已附件", "Created
report.pdf", "Attached the file", "Generated slides.pptx".
Before claiming a file, all must hold:
- Generator import (
import docx/pptx/openpyxl/reportlab) succeeds. - Script ran with exit 0 (no traceback).
ls -lah <artifacts_dir>(the absolute path from<execution_context>) shows the file, non-zero size.
If (1)-(3) fail, your reply must start with 无法生成文件 (reason) / Cannot produce file (reason).
Runtime baseline (release 201)
Sandbox image bakes python-docx, python-pptx, openpyxl,
reportlab, pandas, pillow, numpy, imageio, apt file — all of
them inside the image venv interpreter /home/user/.venv/bin/python3
(NOT the system /usr/bin/python3, which has NONE of these libraries).
Always run generator scripts with that interpreter, e.g.
/home/user/.venv/bin/python3 generate_report.py
If an import docx (or pptx / openpyxl / reportlab) probe fails, DO
NOT conclude the dependency is missing until you have retried under
/home/user/.venv/bin/python3 — a failure under the system python only
means you probed the wrong interpreter. Only when the import ALSO fails
under the venv interpreter, fail the task with module 'X' missing — rebuild sandbox image. Do not pip install on the fly.
Resolving the output directory
Your artifacts dir is delivered as an ABSOLUTE path in your prompt's
<execution_context> block:
<execution_context …>
<artifacts_dir>/home/user/.prismer/workspaces/<ws>/…/tasks/<id>/artifacts</artifacts_dir>
…
</execution_context>
It is also re-stated in the [Artifacts directive] block. Use that exact
absolute path. The $PRISMER_ARTIFACTS_DIR env var is set ONLY by
spawn-style adapters (claude-code / codex / openclaw); Hermes does NOT set
it (os.environ.get("PRISMER_ARTIFACTS_DIR") returns None). Never write to
a relative path, the CWD, or a literal placeholder — copy the <artifacts_dir>
value verbatim:
import os
from pathlib import Path
# Paste the absolute path from <execution_context><artifacts_dir> here:
ARTIFACTS_DIR = "/home/user/.prismer/workspaces/.../tasks/.../artifacts" # ← from <artifacts_dir>
out = Path(os.environ.get("PRISMER_ARTIFACTS_DIR") or ARTIFACTS_DIR)
out.mkdir(parents=True, exist_ok=True)
Generator recipes
# DOCX
from docx import Document
d = Document(); d.add_heading("Report", 0)
d.add_paragraph("Executive summary…"); d.save(out / "report.docx")
# PPTX
from pptx import Presentation
p = Presentation(); s = p.slides.add_slide(p.slide_layouts[0])
s.shapes.title.text = "Deck"; s.placeholders[1].text = "Key message"
p.save(out / "deck.pptx")
# XLSX
from openpyxl import Workbook
wb = Workbook(); ws = wb.active; ws.title = "Model"
ws.append(["Metric", "Value"]); ws.append(["Revenue", 100000])
ws.freeze_panes = "A2"; wb.save(out / "model.xlsx")
# PDF — reportlab (never rename markdown/html to .pdf)
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
c = canvas.Canvas(str(out / "report.pdf"), pagesize=letter)
c.drawString(72, 720, "Report"); c.save()
Validation + delivery
- File non-empty, magic bytes match (DOCX/PPTX/XLSX →
PK; PDF →%PDF; CSV → UTF-8 with headers). - Confirm every product landed in the artifacts dir:
ls -lah <artifacts_dir> # the absolute path from <execution_context><artifacts_dir>
- Deliver each file explicitly (this is the step that actually delivers — there is no auto-scan):
cloud deliver "<artifacts_dir>/report.pdf"
cloud deliver "<artifacts_dir>/deck.pptx"
# On hermes (no env ids) append --run-id / --conversation-id from <execution_context>:
cloud deliver "<artifacts_dir>/report.pdf" --run-id "<run_id>" --conversation-id "<conversation_id>"
Each call attaches the file to your reply (动作 A). Confirm exit 0 and
the printed assetId. Then write your final reply describing what you
produced. Do NOT run cloud task attach / cloud task complete for
chat delivery.
Working a real kanban TASK (board card), not a chat reply? Only then may you optionally
cloud task complete "$PRISMER_TASK_ID" --result "..."to move the card — and only when$PRISMER_TASK_IDis a real task id, not a chat run.
Failure mode
Missing library + cannot install → fail with module 'X' missing and
list the package. No renamed extensions, no prose-only substitute.
Version History
- f4c4a0c Current 2026-10-19 02:39


