@patimweb/pi-teams 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,8 @@ from your account, in your name, in the right thread. Multiple Teams accounts
7
7
  and multiple company tenants live side by side, and a configuration file
8
8
  decides exactly which teams, channels, chats and people pi may touch.
9
9
 
10
+ ![pi-teams in the pi coding agent](screenshot.png)
11
+
10
12
  ## Installation
11
13
 
12
14
  ```bash
@@ -19,7 +21,9 @@ pi install /path/to/pi-teams
19
21
 
20
22
  ## Quick start
21
23
 
22
- 1. **Register an app in Entra ID** (once per organization — see below).
24
+ 1. **Register an app in Entra ID** (once per organization) — the step-by-step
25
+ walkthrough, including the redirect URI and public-client settings that sign-in
26
+ needs, is in [docs/entra-app-registration.md](docs/entra-app-registration.md).
23
27
  2. **Configure the account:**
24
28
  ```
25
29
  teams_setup:
@@ -32,12 +36,80 @@ pi install /path/to/pi-teams
32
36
  sign-in page and closes itself when you are done — nothing to type.
33
37
  4. **Check it works:** `/teams-status`, then `/teams-inbox`.
34
38
 
39
+ Full walkthrough with the exact portal paths: **[docs/entra-app-registration.md](docs/entra-app-registration.md)**.
40
+
41
+ ---
42
+
43
+ ## Listen mode
44
+
45
+ Off by default. When it is on, pi polls your chats and turns an incoming message
46
+ into a prompt it answers — as you, in the chat it arrived in.
47
+
48
+ ```
49
+ /teams-listen on # switch it on for the session's account
50
+ /teams-listen status # what is configured, and what is running right now
51
+ /teams-listen off
52
+ ```
53
+
54
+ ```json
55
+ "watch": {
56
+ "enabled": true,
57
+ "intervalSeconds": 60,
58
+ "chats": ["Anna*", "Vertrieb*"],
59
+ "from": ["anna.schmidt@contoso.com"],
60
+ "mentionOnly": false,
61
+ "cooldownSeconds": 300,
62
+ "maxTriggersPerHour": 10
63
+ }
64
+ ```
65
+
66
+ | Key | Default | Meaning |
67
+ |-----|---------|---------|
68
+ | `enabled` | `false` | whether the watcher runs |
69
+ | `intervalSeconds` | `60` | seconds between polls (minimum 15) |
70
+ | `chats` | `[]` | **where** pi listens: glob patterns matched against topic, label, chat ID and participant names. Empty means every recent chat. |
71
+ | `from` | `[]` | **who** pi listens to: glob patterns matched against display name, UPN and e-mail. Empty means any sender. |
72
+ | `mentionOnly` | `false` | only wake where you are mentioned |
73
+ | `cooldownSeconds` | `300` | stay quiet in a chat after waking pi for it |
74
+ | `maxTriggersPerHour` | `10` | hard cap on wakes per hour |
75
+
76
+ Set it globally, or per account (the account level wins field by field).
77
+ `teams_watch` writes the same keys; `/teams-listen` writes to the session's
78
+ account.
79
+
80
+ What it does, and what it deliberately does not:
81
+
82
+ - **Costs a model turn per wake.** That is what the filters and the hourly cap
83
+ are for — narrow `chats` and `from`, and enable it for a handful of
84
+ conversations rather than the whole company.
85
+ - **Never answers the backlog.** The first poll records what is there; only what
86
+ arrives afterwards wakes pi.
87
+ - **Never wakes for its own messages.** pi posts as you, so its own reply comes
88
+ back as "my own message" and stops the loop.
89
+ - **Respects the read rules.** A chat excluded by `permissions.read.chats` is
90
+ not even polled.
91
+ - **Does not bypass the safety model.** A reply it decides to send goes through
92
+ the same safety level and scope rules as one you asked for. At `confirm` you
93
+ approve each one; for hands-free answers, narrow `permissions.write.chats` and
94
+ set `safetyLevel: open` — deliberately, and on an account where you mean it.
95
+ - **Chats only, not channels.** Channel polling needs `ChannelMessage.Read.All`
96
+ (admin consent) and multiplies the calls; answering in channels stays a
97
+ requested action.
98
+
99
+ Why polling and not a webhook: Graph change notifications for chats need a
100
+ publicly reachable HTTPS endpoint plus subscription renewal, which a laptop on a
101
+ home network cannot offer. Polling costs one `/me/chats` call per interval and
102
+ works anywhere.
103
+
35
104
  ---
