Skip to content

Commit 2f15712

Browse files
authored
Merge pull request #96 from adobecom/feature/outlook-connector
Add Outlook connector via device-code sign-in
2 parents 8784ef1 + 85da6be commit 2f15712

18 files changed

Lines changed: 536 additions & 77 deletions

File tree

.claude/skills/unpack/SKILL.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@ Rebuilds mypa from whatever is currently checked out and replaces `/Applications
3232
xattr -dr com.apple.quarantine /Applications/mypa.app
3333
```
3434

35-
8. **Confirm**`ls -la /Applications | grep -i mypa` and report success, reminding the user to (re)launch it from Applications or Spotlight.
35+
8. **Clean up the build output** — once it's copied into `/Applications`, `dist/` is spent: `rm -rf dist`. Skipping this leaves a second, fully valid `mypa.app` bundle sitting in the repo (`dist/<arch-dir>/mypa.app`), which Spotlight indexes just like the installed one — searching "mypa" then shows two results, and launching the wrong one runs an unregistered, unquarantined copy outside `/Applications`. `dist/` is gitignored build output, entirely regenerated by the next `npm run pack`, so deleting it loses nothing.
36+
37+
9. **Confirm**`ls -la /Applications | grep -i mypa` and report success, reminding the user to (re)launch it from Applications or Spotlight.
3638

3739
## Notes
3840

3941
- This is `npm run pack`, not `npm run dist` — no dmg/zip, no code signing, just the fastest path from source to a runnable local `.app`. If the user wants an actual distributable installer, use `npm run dist` instead.
4042
- `electron-builder`'s packaging step rebuilds native dependencies (`better-sqlite3`) for the correct Electron ABI automatically — no separate `npm run postinstall` needed unless a native dependency itself changed.
4143
- Never touches `~/.mypa/config.json` or `~/.mypa/data.db` — only the app bundle in `/Applications` is replaced, so the user's config and data survive the reinstall.
44+
- Always finish with `dist/` removed (step 8) — don't leave it behind "just in case." It's regenerated on the next run, and leaving it is what causes the duplicate-Spotlight-result problem this skill should not reintroduce.
4245
- Auto-update can't be exercised on a build installed this way: `--dir` mode never writes `Contents/Resources/app-update.yml` (electron-builder only writes it when building the mac zip/dmg target). "Check for Updates" on an unpack-installed app shows a friendly "not installed from a signed release" toast rather than checking anything — that's expected, not a bug.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ A local-first personal assistant for developers, built as a macOS/Linux/Windows
1616
- **Owner identity** — set your name and per-surface handles (GitHub, Slack, Jira, Linear, Notion) so the assistant addresses you as "you" rather than by handle; auto-fills from connected MCP servers with one click
1717
- **Usage dashboard** — detailed token usage and estimated cost breakdown by feature, model, and time period; powered by data the Claude Agent SDK already reports
1818
- **MCP integration** — connect to any MCP server (local stdio process) from a built-in catalog or custom config; auto-import from an existing Claude Code config
19-
- **OAuth integrations** — Notion PKCE and Linear PKCE for enriching routines with live data; GitHub connects via a personal access token instead of OAuth, since org OAuth-app access-control policies can block a device-flow connection
19+
- **OAuth integrations** — Notion PKCE and Linear PKCE for enriching routines with live data; GitHub connects via a personal access token instead of OAuth, since org OAuth-app access-control policies can block a device-flow connection; Outlook (Microsoft 365 email + calendar) connects via a device-code sign-in that the MCP server manages end-to-end, including token refresh
2020

2121
## Prerequisites
2222

