Repository navigation
Add offline M365 Copilot (Cowork) plugin build and release workflow - #55
Conversation
Microsoft 365 Copilot Cowork runs skill scripts in a sandbox with Node but no network, so the skill's npx workflow can't run there. Instead of a separate snapshot script, reuse the real CLI: - MSEVENTS_OFFLINE=1 makes the CLI read its cache without fetching, revalidating, or writing. Missing events return no sessions with a stderr note; refresh exits 1; status marks entries as offline. - npm run build:snapshot downloads the catalogs, bundles the whole CLI into skills/microsoft-build/scripts/msevents.mjs with esbuild (banner forces offline mode and points MSEVENTS_CACHE_DIR at ../data), writes the cache files as a frozen data/ folder, renders the hand-written cli/snapshot-skill/SKILL.md template, and optionally packages the plugin with atk (--package). - The Cowork template tells the agent not to volunteer that the data is bundled or when it was downloaded. It adds a "check the event site for the latest schedule" tip for times and rooms, and gives the download date truthfully if the user asks how current the data is. - npm run smoke:snapshot checks the bundle against the live CLI with network disabled and read-only data; CI runs it. The repo skill (skills/microsoft-build/SKILL.md) is unchanged. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dc8d0c3c-e6a7-4a74-bee9-cf99887b72b5
There was a problem hiding this comment.
🟡 Changes recommended
Packaging interprets output paths through a shell, allowing unintended command execution.
1 open finding
What changed in this PR
Adds read-only offline catalog access to the CLI and packages it for Microsoft 365 Copilot Cowork.
Changes:
- Adds offline cache behavior and status reporting.
- Builds a bundled CLI with frozen catalogs and a dedicated skill template.
- Adds offline tests, CI smoke coverage, and usage documentation.
| File | Description |
|---|---|
| README.md | Adds Cowork installation guidance. |
| cli/test/offline.test.ts | Tests offline behavior. |
| cli/src/output/format.ts | Reports offline status. |
| cli/src/index.ts | Supports bundled version metadata. |
| cli/src/config.ts | Detects offline mode. |
| cli/src/commands/status.ts | Passes offline state to formatting. |
| cli/src/commands/refresh.ts | Rejects offline refreshes. |
| cli/src/commands/common.ts | Reads cached sessions without network access. |
| cli/snapshot-skill/SKILL.md | Defines offline agent workflows. |
| cli/scripts/smoke-snapshot.mjs | Checks bundle behavior and CLI parity. |
| cli/scripts/build-snapshot.mjs | Builds and packages frozen catalogs. |
| cli/README.md | Documents offline usage and packaging. |
| cli/package.json | Adds snapshot scripts and esbuild. |
| cli/package-lock.json | Locks bundling dependencies. |
| AGENTS.md | Documents snapshot maintenance requirements. |
| .github/workflows/ci.yml | Runs snapshot smoke checks. |
Files not reviewed (1)
- cli/package-lock.json: Generated file
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Output paths were interpolated into a shell command, so names containing $(...), backticks or %VAR% were expanded before reaching atk. Start atk with execFile instead. On Windows, npm installs atk as a .cmd shim that Node can't start without cmd.exe, so run the package's JS entry with node. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dc8d0c3c-e6a7-4a74-bee9-cf99887b72b5
There was a problem hiding this comment.
🔵 Needs a closer look
The live-download, Cowork packaging, and installation workflow still needs end-to-end human validation.
0 open findings
1 resolved since last review
Files not reviewed (1)
- cli/package-lock.json: Generated file
🧠 Review effort: Balanced
The esbuild entries added with the offline snapshot build resolved to an internal Azure Artifacts feed with sha1 integrity. Point them at registry.npmjs.org with sha512 integrity so the lockfile matches main's registry; tarballs are byte-identical. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dc8d0c3c-e6a7-4a74-bee9-cf99887b72b5
Rename build:snapshot/smoke:snapshot to build:m365/smoke:m365, cli/snapshot-skill to cli/m365-skill, and default output to out/m365-plugin. Add the M365 plugin workflow. A manual run on main takes a version, which must be higher than the last m365-v* tag, builds the package with live catalogs, and publishes release m365-v<version> with microsoft-build-m365.zip attached, marked Latest. The M365 plugin is versioned separately from the plugin manifests because catalog refreshes need new releases too. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dc8d0c3c-e6a7-4a74-bee9-cf99887b72b5
The test hard-coded the uncached event names, so it failed once main added Ignite 2026. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dc8d0c3c-e6a7-4a74-bee9-cf99887b72b5
Resolve the cli/package-lock.json conflict by keeping this branch's esbuild entries with the CLI version from main (0.3.2). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dc8d0c3c-e6a7-4a74-bee9-cf99887b72b5
Replace the hand-written cli/m365-skill/SKILL.md with cli/m365-skill/offline-overlay.md. build:m365 now generates the Cowork SKILL.md from skills/microsoft-build/SKILL.md: the overlay replaces the catalog-access section and offline frontmatter, SKILL_REWRITES swap the remaining online steps, and the build fails if a rewrite stops matching or online wording is left. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dc8d0c3c-e6a7-4a74-bee9-cf99887b72b5
| @@ -1,12 +1,12 @@ | |||
| import { KNOWN_EVENTS } from '../config.js'; | |||
| import { KNOWN_EVENTS, OFFLINE_ENV_VAR, isOfflineMode } from '../config.js'; | |||
There was a problem hiding this comment.
could you add some evidences in the comments that show offlinemode and onlien mode both works after this change?
There was a problem hiding this comment.
Thanks Osmond Jiang (@OsmondJiang). Below are the evidences for online and offline modes.
Online mode: GitHub Copilot and Claude Code plugins
| Check | Result |
|---|---|
npm run smoke:live |
✅ live download of Build 2026, 461 sessions |
sessions --query agents --event build-2026 |
✅ ODSP926, … |
sessions --tech "Azure Cosmos DB" --event build-2026 |
✅ OD820, LTG411 |
sessions --query agents --event ignite-2026 |
✅ LTG1172 (1,037 sessions) |
sessions --query agents (all events) |
✅ results from Build 2026, and status shows Ignite 2026 cached |
refresh --event build-2026 |
✅ conditional GET, 304 Not Modified |
Offline mode in Cowork
Lets a release bundle only the listed events, for example when a past event's catalog is no longer downloadable. Empty bundles every supported event. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dc8d0c3c-e6a7-4a74-bee9-cf99887b72b5
There was a problem hiding this comment.
🔵 Needs a closer look
The release workflow has not run end to end, so publishing and installation of the released Cowork package need final human validation.
No open issues 🎉
1 file not reviewed
cli/package-lock.json: Generated file
Review effort: Balanced
…ip in CI The offline skill now adds one short note with the snapshot date the first time it recommends sessions or gives times or rooms, instead of never mentioning the snapshot unprompted. smoke:m365 gains a --package mode that builds the zip with atk, unzips it, checks the skill files match the plugin folder byte for byte, and runs the offline checks against the unzipped skill. CI installs atk and runs this mode. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dc8d0c3c-e6a7-4a74-bee9-cf99887b72b5

