agentleFS
ask-marcelask-marcel/ask-marcel-office-cli/skills/SKILL.md

ask-marcel

Skill9 starsChanged 20 days agoMITView on GitHub

Answer questions from the user's own Microsoft 365, and draft their replies, forwards and new mails as UNSENT Outlook drafts, via the local ask-marcel-office bins (`ask-marcel-office-read`, `ask-marcel-office-auth`, and `ask-marcel-office-write` for drafts). It is the ONLY way to read the user's mail, files, calendar, colleagues and Teams chats, and the only way to write a draft in their mailbox: use it for ANY factual question about their work content and ANY request to reply, forward or write an email, even when they name no tool or assume you can't see their data, instead of answering from memory. Triggers: "what's the status of X", "summarize the latest doc on Y", "who is Z", "did we decide / hear back on…", "what's on my calendar / plate", "reply to X saying…", "answer this email", "draft an email to Y", "forward that thread to Z". Do NOT use it to SEND mail (every draft stays unsent; the user sends in Outlook), schedule time, create tasks or change settings, nor for a local file, debugging the CLI, or generic how-to questions not about the user's own content.

  • Installs packages

The file

We split this skill into the rules it gives an agent

9 rules. About 0 are common, 4 are distinctive, and 5 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈4

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Always take the newest: Every hit carries a date — a document's last-modified, an email's received.

    line 24
  2. Distinctive:

    Only cite and promise what you've actually found: Never reference — or, in a draft, commit the user to sending — a document, analysis, or …

    line 26
  3. Very specific:

    Default text output is fine: Add --output json only when you need to extract fields programmatically.

    line 30
  4. Very specific:

    Only ask-marcel-office 2.8.0 installed?

    line 56
  5. Distinctive:

    2. Route by question shape: When you can't decide, search Mail + Files both.

    line 69
  6. Distinctive:

    When a claim rests on a specific figure or line, cite where it lives — page (PDF / deck slide), sheet name, or cell — plus the short phrase …

    line 112
  7. Very specific:

    Every document link must end with web=1 so it opens in the browser instead of launching the desktop app: append ?web=1, or &web=1 when the …

    line 114
  8. Very specific:

    Exact phrases: put double quotes inside --query when word order matters ('"budget allocation"', 'subject:"project timeline"').

    line 120
  9. Very specific:

    Revise, never recreate: If the thread already carries an isDraft: true row, update that draft rather than creating a second.

    line 144

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Answer a question, or draft a reply, from Microsoft 365

Thin orchestrator over three bins. ask-marcel-office-read has the 200+ typed Microsoft Graph read commands. ask-marcel-office-auth signs you in. ask-marcel-office-write has the write commands: the unsent-draft writers and the PDF converters that use a temporary OneDrive file. The bins do auth, pagination and file conversion. Your job: pick the right commands, read what they return, follow the leads, and assemble a sourced answer — or, on request, an unsent draft.

Ground rules

  • Always take the newest. Every hit carries a date — a document's last-modified, an email's received. When several look relevant, open the most recently changed first; when two sources disagree, the newest wins.
  • Build context before you answer. For any status / catch-up / decision question, after reading the primary source run a topic search: search-all-files and search-mail-messages on the subject and its key nouns — the project, vendor, any document it names — to pull the actual deck, figures, or prior decision. Resolve the key people and their roles via the people path. Search several angles (files, mail, people), in parallel when possible, then consolidate — answer from the fuller picture, not the first email you opened.
  • Only cite and promise what you've actually found. Never reference — or, in a draft, commit the user to sending — a document, analysis, or deliverable you haven't confirmed exists. Search first; if it exists, pull the real details in; if not, drop the claim.
  • Fire independent calls in parallel. Calls that don't feed each other go out in one turn: the first-round searches (mail + files + people), per-sheet Excel reads, per-attachment converts, per-scan Reads. Sequence only when one call's input comes from another's output. Draft writes are the exception: one at a time.
  • Answer for the user, not the log. The final answer carries findings, not plumbing: no command names, no Graph/HTTP error codes, no token-or-scope talk (that a lookup returned nothing is a fact; how the API said so is not). Keep it concise and mirror how the user writes to you — when in doubt, terse and skimmable beats a wall of prose.
  • All timestamps come back in UTC. my-quick-context returns tenantTimeZone; convert before stating any time. In a UTC+8 tenant, a meeting Graph reports at 07:00 starts at 15:00 local — answering "7am" is wrong.
  • Default text output is fine. Add --output json only when you need to extract fields programmatically.
  • Large payloads go to disk. --output-path <file> works on every command that returns a document body — and ONLY those: JSON commands (searches, listings) refuse it, so shell-redirect those instead (--output json > out.json) and extract with a script. microsoft-search-query is the usual case: six containers of up to 25 hits each (--top lowers that), no --select, routinely >100 KB.
  • Follow the next: footer. A listing with more pages ends with a line --- next: ask-marcel-office-read next-page --url '…'; run that command verbatim for the next page, and stop when the line disappears or you have what you need.
  • A failure names its own fix. Most errors print a hint: line (a missing flag, the wrong id shape, a token to refresh); follow it before retrying, and keep it out of the answer.
  • Discover anything not covered here with --help (all commands of a bin) or docs <command> (the page of one command) on each bin, for example ask-marcel-office-read --help and ask-marcel-office-write docs create-reply-draft.