36
105
 
37
106
  ## Entra ID app registration
38
107
 
39
- pi signs in with the OAuth 2.0 **device authorization grant**, so it never sees
40
- your password and your MFA works normally.
108
+ Full walkthrough: **[docs/entra-app-registration.md](docs/entra-app-registration.md)**.
109
+ The short version, with the two settings that sign-in actually depends on:
110
+
111
+ pi signs in as you with a delegated OAuth 2.0 flow — the browser opens, you sign
112
+ in with your own credentials and MFA, and pi never sees your password.
41
113
 
42
114
  ### 1. Create the registration
43
115
 
@@ -207,8 +279,8 @@ prefer.
207
279
  "accounts": [
208
280
  {
209
281
  "name": "work",
210
- "displayName": "NeoImpulse",
211
- "tenantId": "neoimpulse.onmicrosoft.com",
282
+ "displayName": "Contoso",
283
+ "tenantId": "contoso.onmicrosoft.com",
212
284
  "clientId": "00000000-0000-0000-0000-000000000000",
213
285
  "authMode": "interactive",
214
286
  "safetyLevel": "confirm",
@@ -223,7 +295,7 @@ prefer.
223
295
  "deny": ["*/Announcements"]
224
296
  },
225
297
  "chats": { "allow": ["*"] },
226
- "people": { "deny": ["ceo@neoimpulse.de"] }
298
+ "people": { "deny": ["ceo@contoso.com"] }
227
299
  }
228
300
  },