docs-dev/code-authoring.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ interface RepoLink {
4646
id: string
4747
localPath: string // absolute path to an existing git checkout
4848
githubRepo?: string // "owner/name", derived from `git remote get-url origin`
49-
jiraProjectKeys: string[] // e.g. ["PROJ"]
49+
jiraProjectKeys: string[] // e.g. ["PROJ"] — auto-derived from git history, not user-entered
5050
defaultBaseBranch: string // derived from origin/HEAD
5151
authoringEnabled: boolean // opt-in per repo; defaults to false for discovered repos
5252
source?: 'discovered' | 'manual'
@@ -55,7 +55,7 @@ interface RepoLink {
5555
}
5656
```
5757

58-
Stored in `AppConfig.repos` (config.json), not the DB — registration is a config mutation, mirroring how MCP servers are configured. `RepoLink`s are **auto-discovered**, not hand-registered: the user configures one or more parent folders (`AppConfig.codeRoots`) in Settings, and `repos.ts` `rescanRepos()` walks each for git checkouts, deriving `githubRepo`/`defaultBaseBranch` and filtering to the configured GitHub-org scope. New discoveries default `authoringEnabled: false` — authoring (mypa opening a real PR against the checkout) is an explicit per-repo opt-in, toggled in the Settings "Repos" section (`ReposSection` in `Settings.tsx`). mypa **never clones a repo itself** — the scanner only reads (`.git` presence, `git remote get-url origin`, `git symbolic-ref .../HEAD`). A `source: 'manual'` link may still exist from before auto-discovery shipped; the scanner never edits or removes those.
58+
Stored in `AppConfig.repos` (config.json), not the DB — registration is a config mutation, mirroring how MCP servers are configured. `RepoLink`s are **auto-discovered**, not hand-registered: the user configures one or more parent folders (`AppConfig.codeRoots`) in Settings, and `repos.ts` `rescanRepos()` walks each for git checkouts, deriving `githubRepo`/`defaultBaseBranch`/`jiraProjectKeys` and filtering to the configured GitHub-org scope. `jiraProjectKeys` is inferred, not typed in by the user — `deriveJiraProjectKeys` scans recent commit subjects/bodies and local branch names for `KEY-123`-style references and keeps any key seen more than once, since a one-off stray mention shouldn't route a whole repo. New discoveries default `authoringEnabled: false` — authoring (mypa opening a real PR against the checkout) is an explicit per-repo opt-in, toggled in the Settings "Repos" section (`ReposSection` in `Settings.tsx`). mypa **never clones a repo itself** — the scanner only reads (`.git` presence, `git remote get-url origin`, `git symbolic-ref .../HEAD`, `git log`, `git for-each-ref`). A `source: 'manual'` link may still exist from before auto-discovery shipped; the scanner never edits or removes those.
5959

6060
`resolveRepoForSignal(signal)` / `resolveRepoForNode(key, url?)` match a signal or graph-node key to a `RepoLink` with `authoringEnabled`, reusing the same owner/repo and Jira-project-key parsing as `deriveContainer` in `memory-graph.ts`.
6161

docs-dev/ipc.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,21 +74,22 @@ Links external repos/projects (GitHub `owner/repo`, Jira project keys) to a loca
7474
| Method | Signature | Description |
7575
|---|---|---|
7676
| `getAll` | `() → RepoLink[]` | List all registered repo links (discovered + any legacy manual ones) |
77-
| `update` | `(id, update: Partial<RepoLink>) → RepoLink` | Patch a repo link — used for the per-repo `authoringEnabled` toggle and `jiraProjectKeys` edits. `localPath`/`githubRepo` are scanner-owned and not user-editable. |
77+
| `update` | `(id, update: Partial<RepoLink>) → RepoLink` | Patch a repo link — used for the per-repo `authoringEnabled` toggle. `localPath`/`githubRepo`/`jiraProjectKeys` are scanner-owned (re-derived on every rescan) and not user-editable. |
7878
| `getCodeRoots` | `() → string[]` | Parent folders mypa scans for local git checkouts |
7979
| `addCodeRoots` | `(paths: string[]) → { roots: string[]; repos: RepoLink[] }` | Adds code roots and immediately rescans; returns the updated roots and repo list |
8080
| `removeCodeRoot` | `(path: string) → { roots: string[]; repos: RepoLink[] }` | Removes a code root and immediately rescans |
8181
| `rescan` | `() → RepoLink[]` | Re-scans all configured code roots on demand (Settings "Rescan" button) |
8282

8383
### `oauth`
8484

85-
OAuth flows for Notion and Linear. GitHub is not an OAuth entry — it's a plain PAT/`api_key` catalog entry (see [mcp-and-oauth.md](mcp-and-oauth.md#github--personal-access-token)).
85+
OAuth/sign-in flows. PKCE for Notion and Linear; device-code sign-in for Outlook. GitHub is not an OAuth entry — it's a plain PAT/`api_key` catalog entry (see [mcp-and-oauth.md](mcp-and-oauth.md#github--personal-access-token)).
8686

8787
| Method | Signature | Description |
8888
|---|---|---|
8989
| `startPkce` | `(provider: 'notion' \| 'linear') → string` | Begin PKCE flow; returns the authorization URL to open in browser |
90+
| `startDeviceLogin` | `(entryId: string, env: Record<string, string>) → void` | Run a catalog entry's own device-code login (currently `outlook`); resolves once sign-in completes. No token is returned — the MCP server manages its own token cache. See [mcp-and-oauth.md](mcp-and-oauth.md#outlook--device-code-flow) |
9091

91-
The redirect URI for PKCE is `mypa://oauth/callback`. The `state` nonce is validated in `oauth.ts` to prevent authorization code injection.
92+
The redirect URI for PKCE is `mypa://oauth/callback`. The `state` nonce is validated in `oauth.ts` to prevent authorization code injection. The device-code user code and verification URL are delivered separately via the `oauth:device-code` push channel (below), not this method's return value.
9293

9394
### `setup`
9495

@@ -209,6 +210,7 @@ Subscribed with `window.electron.on(channel, listener)`. Returns an unsubscribe
209210
| `ambient:chat-message` | `{ intentId: string; chunk: string; done: boolean; error?: string }` | Streaming chunk for an intent's "Chat about it" reply. `done: true` signals completion or error. | **widget + main** |
210211
| `chat:tool-approval-request` | `PendingToolApproval` | An agent stream reached a write tool and is paused waiting for user approval. The renderer should show an inline approval UI. Resolved by calling `window.electron.chat.resolveToolApproval(approvalId, allow, editedInput?)`. | **widget + main** |
211212
| `chat:ask-question` | `PendingQuestion` | The model called the `ask_user` tool and the stream is paused waiting for the user's selection. The renderer shows clickable option chips. Resolved by calling `window.electron.chat.answerQuestion(questionId, answer)`. | **widget + main** |
213+
| `oauth:device-code` | `{ entryId: string; userCode: string; verificationUri: string }` | A device-code login (`oauth.startDeviceLogin`) reached its MSAL device-code prompt. The renderer shows the code and opens `verificationUri` (also auto-opened in the system browser by the main process). | main only |
212214

213215
**`routine:run-started` and `routine:run-completed` are broadcast to both windows** via `broadcast()` in `src/main/windows.ts`. The main window uses them to drive in-app toast notifications. The widget uses them to update its inline run card. All other events remain window-specific.
214216

@@ -292,6 +294,8 @@ Manage PA check-in sessions and their chat threads.
292294
293295
## Changelog
294296
297+
- 2026-07-23 — **Add Outlook device-code sign-in: `oauth.startDeviceLogin` + `oauth:device-code` push channel.** New `IpcApi.oauth.startDeviceLogin(entryId, env)` (channel `oauth:start-device-login`) runs a catalog entry's own login command and resolves once it exits — used by the new `outlook` connector, whose MCP server manages its own Microsoft token cache/refresh rather than mypa performing an OAuth handshake. New push channel `oauth:device-code` (`{ entryId, userCode, verificationUri }`) delivers the MSAL device code to the renderer as it appears in the login process's output. `AppConfig` gains `device_login_at?: Record<string, string>` (display-only "Connected on <date>" timestamp, not a credential). See [mcp-and-oauth.md](mcp-and-oauth.md#outlook--device-code-flow).
298+
295299
- 2026-07-23 — **`repos` namespace switches from manual add/remove to code-root auto-discovery.** `repos.add`/`repos.remove` removed; added `repos.getCodeRoots`, `repos.addCodeRoots`, `repos.removeCodeRoot`, `repos.rescan`. `repos.getAll`/`repos.update` unchanged in shape, but discovered `RepoLink`s now default `authoringEnabled: false` and carry `source`/`lastSeenAt`. See [services.md](services.md#changelog) for the scan/reconciliation details.
296300
297301
- 2026-07-22 — **Removed GitHub OAuth device-flow IPC.** `oauth:start-device`/`oauth:poll-device` channels and their preload bindings (`oauth.startDevice`/`oauth.pollDevice`) are removed — GitHub is now a PAT/`api_key` catalog entry, not OAuth (org OAuth-app access-control policies could block the device flow). `IpcApi.oauth` now only exposes `startPkce` (Notion/Linear). See [mcp-and-oauth.md](mcp-and-oauth.md#github--personal-access-token).

0 commit comments

Comments
 (0)