Packages and bins

PackageBinNeededCommands
@ask-marcel/office-authask-marcel-office-authyeslogin, logout, status
@ask-marcel/office-readask-marcel-office-readyeseach command in this skill that is not in another row
@ask-marcel/office-writeask-marcel-office-writeoptional, for drafts and for the PDF conversion of attachmentscreate-reply-draft, create-forward-draft, create-mail-draft, update-mail-draft, convert-mail-attachment-to-pdf, convert-calendar-event-attachment-to-pdf, convert-group-post-attachment-to-pdf

A command line in this skill shows its bin. If you use the wrong bin, the bin tells you which package has the command. If ask-marcel-office-write is not installed, tell the user that a draft needs @ask-marcel/office-write, and give the install line below. Do not try another route.

Setup (once)

ask-marcel-office-read --version   # read installed?
ask-marcel-office-auth --version   # auth installed?
ask-marcel-office-write --version  # write installed? (only for drafts)

If one is missing: npm i -g @ask-marcel/office-auth @ask-marcel/office-read @ask-marcel/office-write (Node 20 or later, or Bun 1.0 or later). For read-only use, leave out @ask-marcel/office-write. Then restart the terminal (or load PATH again). Sign in once with ask-marcel-office-auth login: a browser opens one time, and auth keeps the token in its cache. The read and write bins get their tokens from auth. If a command later says that you are not signed in, or that a token has lapsed (people lookups use a separate "elevated" token that lapses on its own), run ask-marcel-office-auth login with no flag. It gets a lapsed token again through silent SSO with the saved browser profile (a window flash, no prompt), and it does a full sign-in only if that fails. Do not reach for --force (ask-marcel-office-auth login --force). Use it only when a tier stays stuck after a plain login: it clears the browser session, so the user usually types the password again.

Only ask-marcel-office 2.8.0 installed? Use it in place of each of the three bins, and use scopes-check in place of status. The three packages are not on npm yet; until then, 2.8.0 is what npm installs.

Workflow

1. Know who you are — once per session:

ask-marcel-office-read my-quick-context

Returns name, job title, tenantTimeZone, and the IDs everything below reuses (primary drive, inbox, primary calendar, planner plan, notebook).

2. Route by question shape. When you can't decide, search Mail + Files both.