What does this PR do?
Adds an offline Microsoft 365 Copilot (Cowork) plugin for the Microsoft Events skill, and a workflow that publishes it as a release zip. Cowork runs skill scripts in a sandbox with Node.js but no network access, so the skill's usual
npx -y @microsoft/events-clicommands can't run there. Instead, the plugin includes the CLI and a frozen copy of the session catalogs. The Learn MCP connector still works, because Cowork calls MCP connectors from outside the sandbox.MSEVENTS_OFFLINE=1): the CLI only reads its cache. It doesn't fetch, revalidate or write. For an event that isn't cached, it returns no sessions and prints a note to stderr.refreshexits with code 1, andstatusmarks entries as offline.npm run build:m365(cli/scripts/build-m365-plugin.mjs):data/folder.scripts/msevents.mjs. The bundle always runs in offline mode and reads its cache fromdata/.cli/m365-skill/SKILL.mdtemplate.--package, also builds the app package zip withatk, run without a shell.out/m365-plugin, which is git-ignored.cli/m365-skill/SKILL.md, under 20,000 characters): uses onlynode scripts/msevents.mjscommands. The agent doesn't mention that the data is bundled, and suggests checking the event site for the latest times and rooms.npm run smoke:m365: builds the plugin from a fixture with the network disabled and the data read-only, then checks that the bundle's output matchesmsevents. It's added to CI..github/workflows/m365-plugin.yml):mainwith a version (x.y.z, higher than the lastm365-v*tag).m365-v<version>withmicrosoft-build-m365.zipattached, marked Latest. The README links toreleases/latest/download/microsoft-build-m365.zip.contents: write.AGENTS.md,README.mdandcli/README.mdare updated.The repo skill (
skills/microsoft-build/SKILL.md) is unchanged, so this PR doesn't bump the plugin manifest versions.Validation (from
cli/):npm ci: passednpm run build: passednpm test: passed (7 files, 67 tests, including 15 new tests intest/offline.test.ts)npm run smoke:fixture: passednpm run smoke:m365: passed ("M365 plugin smoke passed (10 commands match the CLI).")npm run build:m365 -- --plugin-version 1.0.6 --package: passed on Windows, and in anode:22Linux container that repeats the workflow's build steps. Earlier builds were installed and tested in Cowork.m365-plugin.ymlandci.yml: passednpm run smoke:live, and the M365 plugin workflow itself, which can only run frommain. Not yet tested with the Ignite 2026 event from Add Ignite 2026 event to CLI #57; the branch merges cleanly withmain.Note
This project is maintained by a small team for Microsoft Build. We may not be able to review or accept all contributions — but we appreciate the effort!
Before submitting a PR, consider opening an issue or discussion first. This helps us align on direction before you invest time in code.
Checklist