229
301
  "tenants": [
@@ -337,6 +409,7 @@ teams_permissions:
337
409
  | `teams_status` | Which account is active, who pi acts as, what it may do |
338
410
  | `teams_permissions` | Show, test or change the allow/deny rules |
339
411
  | `teams_doctor` | Diagnose config, sign-in, consent and connectivity |
412
+ | `teams_watch` | Listen mode: status, enable, disable, and the filters for who and where |
340
413
 
341
414
  ### Reading
342
415
 
@@ -354,6 +427,7 @@ teams_permissions:
354
427
  | `teams_find_user` | Directory lookup by name, e-mail or UPN |
355
428
  | `teams_list_files` | Files in a channel's SharePoint folder |
356
429
  | `teams_get_presence` | Your status, a colleague's, or a whole chat's |
430
+ | `teams_availability` | When several people are all free, from calendar free/busy |
357
431
  | `teams_list_meetings` | Calendar events and meetings in a range |
358
432
  | `teams_get_meeting` | One meeting in full |
359
433
 
@@ -366,14 +440,23 @@ teams_permissions:
366
440
  | `teams_send_channel_message` | Post in a channel as you |
367
441
  | `teams_reply_channel_message` | Reply inside an existing thread |
368
442
  | `teams_react` | Add or remove an emoji reaction |
443
+ | `teams_mark_read` | Mark a chat as read (or unread again) — the natural end of a catch-up |
444
+ | `teams_update_message` | Edit a message you already sent |
445
+ | `teams_chat_members` | Add someone to a group chat, or remove them |
369
446
  | `teams_delete_message` | Soft-delete (or restore) a message you sent |
370
- | `teams_create_channel` | Add a channel to a team |
371
447
  | `teams_set_presence` | Set your Teams status |
372
448
  | `teams_set_status_message` | Set the note under your name |
373
449
  | `teams_create_meeting` | Schedule a Teams meeting with invitations |
374
450
  | `teams_update_meeting` | Change a meeting you organize |
451
+ | `teams_respond_invite` | Accept, decline or tentatively accept an invitation |
375
452
  | `teams_cancel_meeting` | Cancel a meeting |
376
453
 
454
+ Editing and deleting apply to **chat** messages. A channel post cannot be
455
+ created, edited or deleted through Graph with the permissions pi asks for
456
+ (`Channel.Create`, `ChannelMessage.ReadWrite`) — that stays in the Teams client.
457
+ Posting and replying in channels, and reacting to channel messages, work as
458
+ usual.
459
+
377
460
  ## Commands
378
461
 
379
462
  | Command | Description |
@@ -381,6 +464,7 @@ teams_permissions:
381
464
  | `/teams-status` | Show the connection and permissions |
382
465
  | `/teams-login` | Sign in to Teams as yourself |
383
466
  | `/teams-inbox` | What needs your attention right now |
467
+ | `/teams-listen` | Listen mode: `on`, `off`, or `status` |
384
468
  | `/teams-permissions` | What pi may and may not do |
385
469
  | `/teams-doctor` | Diagnose the setup |
386
470
 
@@ -393,10 +477,37 @@ teams_permissions:
393
477
  | `/teams-channel-digest` | Decisions, open questions and action items in a channel |
394
478
  | `/teams-standup` | Draft and post a standup update |
395
479
  | `/teams-meeting-prep` | Context and talking points for your next meeting |
480
+ | `/teams-schedule` | Find a time everyone is free and book it |
396
481
  | `/teams-doctor` | Guided troubleshooting |
397
482
 
398
483
  ---
399
484
 
485
+ ## How messages are formatted
486
+
487
+ A message body is treated as **lightweight markdown** and converted to the HTML
488
+ subset Teams renders in a chat bubble:
489
+
490
+ | You write | Teams shows |
491
+ |-----------|-------------|
492
+ | `**bold**`, `*italic*`, `~~struck~~` | bold, italic, struck through |
493
+ | `` `code` ``, fenced blocks | inline code, code block |
494
+ | `- item`, `1. item` | bullet list, numbered list |
495
+ | `[label](https://…)` | link |
496
+ | `# Heading` | a bold line — Teams has no headings in a chat |
497
+
498
+ Tables and images are deliberately **not** translated: Teams renders neither in
499
+ a chat, so passing them through would look worse than plain text. Put a table in
500
+ a file and link it instead.
501
+
502
+ Pass `html: true` on a send tool to bypass the conversion and supply raw HTML
503
+ instead. Everything else is escaped, so a message body cannot inject markup.
504
+
505
+ This is also why pi writes the way it does: see *How to format a Teams message*
506
+ in the bundled skill. Short, answer-first, bullets for anything enumerable — the
507
+ medium is a chat bubble, not a document.
508
+
509
+ ---
510
+
400
511
  ## Addressing things
401
512
 
402
513
  **Channels** — a `Team/Channel` path, or `team` and `channel` separately.
@@ -449,17 +560,25 @@ Data-oriented: plain immutable data, I/O at the edges, pure functions in the
449
560
  middle.
450
561
 
451
562
  - **`src/types.ts`** — domain data as plain interfaces. No behavior.
452
- - **`src/config/index.ts`** — accounts, tenants, the safety cascade, persistence.
563
+ - **`src/config/index.ts`** — accounts, tenants, the safety cascade, listen-mode settings, persistence.
453
564
  - **`src/config/scope.ts`** — allow/deny matching. Pure, and the most heavily
454
565
  tested module in the package.
455
566
  - **`src/auth/`** — MSAL applications, the file-backed token cache, JWT claims.
456
567
  - **`src/graph/client.ts`** — fetch wrapper: bearer token, paging, throttling.
457
568
  - **`src/graph/*.ts`** — one module per resource area, returning domain types.
458
569
  - **`src/graph/mappers.ts`** — Graph JSON → domain types. Pure.
570
+ - **`src/watch/`** — listen mode: the decision rules (`index.ts`, pure), the
571
+ polling loop (`loop.ts`), and the prompt an incoming message turns into
572
+ (`prompt.ts`).
459
573
  - **`src/utils/formatting.ts`** — domain types → display strings. Pure.
574
+ - **`src/utils/richtext.ts`** — markdown → the HTML subset Teams renders. Pure.
575
+ - **`src/utils/slots.ts`** — availability view → the free slots everybody
576
+ shares. Pure, and the part of scheduling that can be wrong without looking
577
+ wrong.
460
578
  - **`src/safety/`** — the three gates and the audit log.
461
579
  - **`src/tools/`** — one module per tool.
462
- - **`src/extension/index.ts`** — registration, commands, the `tool_call` interceptor.
580
+ - **`src/extension/index.ts`** — registration, commands, the `tool_call`
581
+ interceptor, and the listen-mode lifecycle.
463
582
 
464
583
  ## Development
465
584
 
@@ -470,23 +589,30 @@ npm test
470
589
  ```
471
590
 
472
591
  Tests cover the pure logic — scope matching, the config cascade, message body
473
- construction, HTML flattening, the safety gates — and run without a tenant or
474
- a network connection.
592
+ construction, markdown rendering, HTML flattening, free-slot arithmetic, the
593
+ listen-mode decision rules, the safety gates — and run without a tenant or a
594
+ network connection.
475
595
 
476
596
  ## Troubleshooting
477
597
 
478
598
  | Symptom | Cause and fix |
479
599
  |---------|---------------|
600
+ | `AADSTS500113` (no reply address) | The registration has no redirect URI. Add `http://localhost` under *Mobile and desktop applications* — see [docs/entra-app-registration.md](docs/entra-app-registration.md) |
480
601
  | `AADSTS7000218` | "Allow public client flows" is off in the app registration |
481
602
  | `AADSTS65001` | Consent missing — sign in again, or have an admin consent |
482
603
  | `AADSTS50011` (redirect URI mismatch) | Add `http://localhost` under *Mobile and desktop applications*, or pin `loopbackPort` |
483
604
  | `AADSTS50020` | The account is not a member/guest of that tenant, or the app is single-tenant |
605
+ | Signed out after about an hour | The tenant grants no `offline_access`, so there is no refresh token. `teams_doctor` names the missing scope; otherwise sign in again when it expires |
484
606
  | Browser never opens | No browser is reachable here; `teams_doctor` says why. `teams_login mode: device-code` always works |
485
607
  | Browser opens, page never returns | A firewall is blocking the loopback port. Pin `loopbackPort` and allow it, or use the device code flow |
486
608
  | "Not signed in" | Run `teams_login`; the saved session may have been revoked |
609
+ | Status line says "not signed in" although sign-in worked | The line is written at session start and after every auth tool; if it lags, `/teams-status` repaints it |
487
610
  | "Blocked by configuration" | Your own scope rules. `teams_permissions action: test` shows which rule |
488
611
  | "cannot run on an app-only token" | Set `authMode: "interactive"` and run `teams_login` |
489
612
  | 403 reading channel messages | `ChannelMessage.Read.All` needs **admin** consent — see "Who has to approve what" |
613
+ | Listen mode does nothing | `teams_watch action: status` says whether it is enabled and running. Only messages arriving *after* it was switched on wake pi |
614
+ | Listen mode answers too much | Narrow `watch.chats` and `watch.from`, raise `cooldownSeconds`, lower `maxTriggersPerHour` |
615
+ | Listen mode stopped by itself | `teams_watch action: status` shows the last polling error; a revoked session or a sleeping laptop is the usual cause, and it resumes on its own |
490
616
  | Throttled | Graph rate limit; the client retries with back-off, then reports it |
491
617
 
492
618
  Run `teams_doctor` first — it checks all of the above at once.
@@ -0,0 +1,243 @@
1
+ # Entra ID app registration for pi-teams
2
+
3
+ The one-time setup that lets pi sign in as you. Fifteen minutes, once per
4
+ organization, and nothing in it needs a build or a server.
5
+
6
+ Everything below uses placeholders. Wherever you see
7
+
8
+ | Placeholder | Where it comes from |
9
+ |---|---|
10
+ | `<APPLICATION_CLIENT_ID>` | *Overview* → **Application (client) ID** |
11
+ | `<DIRECTORY_TENANT_ID>` | *Overview* → **Directory (tenant) ID** |
12
+ | `<TENANT_DOMAIN>` | e.g. `contoso.onmicrosoft.com`, or your verified domain |
13
+
14
+ …substitute your own values locally. Do not paste real tenant or client IDs into
15
+ a README, a ticket, a screenshot or a chat — they identify your organization,
16
+ and the client ID is half of what an attacker needs to start a sign-in flow
17
+ against your tenant.
18
+
19
+ ---
20
+
21
+ ## 1. Create the registration
22
+
23
+ **Entra admin center** → [entra.microsoft.com](https://entra.microsoft.com) →
24
+ **App registrations** → **New registration**
25
+
26
+ | Field | Value |
27
+ |---|---|
28
+ | Name | `pi-teams` (anything recognizable) |
29
+ | Supported account types | **Accounts in any organizational directory (multitenant)** if pi should also reach guest or customer tenants; **single tenant** is fine for one company |
30
+ | Redirect URI | leave empty here — it goes in step 2, on the right platform |
31
+
32
+ Two things to copy out of **Overview** before moving on:
33
+
34
+ - **Application (client) ID** → `<APPLICATION_CLIENT_ID>`
35
+ - **Directory (tenant) ID** → `<DIRECTORY_TENANT_ID>`
36
+
37
+ ## 2. Authentication — the part that actually decides whether sign-in works
38
+
39
+ **App registrations** → your app → **Authentication**. Two settings matter, and
40
+ they are needed for two different reasons. Both are in this one blade.
41
+
42
+ Direct link, with your own application ID substituted:
43
+
44
+ ```
45
+ https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Authentication/appId/<APPLICATION_CLIENT_ID>
46
+ ```
47
+
48
+ ### 2a. A reply address, for the browser sign-in
49
+
50
+ **Authentication** → **Add a platform** → **Mobile and desktop applications** →
51
+ tick **`http://localhost`** → **Configure**.
52
+
53
+ MSAL picks a free loopback port when the browser sign-in starts, and Entra
54
+ treats the port-less `http://localhost` entry as matching *any* port for a
55
+ public client. One entry therefore covers every run — no per-port registration
56
+ and no fixed port needed.
57
+
58
+ Skipping this is the single most common setup mistake, and it fails *after* the
59
+ user has typed their password, with:
60
+
61
+ ```
62
+ AADSTS500113: No reply address is registered for the application.
63
+ ```
64
+
65
+ If your tenant insists on an exact URI, register e.g. `http://localhost:3000`
66
+ instead and pin the same number in pi (`loopbackPort` under the account, or
67
+ `teams_login port: 3000`).
68
+
69
+ ### 2b. Public client flows, for the device code sign-in
70
+
71
+ Same blade → **Advanced settings** → **Allow public client flows** → **Yes** →
72
+ **Save**.
73
+
74
+ pi uses delegated OAuth flows and never handles a client secret for sign-in, so
75
+ Entra must treat the app as a public client. Without this, sign-in fails with:
76
+
77
+ ```
78
+ AADSTS7000218: The request body must contain the following parameter:
79
+ 'client_assertion' or 'client_secret'.
80
+ ```
81
+
82
+ ## 3. API permissions
83
+
84
+ **App registrations** → your app → **API permissions** → **Add a permission** →
85
+ **Microsoft Graph** → **Delegated permissions**:
86
+
87
+ | Permission | Needed for |
88
+ |---|---|
89
+ | `User.Read`, `User.ReadBasic.All` | identity, looking colleagues up |
90
+ | `Team.ReadBasic.All`, `Channel.ReadBasic.All` | listing teams and channels |
91
+ | `ChannelMessage.Read.All` | reading channel messages and threads |
92
+ | `ChannelMessage.Send` | posting and replying in channels |
93
+ | `ChannelMember.Read.All` | channel member lists |
94
+ | `Chat.ReadWrite` | reading chats, creating chats |
95
+ | `ChatMessage.Send` | sending chat messages |
96
+ | `Presence.ReadWrite`, `Presence.Read.All` | your status, colleagues' availability |
97
+ | `Calendars.ReadWrite` | reading and creating calendar events |
98
+ | `OnlineMeetings.ReadWrite` | Teams meeting links |
99
+ | `Files.Read.All`, `Sites.Read.All` | channel files |
100
+
101
+ `openid`, `profile` and `offline_access` are deliberately absent: MSAL always
102
+ requests them and rejects them in an explicit scope list.
103
+
104
+ **What is not needed:** no client secret, no certificate, no redirect URI beyond
105
+ step 2, no application (as opposed to delegated) permissions.
106
+
107
+ ### Optional: one capability that needs one more permission
108
+
109
+ Everything above is what pi asks for by default. One action stays off until you
110
+ add the scope — to this list **and** to `scopes` in `pi-teams.json` — and sign in
111
+ again:
112
+
113
+ | Permission | Unlocks | Consent |
114
+ |---|---|---|
115
+ | `ChatMember.ReadWrite` | removing someone from a chat (`teams_chat_members`) | user |
116
+
117
+ Without it the tool answers with the exact scope it is missing instead of
118
+ failing at Graph with a consent error. Editing and deleting chat messages,
119
+ adding chat members and answering invitations all work with the default set.
120
+
121
+ **Deliberately not requested:** `Channel.Create` and
122
+ `ChannelMessage.ReadWrite`. They would unlock creating channels and editing or
123
+ deleting a **channel** post, and pi is built not to depend on permissions that
124
+ may never be granted. Channel posts are created, edited and deleted in the Teams
125
+ client; posting, replying and reacting in channels work without them.
126
+
127
+ **Also not covered:** `findMeetingTimes` and anything that reads another person's
128
+ *appointments* need `Calendars.Read.Shared`. `teams_availability` uses
129
+ `getSchedule` instead, which is free/busy only — no extra consent, and no view
130
+ of what people are actually doing.
131
+
132
+ ## 4. Consent
133
+
134
+ **API permissions** → **Grant admin consent** — only for the two scopes that
135
+ require it:
136
+
137
+ | Scope | Consent | Without it |
138
+ |---|---|---|
139
+ | `ChannelMessage.Read.All` | **admin** | cannot read channel messages or threads |
140
+ | `ChannelMember.Read.All` | **admin** | cannot list channel members |
141
+ | everything else above | user | a normal sign-in consent prompt covers it |
142
+
143
+ Without an administrator, drop those two scopes from the configuration and
144
+ everything else still works: chats in full, posting and replying in channels,
145
+ presence, calendar, meetings, directory lookup. See the README, *Who has to
146
+ approve what*, for the reduced scope list.
147
+
148
+ If the tenant has *Users can consent to applications* switched off, even the
149
+ user-consentable scopes need an administrator — usually through the admin
150
+ consent request workflow.
151
+
152
+ ## 5. Give it to pi
153
+
154
+ ```yaml
155
+ teams_setup:
156
+ name: work
157
+ displayName: Contoso
158
+ tenantId: "<DIRECTORY_TENANT_ID or <TENANT_DOMAIN>>"
159
+ clientId: "<APPLICATION_CLIENT_ID>"
160
+ setDefault: true
161
+ ```
162
+
163
+ Or edit `~/.pi/agent/pi-teams.json` directly — the same file, mode `0600`:
164
+
165
+ ```json
166
+ {
167
+ "accounts": [
168
+ {
169
+ "name": "work",
170
+ "tenantId": "<TENANT_DOMAIN>",
171
+ "clientId": "<APPLICATION_CLIENT_ID>",
172
+ "authMode": "interactive",
173
+ "safetyLevel": "confirm"
174
+ }
175
+ ],
176
+ "defaultAccount": "work"
177
+ }
178
+ ```
179
+
180
+ ## 6. Sign in and verify
181
+
182
+ 1. `teams_login` — the browser opens at the Microsoft sign-in page, you sign in
183
+ with your own credentials and MFA, and the page closes itself. Nothing to
184
+ copy or type. Over SSH or in a container, pi falls back to the device code
185
+ flow and shows a short code to enter on any device.
186
+ 2. `teams_status` — who pi acts as, which token is cached, what it may do.
187
+ 3. `teams_doctor` — checks configuration, sign-in, granted scopes and
188
+ connectivity in one go. It reports a missing consent as a **named
189
+ permission**, which is the quickest way to see whether the admin consent of
190
+ step 4 actually landed.
191
+
192
+ A successful sign-in is cached on disk and renewed silently, so this is a
193
+ once-per-account affair — as long as the tenant grants the session a refresh
194
+ token (see below).
195
+
196
+ ## 7. Guest and customer tenants
197
+
198
+ For a tenant that is not your own:
199
+
200
+ 1. the registration must be **multitenant** (step 1),
201
+ 2. your account must be a **guest in that tenant**,
202
+ 3. an administrator **of that tenant** consents to the app once.
203
+
204
+ In practice the third step is the hurdle, not your own tenant. Once it is done:
205
+
206
+ ```yaml
207
+ teams_setup:
208
+ parentAccount: work
209
+ name: customer-alpha
210
+ tenantId: "customeralpha.onmicrosoft.com"
211
+ ```
212
+
213
+ then `teams_login` with `tenant: customer-alpha`, and `teams_doctor` to confirm
214
+ that tenant on its own.
215
+
216
+ ---
217
+
218
+ ## Troubleshooting the registration
219
+
220
+ | Error | Cause | Fix |
221
+ |---|---|---|
222
+ | `AADSTS500113` — *No reply address is registered* | no redirect URI on the registration | step 2a: add `http://localhost` under **Mobile and desktop applications** |
223
+ | `AADSTS50011` — *redirect URI mismatch* | an exact URI is registered that does not match the chosen port | register the port-less `http://localhost`, or pin the port in the same file |
224
+ | `AADSTS7000218` — *client_assertion or client_secret* | **Allow public client flows** is off | step 2b |
225
+ | `AADSTS65001` — *consent required* | permissions added but never consented | sign in again; for the two admin scopes, step 4 |
226
+ | `AADSTS50020` — *not a member of the tenant* | wrong tenant, no guest account, or the app is single-tenant | steps 1 and 7 |
227
+ | `AADSTS700016` — *application not found in directory* | wrong tenant ID or client ID | recheck `<DIRECTORY_TENANT_ID>` and `<APPLICATION_CLIENT_ID>` |
228
+ | signed out after about an hour | the tenant grants no `offline_access` | the app needs a refresh token to renew silently; without it, run `teams_login` when the session expires. `teams_doctor` names the missing scope |
229
+
230
+ Run `teams_doctor` first for anything in this table: it validates the
231
+ configuration, the cached session, the granted scopes and the connection to
232
+ Graph, and it says which of them failed rather than only that something did.
233
+
234
+ ## Rotating or revoking access
235
+
236
+ - **To sign out of pi:** `teams_logout` (per account), or `all: true` for every
237
+ account. This deletes the cached session, not the app.
238
+ - **To revoke pi's access for everyone:** Entra admin center → **Enterprise
239
+ applications** → your app → **Properties** → *User assignment required*, or
240
+ delete the registration. Deleting it invalidates every cached session at once.
241
+ - **To change the registration:** editing scopes or redirect URIs does not touch
242
+ the local configuration — but it does invalidate a *new* consent, so sign in
243
+ again afterwards.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patimweb/pi-teams",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Microsoft Teams integration for pi coding agent — acts fully as the signed-in user across multiple accounts and tenants: chats, channels, meetings, presence, with configurable guardrails",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: Find a time everyone is free and book the meeting
3
+ argument-hint: "[who, what, how long]"
4
+ ---
5
+ Organize a Teams meeting: $@
6
+
7
+ 1. Work out who has to be there. Resolve unclear names with `teams_find_user` —
8
+ never guess an e-mail address. Ask me for anything you cannot derive from the
9
+ request.
10
+
11
+ 2. Run `teams_availability` with those people and the meeting length (30 minutes
12
+ unless I said otherwise). If it finds nothing, widen the window with
13
+ `start`/`end` before giving up, and tell me what you tried.
14
+
15
+ 3. Show me two or three slots that fit, as weekday, date and time. **Do not book
16
+ anything yet.**
17
+
18
+ 4. Once I pick one, create it with `teams_create_meeting` in my time zone, with a
19
+ subject that says what the meeting is about. Confirm the slot, the people and
20
+ the subject in one line. Offer to add two or three lines of context from
21
+ `teams_search_messages` to the invitation body — only if I say yes.
22
+
23
+ Do not invite anyone who was not named, and do not answer any invitation.
package/screenshot.png ADDED
Binary file