Question shapeFirst call
What did A say / status told by mailsearch-mail-messages --query '<kql>'
Find a doc / status in documentssearch-all-files --query '<kql>' — personal OneDrive + shared + every SharePoint/Teams library
Who is X (name known)get-user --user-id '<name>' → pick candidate → get-user --user-id '<guid>' (full profile incl. department and phones)
Who holds role X / tenant-wide person searchmicrosoft-search-query --query '<role> <org>' — person hits match names/company, not job titles; cross-check list-relevant-people and see the role-title pitfall under People
Who do I work with on Xlist-relevant-people
Who wrote / last touched this docget-drive-item-created-by-user / get-drive-item-last-modified-by-user
What changed in my files since Xlist-changed-files --since '<date>' — every library the user can open, newest first; the search index can lag the newest saves
What changed in a document since Xdiff-drive-item-versions --drive-id '<id>' --item-id '<id>' --before '<date>' (without --before: what the last save changed) — only the changed lines; needs the elevated token, so a headless run calls ask-marcel-office-auth login first; add --include-metadata true to see comments and tracked changes; two files (a weekly deck): diff-drive-items
Who can open a SharePoint site or pagelist-sharepoint-site-members --site-id '<id>' — the owning group's people (the fewest who can open it) plus the SharePoint groups and sharing links with their roles
Comments on a document, who was @-mentionedlist-document-comments --drive-id '<id>' --item-id '<id>' — Word, Excel (with the sheet) and PowerPoint
Org treeget-user-manager, list-user-direct-reports (recurse manually)
Team / group membershiplist-joined-teams → list-team-members (roles: owner, guest) or list-team-channel-members --channel-id '<id>' for a private channel's roster; list-groups → list-group-members / list-group-owners for a plain group
What was said in a Teams channellist-joined-teams → list-team-channels --team-id '<id>' → convert-team-channel-messages-to-markdown --team-id '<id>' --channel-id '<id>' --since '7d' (dated transcript, replies nested; omit --since for the newest page); convert-team-channel-message-to-markdown --message-id '<id>' for one thread; list-team-channel-messages and list-team-channel-messages-delta --since '<date>' for the JSON forms. Channel content is not searchable: pick the channel, then read
A screenshot or file shared in a Teams chatmessages carry files (resolve a url with resolve-drive-share-link); pasted images: extract-teams-chat-message-images --chat-id '<id>' --message-id '<id>' with --output-dir
What did X say in a Teams chatfind-chats-with-user --name '<person>' → list-teams-chat-messages --chat-id '<id>' --skip-system true (add --mentions-me true for what was asked of the user; every message carries webUrl); for a bounded read use list-teams-chat-history --chat-id '<id>' --since '<date>'; or list-teams-chats-with-messages for recent chats with bodies inlined. Chat content is not in federated search, so this is the only route
What's on my calendarlist-calendars → list-specific-calendar-view --calendar-id '<id>' --start-date-time '<from>' --end-date-time '<to>' — dates accept today, start-of-week, +7d
Is X free / common slotget-schedule
What's on my platelist-incomplete-todo-tasks (--due-before tomorrow for due today or overdue) + list-incomplete-planner-tasks — neither is in federated search; a group's plans: list-group-planner-plans --group-id '<id>', and a plan's label names: get-planner-plan-details
Meeting notes / decisionssearch-onenote-pages --query '<keyword>' — OneNote search is title-only, so also try Mail + Files
Reply to / answer this emailRead the thread first (Read an email in full), then Draft an email → Reply. The draft goes under the thread's newest substantive message
Forward this to YRead an email in full for what it carries, then Draft an email → Forward
Write / send a new email to YDraft an email → New mail (it will be an unsent draft; the user sends). Resolve Y via the people path first

3. Open the best few in full — newest first, only the handful that look relevant. Emails: Read an email in full. Files: Read a document in full. Both below.

4. Didn't find it, or found a lead? Re-query from another angle — a synonym, a person, a filetype:, a date. If a round surfaced something worth chasing (a name, a project, a referenced doc, an unfamiliar term), follow it. Stop after 4 rounds.

5. Answer from what you read, leading with the current state; name anything you couldn't find. Resolve every distinct person on the thread — sender, To, and Cc — via the people path, so the summary says who each player is, not just what they said. When you state what happens next, name who owns the next action and what the user's own option is. Always end with a Sources footer:

---
Sources:
- [Document name](webUrl?web=1) — last modified YYYY-MM-DD — p.4: "the figure or phrase you actually used"
- Email: "subject" — from Sender, YYYY-MM-DD — what it contributed
- [A file you could NOT open](webUrl?web=1) — inaccessible (per-file permission gap or broken link) — request access to confirm

List any source you could NOT open or read in this footer too — a denied file, a broken or unresolvable link, an unreadable scan — marked as inaccessible. A source you couldn't verify belongs in Sources, not buried in your working notes: that way the user sees the gap and can grant access, rather than assuming you read everything.

When a claim rests on a specific figure or line, cite where it lives — page (PDF / deck slide), sheet name, or cell — plus the short phrase you used, so the user can jump straight to it and check.

Every document link must end with web=1 so it opens in the browser instead of launching the desktop app: append ?web=1, or &web=1 when the webUrl already contains a ? (SharePoint Doc.aspx?sourcedoc=… URLs do).

Search query rules

KQL or free text. Keywords, not sentences (Q3 budget 2025, from:alice subject:invoice, filetype:xlsx roadmap).

  • Exact phrases: put double quotes inside --query when word order matters ('"budget allocation"', 'subject:"project timeline"').
  • Narrow broad search-all-files queries anyway. It aggregates every hit into one response (thousands of items, no pagination); a broad query renders fine but buries you in output — add filetype: or a second keyword, and heed the sizeHint remedies printed on large responses.
  • Past ~5000 hits results truncate silently — narrow rather than paginate.

Read an email in full

Everything in a thread — every message, its attachments, and the SharePoint links in its body. Recipe in references/read-email.md: list the thread, read the newest message with --keep-quoted true, then attachments by size and type, then links.

Read a document in full

Get drive-id + item-id (from a search hit, or resolve-drive-share-link for a sharing URL), then read by type: raw download for PDF/CSV/text, download-drive-item-as-markdown --include-metadata true for Word/Excel/OpenDocument, sheet by sheet for a big workbook, download-drive-item-as-pdf when layout matters, the zip converters for archives, extract-drive-item-images --pages 1-3 for the page images of a scanned PDF. Recipe, id-picking pitfalls, and the formula-error and hand-count rules in references/read-document.md.

