Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
929ee07
feat: complete CLI with auth, mail, calendar, contacts, and tests
jeffstall Apr 13, 2026
3517ef6
docs: Azure setup guide, multi-account guide, security model, README,…
jeffstall Apr 13, 2026
83cf8ad
test: add token cache persistence tests and detailed formatter tests
jeffstall Apr 13, 2026
4cb917b
Add --input JSON file support and --body-file for all commands
jeffstall Apr 13, 2026
f872779
Add --format and --output options with markdown/HTML renderers
jeffstall Apr 13, 2026
9c871ec
Replace npm install -g with npm link for development
jeffstall Apr 13, 2026
4fb18c6
Fix .gitignore: remove .vscode/ and package-lock.json from tracking
jeffstall Apr 13, 2026
a47618f
Fix device code auth: robust response handling and better errors
jeffstall Apr 13, 2026
4b8c8f8
Use process.exitCode in auth commands for clean async shutdown
jeffstall Apr 13, 2026
d1a5f75
Add pre-flight diagnostic for device code auth failures
jeffstall Apr 13, 2026
30d57e9
Auto-detect correct authority for personal vs org accounts
jeffstall Apr 13, 2026
beea34a
Remove accidentally committed empty file
jeffstall Apr 13, 2026
35c0f2f
Update AZURE-SETUP: recommend multi-tenant + personal, document auto-…
jeffstall Apr 13, 2026
67a216f
Handle opaque access tokens from personal Microsoft accounts
jeffstall Apr 13, 2026
0862275
Add contact aliases, delegate access (--as), and mail send
jeffstall Apr 13, 2026
994e20b
Add comprehensive code comments to all source files for C# port refer…
jeffstall Apr 13, 2026
b7cac46
Add testing docs, directory structure guide, test scripts
jeffstall Apr 14, 2026
89dd07b
Add watch mode with delta queries for real-time event monitoring
jeffstall Apr 14, 2026
8944aa6
Add C# (.NET 8) implementation mirroring Node.js reference
jeffstall Apr 14, 2026
256d1a4
Add comprehensive code comments to core modules
jeffstall Apr 14, 2026
a4dd1ba
Phase 1: Restructure project layout
jeffstall Apr 14, 2026
af07686
Replace reflection-based JsonSerializer calls with source-generated O…
jeffstall Apr 14, 2026
a1e1158
Add build.ps1 unified build/test/publish script and update .gitignore
jeffstall Apr 14, 2026
2872ae6
Phase 3: Configurable permissions system
jeffstall Apr 14, 2026
f230ee4
Phase 6: Documentation overhaul
jeffstall Apr 14, 2026
4d8694c
Phase 4 & 5: Watch rules engine and SQLite operation logging
jeffstall Apr 14, 2026
42a4962
Phase 7: Test expansion and battle-hardening (501 tests)
jeffstall Apr 14, 2026
399859b
Phase 8: Final cleanup — update copilot instructions for new architec…
jeffstall Apr 14, 2026
8cbf0d9
Fix Node.js ↔ C# token cache interop: IV size and hostname casing
jeffstall Apr 14, 2026
a312da9
Add comprehensive self-hosting guide (docs/self-hosting.md)
jeffstall Apr 14, 2026
21ca394
Extract PowerShell setup scripts from self-hosting guide
jeffstall Apr 14, 2026
9aa99ab
Remove inline Azure CLI GUIDs from self-hosting.md
jeffstall Apr 14, 2026
77df4c7
Add short message IDs (#1, #2, ...) for 120-column display
jeffstall Apr 14, 2026
8291a55
Enable Mail.Send, add version system, add -a shorthand
jeffstall Apr 14, 2026
648544a
Add error classification system with typed errors and global handler
jeffstall Apr 14, 2026
ce6beac
Add doctor health check command
jeffstall Apr 14, 2026
62053b8
Port watch command to .NET with rules engine, delta service, and acti…
jeffstall Apr 14, 2026
6cd3730
Production readiness: versioning, migrations, logging, docs, tests
jeffstall Apr 14, 2026
f59e4e4
Fix NativeAOT SQLite: use provider.e_sqlite3 instead of bundle, repub…
jeffstall Apr 14, 2026
ef9bacf
Add NativeAOT binary tests to catch stale publish artifacts
jeffstall Apr 14, 2026
9778565
Add 'mail watch' as alias for top-level watch command
jeffstall Apr 15, 2026
82b5062
Add global error handling — never show stack traces to users
jeffstall Apr 15, 2026
12acc92
Add robust network and auth error handling for both Node.js and C#
jeffstall Apr 15, 2026
4d7aaff
Fix login process hang and improve auth failure diagnostics
jeffstall Apr 15, 2026
28bc472
Add webhook and fast-poll watch modes for real-time notifications
jeffstall Apr 15, 2026
85f3b7b
Add agents directory, skill scripts, and documentation overhaul
jeffstall Apr 15, 2026
1b376fe
Fix #N hint to show bare numbers work (PowerShell eats # as comment)
jeffstall Apr 15, 2026
6df6be5
Remove # prefix from message list numbering
jeffstall Apr 15, 2026
5b31b63
Add diagnostics documentation (logs, telemetry, doctor)
jeffstall Apr 15, 2026
a62be10
Upgrade System.CommandLine to stable 2.0.5, add log/doctor/upgrade co…
jeffstall Apr 15, 2026
82264cf
docs: expand ARCHITECTURE.md with flow diagrams, module reference, an…
jeffstall Apr 15, 2026
1e8a50b
docs: expand BUILDING, MAKING-CHANGES, TESTING, and REAL-WORLD-VALIDA…
jeffstall Apr 15, 2026
6a59a59
docs: expand COMMON-PITFALLS.md with additional troubleshooting details
jeffstall Apr 15, 2026
3d2b540
docs: expand DIAGNOSTICS, WATCH-MODE, and self-hosting guides
jeffstall Apr 15, 2026
9dc2dd3
docs: refactor usage.md into exhaustive per-command reference pages
jeffstall Apr 15, 2026
8e4636a
fix: E2E tests pass against live Graph API
jeffstall Apr 15, 2026
6b43cbe
Comprehensive integration test documentation and test infrastructure …
jeffstall Apr 15, 2026
3cc6cdb
feat: per-account read-only mode with write guard (Node.js + C#)
jeffstall Apr 15, 2026
4abc1e5
Add pagination --skip to both runtimes, fix unit tests for new return…
jeffstall Apr 15, 2026
f555301
Add cursor-based --page next/prev pagination for large mailboxes
jeffstall Apr 15, 2026
957f0c3
Add page-state unit tests
jeffstall Apr 15, 2026
3f842c1
Add --body-preview and --truncate flags for large email handling
jeffstall Apr 15, 2026
b98bed7
Update docs: pagination, body handling, new pitfalls
jeffstall Apr 15, 2026
d7b0e26
Add HTML-to-text rendering for CLI display
jeffstall Apr 15, 2026
3edafcd
Add HTML-to-text rendering to C# implementation
jeffstall Apr 15, 2026
e72de69
Wire telemetry SQLite persistence for cross-invocation data
jeffstall Apr 15, 2026
ca9ca0e
Fix soak tests to use OUTLOOK_CLI_TEST_ACCOUNT
jeffstall Apr 15, 2026
0f791a9
Add pagination and telemetry integration tests
jeffstall Apr 15, 2026
e309f66
Add mail combination and HTML rendering integration tests
jeffstall Apr 15, 2026
0ce1a98
Add pitfalls 32-36: soak accounts, telemetry persistence, HTML rendering
jeffstall Apr 15, 2026
bc0ff3d
Add enterprise security design doc, fix SECURITY.md inaccuracies, enf…
jeffstall Apr 15, 2026
d19a00b
Add test reporting dashboard and realistic email fixtures
jeffstall Apr 15, 2026
28c7645
Add email attachment support (Node.js + C#)
jeffstall Apr 15, 2026
8367f26
Update docs: error codes reference, enterprise self-hosting, agent pi…
jeffstall Apr 15, 2026
7c67be4
Add test diversity: size spectrum, international content, error scena…
jeffstall Apr 15, 2026
d161df2
Add GitHub Actions CI pipeline: unit tests, NativeAOT builds, integra…
jeffstall Apr 15, 2026
35ee6e0
Add PII redaction engine with --redact flag (Node.js + C#)
jeffstall Apr 15, 2026
78fc54b
Update OpenClaw and NanoClaw SKILL.md: attachments, redaction, pagina…
jeffstall Apr 15, 2026
9f31f2f
Add OneDrive integration: list, upload, download, search, mkdir, share
jeffstall Apr 15, 2026
667015e
Document scope migration: re-auth required when scopes change
jeffstall Apr 15, 2026
a57a328
Fix scope migration: make Files scopes opt-in, add error classification
jeffstall Apr 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Outlook CLI Configuration
# Copy this to .env and fill in your values

# Azure App Registration client ID (required)
OUTLOOK_CLI_CLIENT_ID=your-client-id-here

# Azure AD tenant ID (default: "common" for multi-tenant)
# Use "consumers" for personal Microsoft accounts only
# Use "organizations" for work/school accounts only
# Use a specific tenant GUID for single-tenant apps
OUTLOOK_CLI_TENANT_ID=common

# Passphrase for encrypting token cache (optional)
# If not set, a machine-derived passphrase is used automatically
# Set this if you want to share token caches across machines
# OUTLOOK_CLI_PASSPHRASE=your-secure-passphrase

# Log level: error, warn, info, debug (default: warn)
OUTLOOK_CLI_LOG_LEVEL=warn
120 changes: 120 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Copilot Instructions — outlook-cli

> **For comprehensive agent documentation**, see the `agents/` directory:
> `agents/ARCHITECTURE.md`, `agents/BUILDING.md`, `agents/MAKING-CHANGES.md`,
> `agents/TESTING.md`, `agents/REAL-WORLD-VALIDATION.md`, `agents/COMMON-PITFALLS.md`

## Build, Test, Lint

```bash
npm install # Install dependencies
npm test # Run all tests (vitest)
npm run test:verbose # Detailed test output
npx vitest run test/node/security/crypto.test.js # Run a single test file
npx vitest run -t "should encrypt" # Run tests matching a name
npm run test:watch # Watch mode (re-runs on save)
npm run test:coverage # Coverage report
```

The C# implementation builds separately:

```bash
dotnet build src/dotnet # Build C# implementation
```

CLI integration tests can run against either implementation:

```bash
OUTLOOK_CLI_BIN="dotnet run --project src/dotnet" npm test
```

No linter is configured. No build step is needed for Node.js — it runs directly as ES modules.

## Architecture

This is a CLI for Microsoft Outlook (email + calendar) via the Microsoft Graph API. It has two implementations — a **Node.js reference implementation** (`src/node/`) and a **C# optimized implementation** (`src/dotnet/`) — that share the same command structure, file formats, and CLI integration test suite.

### Request flow

```
bin/outlook-cli.js → src/node/cli/index.js (Commander.js program)
→ src/node/cli/{mail,calendar,contacts,auth,account,watch}.js (command handlers)
→ src/node/graph/client.js (GraphClient: auth, retry, rate-limit, pagination)
→ src/node/auth/msal-client.js → src/node/auth/token-cache.js → src/node/security/crypto.js
→ src/node/security/token-validator.js (defense-in-depth scope check on EVERY request)
→ src/node/graph/{mail,calendar,contacts,delta}.js (Graph API endpoints)
→ src/node/output/render.js → src/node/output/{formatter,markdown,html}.js
```

### Output dispatch (Strategy pattern)

`render.js` dispatches to format-specific modules (`formatter.js`, `markdown.js`, `html.js`). Each module exports functions named after entity types (`mailList`, `mailDetail`, `eventList`, etc.). The `output()` helper resolves the format, renders, and writes to stdout or file.

### Delegate access

`GraphClient.userPath` returns `/me` for own account or `/users/{email}` for delegate access (`--as` flag). All Graph API modules prepend `userPath` to their endpoint paths.

### Watch mode

Uses Microsoft Graph delta queries (not webhooks). Single-account mode stores delta tokens in `~/.outlook-cli/delta-{alias}.json`. Multi-job mode (`--config watches.json`) runs multiple watches with independent schedules, rule-based condition matching, and action execution (move, mark-read, flag, run commands, channel delivery).

### Configurable permissions

Accounts.json supports a `defaults` block inherited by all accounts, `parent` chains for hierarchical config, and `+`/`-` modifiers on allowed/forbidden scope lists. `send_to` whitelists restrict recipients per-account.

### Operation logging (SQLite)

`src/node/db/database.js` manages `~/.outlook-cli/outlook-cli.db` (WAL mode). `src/node/db/logger.js` provides correlation-ID-based operation logging (start/complete/fail).

### Telemetry

`src/node/telemetry/collector.js` provides an in-memory ring buffer (1000 events), optional SQLite persistence, and process snapshots. Disabled by default; enable via `OUTLOOK_CLI_TELEMETRY=1`.

### Node.js ↔ C# interoperability

Both implementations use identical file formats in `~/.outlook-cli/`:
- `cache-{alias}.enc` — AES-256-GCM encrypted MSAL token cache (binary: salt(32) + iv(16) + authTag(16) + ciphertext)
- `accounts.json`, `aliases.json`, `config.json`, `delta-{alias}.json`

The encryption uses PBKDF2-SHA512 with 310,000 iterations. A cache encrypted by Node.js decrypts in C# and vice versa.

## Key Conventions

### Security — CRITICAL

- **`Mail.ReadWrite.All` is a FORBIDDEN scope** — application-level access to all mailboxes is never allowed. `Mail.Send` and `Mail.Send.Shared` are both allowed. The permissions system (configurable via accounts.json) can restrict per-account.
- `validateTokenScopes()` in `src/node/security/token-validator.js` runs on **every token acquisition** as defense-in-depth. It checks both the MSAL result's scopes array and the decoded JWT `scp` claim.
- Personal Microsoft accounts return opaque (non-JWT) tokens. Token validators must handle this gracefully (return valid, validate via MSAL scopes array instead).

### Module patterns

- **ESM only** (`"type": "module"`). All imports must use `.js` extensions.
- **Named exports only** — no default exports. Use `export function` or `export { name }`.
- **Factory functions** for stateful objects: `createGraphClient()`, `createMsalClient()`, `createCachePlugin()`.
- **Singleton with reset**: `AccountManager` uses `getAccountManager()` / `resetAccountManager()` (reset is for test isolation).

### Error handling

- Throw immediately for validation errors with actionable context (e.g., "Run `outlook-cli auth login` to authenticate").
- Network retry: 401 → clear token cache + retry; 429 → wait `Retry-After` seconds + retry; other errors → throw.
- Decryption failures degrade gracefully (log warning, start with empty cache, user re-authenticates).

### Config resolution (last wins)

1. Hardcoded defaults → 2. Environment variables → 3. `~/.outlook-cli/config.json` → 4. Per-account settings in `accounts.json` → 5. CLI flags.

Config file intentionally overrides env vars (see `Object.assign` in `src/node/config.js`).

### Testing conventions

- **Vitest** with Jest-compatible API (`describe`/`it`/`expect`/`vi.mock()`).
- Test files organized by category: `test/node/<module>/`, `test/dotnet/`, `test/shared/`, `test/stress/`.
- Stress tiers: `test/stress/scale/` (@stress), `test/stress/integrity/` (@stress), `test/stress/performance/` (@perf), `test/stress/soak/` (@soak).
- Tests are fully offline — no network calls, no real Microsoft auth. Mock external dependencies with `vi.mock()`.
- Crypto tests use **real encryption** (not mocks) — expect ~300ms overhead per test.
- Tests that write to disk create unique temp directories and clean up in `afterEach`.
- Test names use `should <expected behavior>` format.

### Input merging

Commands support `--input <file>` for JSON parameter files. **CLI flags always override JSON values.** `bodyFile` takes priority over inline `body`. HTML content type is auto-detected from `.html`/`.htm` file extensions.
130 changes: 130 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# CI — Unit Tests + NativeAOT Build
#
# Runs on every push and PR. Fast feedback loop (~2-3 min).
# - Node.js unit tests on Ubuntu + Windows
# - C# build + NativeAOT publish on Windows
# - Test report generation and artifact upload
#
# Integration tests (requiring M365 credentials) run separately
# via integration.yml on manual dispatch or nightly schedule.

name: CI

on:
push:
branches: [main, 'feature/**']
pull_request:
branches: [main]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
# ── Node.js Unit Tests ────────────────────────────────────────────────
unit-tests:
name: Unit Tests (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
node-version: [22]

steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm

- name: Install dependencies
run: npm ci

- name: Run unit tests
run: npm run test:ci

- name: Generate test report
if: always()
run: npm run test:report:markdown > test-results/report.md 2>&1 || true

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }}
path: test-results/
retention-days: 30

- name: Post test summary to PR
if: always() && github.event_name == 'pull_request'
run: |
if [ -f test-results/report.md ]; then
echo "## 🧪 Test Results (${{ matrix.os }})" >> $GITHUB_STEP_SUMMARY
cat test-results/report.md >> $GITHUB_STEP_SUMMARY
fi
shell: bash

# ── C# Build ──────────────────────────────────────────────────────────
dotnet-build:
name: C# Build
runs-on: windows-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Restore dependencies
run: dotnet restore src/dotnet/OutlookCli.csproj

- name: Build (Debug)
run: dotnet build src/dotnet/OutlookCli.csproj --no-restore -warnaserror-

- name: Verify CLI help
run: dotnet run --project src/dotnet -- --help

# ── NativeAOT Publish ─────────────────────────────────────────────────
nativeaot:
name: NativeAOT (${{ matrix.rid }})
runs-on: ${{ matrix.os }}
needs: dotnet-build
strategy:
fail-fast: false
matrix:
include:
- os: windows-latest
rid: win-x64
ext: .exe
- os: ubuntu-latest
rid: linux-x64
ext: ""

steps:
- uses: actions/checkout@v4

- uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

- name: Publish NativeAOT
run: >
dotnet publish src/dotnet/OutlookCli.csproj
-r ${{ matrix.rid }}
--self-contained
/p:PublishAot=true
-o publish/${{ matrix.rid }}

- name: Verify binary runs
run: ./publish/${{ matrix.rid }}/outlook-cli${{ matrix.ext }} --help
shell: bash

- name: Upload binary
uses: actions/upload-artifact@v4
with:
name: outlook-cli-${{ matrix.rid }}
path: publish/${{ matrix.rid }}/
retention-days: 90
Loading
Loading