Heavy reads — delegate when your harness has subagents

An artifact too big to hold alongside everything else — a long deck, a many-sheet workbook, a zip full of scans — can go to a subagent: hand it the ids and this return contract — structure, key figures with page/sheet/cell locations, short pinpoint quotes, anomalies — and keep only the summary in your context. One subagent per artifact, in parallel. Searching, lead-chasing, synthesis, and every draft stay in the main conversation: leads cross sources, and a draft has one writer. That subagent ships with this skill as agents/ms365-document-reader.md, carrying the recipe and this return contract; copy it into your harness's agent folder to enable it (.claude/agents/ for Claude Code). Without subagents, read inline via references/read-document.md.

Draft an email — the only write

The only write that stays is an UNSENT draft in the Drafts folder (a reply, a forward or a new mail), and ask-marcel-office-write makes it. The bins cannot send mail and cannot delete a draft. The rules below hold whatever the mechanics; the mechanics (flags, HTML body and font, signature, revising an existing draft) are in references/draft-email.md.

  • Approval first. Only text the user handed you verbatim is dictated → create immediately. Anything you composed — "reply saying I agree", "tell them yes", "in my style" — is shown as the exact body text and gets a yes BEFORE the draft exists. Never create a draft whose wording the user hasn't seen.
  • Draft to the right person. Decide who owns the response. If the user owns it, reply on the thread; if a colleague owns it, draft an internal mail to that owner instead of answering the outside sender. Never assert a reporting line ("my team", "your team") without checking it via the people path.
  • Promise only what you found. A "pre-read" or "attached analysis" goes in the draft only after you confirmed it exists.
  • Reply under the thread's newest substantive message, not the one the user happened to mention: list the thread, skip auto-responses and one-line acks, take the newest real message on the branch you are answering, read it, then reply. Reply-all inherits its recipients from that message, so the choice decides who the draft goes to.
  • Revise, never recreate. If the thread already carries an isDraft: true row, update that draft rather than creating a second.
  • Hand over the same way every time: it's in Outlook Drafts, unsent, ready to review and send, with the webLink from the last write's response as a clickable link, and a Sources footer when the substance came from things you searched.

People — pitfalls that change answers

  • The two-step name lookup returns candidates with id, mail, jobTitle, department. Re-query directory users (GUID ids) for the full profile — the default already includes department, phones, and office.
  • A candidate whose id is not a GUID is an external contact: get-user --user-id rejects it with instructions — re-query by their mail as it says.
  • Only the full-profile path — get-user with a GUID / UPN / email — rides the elevated token, which expires independently (preflight it with ask-marcel-office-auth status, no Graph call). Name-search get-user, list-relevant-people, get-user-manager, and list-user-direct-reports run on the basic token and keep working when it's cold: walk the org tree with those first — they carry title, department, and mail — and run a plain ask-marcel-office-auth login only for the fields they lack (phones, office). It gets the elevated token again silently and keeps the signed-in session. When re-auth is impossible (headless run, no browser), answer from the basic-token commands and documents (org chart, signatures) and say which profile fields are missing.
  • Directory fields — jobTitle, officeLocation, department — can lag reality by months (an office that has since moved, a title that changed). Present them as directory values, not ground truth; if the user contradicts one, believe the user. When several candidates are plausible, list them with title + department instead of guessing.
  • Reporting lines are often a matrix: a person can have a primary/solid-line manager and a dotted/functional one. When the directory manager field is empty (common for senior staff), the real line usually lives in an org-chart deck on SharePoint — search for it, and when you report the answer, say which manager is the solid line and which is dotted.
  • Role titles are org-local. "Who is the head of X" rarely maps to a literal title: the same role is a "Director", a "Lead", a "VP", or a local-language title, and it varies by company and country. The person entity of federated search matches names and company, not job titles, so a literal-title query surfaces name lookalikes and misses the real holder. When the literal title misses: search title synonyms, filter list-relevant-people by company, then confirm structurally — who they report to, who reports to them, what their team is called. Answer with the person's actual title and note that nobody holds the literal one.

Known limitations

  • OneNote search is title-substring only, and case-sensitive (budget misses Budget; try both); Teams chat content is not searchable (use the chat commands directly); To Do / Planner need their direct commands.
  • A shared or delegated Exchange mailbox is out of reach: the list-shared-mailbox-* commands answer ErrorAccessDenied for any mailbox but your own.
  • Graph drafts carry no signature automatically — use get-mail-signature (references/draft-email.md). The bins cannot delete a draft: the user deletes it in Outlook.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.