@patimweb/pi-teams 1.0.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/LICENSE +21 -0
- package/README.md +412 -0
- package/index.ts +1 -0
- package/package.json +61 -0
- package/prompts/teams-catch-up.md +24 -0
- package/prompts/teams-channel-digest.md +23 -0
- package/prompts/teams-doctor.md +21 -0
- package/prompts/teams-meeting-prep.md +22 -0
- package/prompts/teams-standup.md +21 -0
- package/prompts/teams-triage.md +22 -0
- package/skills/teams-collaboration/SKILL.md +143 -0
- package/src/auth/client-credentials.ts +72 -0
- package/src/auth/device-code.ts +276 -0
- package/src/auth/index.ts +105 -0
- package/src/auth/jwt.ts +50 -0
- package/src/auth/token-store.ts +156 -0
- package/src/config/index.ts +685 -0
- package/src/config/scope.ts +272 -0
- package/src/extension/index.ts +336 -0
- package/src/graph/calendar.ts +165 -0
- package/src/graph/chats.ts +159 -0
- package/src/graph/client.ts +234 -0
- package/src/graph/mappers.ts +268 -0
- package/src/graph/me.ts +80 -0
- package/src/graph/messages.ts +383 -0
- package/src/graph/presence.ts +160 -0
- package/src/graph/search.ts +111 -0
- package/src/graph/teams.ts +271 -0
- package/src/safety/audit.ts +57 -0
- package/src/safety/index.ts +186 -0
- package/src/status.ts +90 -0
- package/src/tools/resolve.ts +138 -0
- package/src/tools/shared.ts +124 -0
- package/src/tools/teams-accounts.ts +169 -0
- package/src/tools/teams-create-channel.ts +89 -0
- package/src/tools/teams-create-chat.ts +112 -0
- package/src/tools/teams-delete-message.ts +114 -0
- package/src/tools/teams-doctor.ts +182 -0
- package/src/tools/teams-find-user.ts +64 -0
- package/src/tools/teams-inbox.ts +142 -0
- package/src/tools/teams-list-channels.ts +62 -0
- package/src/tools/teams-list-chats.ts +74 -0
- package/src/tools/teams-list-files.ts +54 -0
- package/src/tools/teams-list-members.ts +72 -0
- package/src/tools/teams-list-teams.ts +65 -0
- package/src/tools/teams-login.ts +199 -0
- package/src/tools/teams-meetings.ts +342 -0
- package/src/tools/teams-permissions.ts +169 -0
- package/src/tools/teams-presence.ts +216 -0
- package/src/tools/teams-react.ts +109 -0
- package/src/tools/teams-read-channel.ts +75 -0
- package/src/tools/teams-read-chat.ts +73 -0
- package/src/tools/teams-read-thread.ts +88 -0
- package/src/tools/teams-search-messages.ts +71 -0
- package/src/tools/teams-send-channel-message.ts +250 -0
- package/src/tools/teams-send-chat-message.ts +152 -0
- package/src/tools/teams-setup.ts +245 -0
- package/src/tools/teams-status.ts +73 -0
- package/src/tools/tool-names.ts +30 -0
- package/src/types.ts +192 -0
- package/src/utils/errors.ts +133 -0
- package/src/utils/formatting.ts +356 -0
- package/tsconfig.json +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Weppelmann
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
# pi-teams
|
|
2
|
+
|
|
3
|
+
Microsoft Teams integration for the [pi coding agent](https://github.com/earendil-works/pi).
|
|
4
|
+
|
|
5
|
+
pi acts **as you**: it reads what you can read, and anything it sends comes
|
|
6
|
+
from your account, in your name, in the right thread. Multiple Teams accounts
|
|
7
|
+
and multiple company tenants live side by side, and a configuration file
|
|
8
|
+
decides exactly which teams, channels, chats and people pi may touch.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# From npm
|
|
14
|
+
pi install npm:@patimweb/pi-teams
|
|
15
|
+
|
|
16
|
+
# From a local checkout during development
|
|
17
|
+
pi install /path/to/pi-teams
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
1. **Register an app in Entra ID** (once per organization — see below).
|
|
23
|
+
2. **Configure the account:**
|
|
24
|
+
```
|
|
25
|
+
teams_setup:
|
|
26
|
+
name: work
|
|
27
|
+
tenantId: contoso.onmicrosoft.com
|
|
28
|
+
clientId: <application (client) ID>
|
|
29
|
+
setDefault: true
|
|
30
|
+
```
|
|
31
|
+
3. **Sign in:** run `/teams-login`. pi shows a code and a URL; open it, enter
|
|
32
|
+
the code, sign in as yourself.
|
|
33
|
+
4. **Check it works:** `/teams-status`, then `/teams-inbox`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Entra ID app registration
|
|
38
|
+
|
|
39
|
+
pi signs in with the OAuth 2.0 **device authorization grant**, so it never sees
|
|
40
|
+
your password and your MFA works normally.
|
|
41
|
+
|
|
42
|
+
### 1. Create the registration
|
|
43
|
+
|
|
44
|
+
Entra admin center → **App registrations** → **New registration**
|
|
45
|
+
|
|
46
|
+
| Field | Value |
|
|
47
|
+
|-------|-------|
|
|
48
|
+
| Name | `pi-teams` (anything you like) |
|
|
49
|
+
| Supported account types | **Accounts in any organizational directory (multitenant)** — required if pi should also reach customer or guest tenants. Single tenant is fine for one company. |
|
|
50
|
+
| Redirect URI | leave empty |
|
|
51
|
+
|
|
52
|
+
Note the **Application (client) ID** and **Directory (tenant) ID**.
|
|
53
|
+
|
|
54
|
+
### 2. Enable public client flows
|
|
55
|
+
|
|
56
|
+
**Authentication** → *Advanced settings* → **Allow public client flows** →
|
|
57
|
+
**Yes**.
|
|
58
|
+
|
|
59
|
+
Without this, sign-in fails with `AADSTS7000218`.
|
|
60
|
+
|
|
61
|
+
### 3. Add delegated permissions
|
|
62
|
+
|
|
63
|
+
**API permissions** → **Add a permission** → **Microsoft Graph** →
|
|
64
|
+
**Delegated permissions**:
|
|
65
|
+
|
|
66
|
+
| Permission | Needed for |
|
|
67
|
+
|-----------|-----------|
|
|
68
|
+
| `User.Read`, `User.ReadBasic.All` | identity, looking colleagues up |
|
|
69
|
+
| `Team.ReadBasic.All`, `Channel.ReadBasic.All` | listing teams and channels |
|
|
70
|
+
| `ChannelMessage.Read.All` | reading channel messages |
|
|
71
|
+
| `ChannelMessage.Send` | posting and replying in channels |
|
|
72
|
+
| `ChannelMember.Read.All` | channel member lists |
|
|
73
|
+
| `Chat.ReadWrite` | reading chats, creating chats |
|
|
74
|
+
| `ChatMessage.Send` | sending chat messages |
|
|
75
|
+
| `Presence.ReadWrite`, `Presence.Read.All` | your status, colleagues' availability |
|
|
76
|
+
| `Calendars.ReadWrite` | reading and creating calendar events |
|
|
77
|
+
| `OnlineMeetings.ReadWrite` | Teams meeting links |
|
|
78
|
+
| `Files.Read.All`, `Sites.Read.All` | channel files |
|
|
79
|
+
| `offline_access` | staying signed in |
|
|
80
|
+
|
|
81
|
+
Some of these require **admin consent**. Click *Grant admin consent* if you
|
|
82
|
+
can, or ask an administrator. `teams_doctor` reports exactly which scopes were
|
|
83
|
+
granted and which were not, so you never have to guess from a 403.
|
|
84
|
+
|
|
85
|
+
To request fewer permissions, set `scopes` on the account — see below.
|
|
86
|
+
|
|
87
|
+
### 4. Guest and customer tenants
|
|
88
|
+
|
|
89
|
+
For a tenant that is not your own:
|
|
90
|
+
|
|
91
|
+
- the registration must be **multitenant**,
|
|
92
|
+
- your account must be a guest in that tenant,
|
|
93
|
+
- an administrator there consents to the app once.
|
|
94
|
+
|
|
95
|
+
Then add it as a tenant beneath your account (`parentAccount`) and sign in
|
|
96
|
+
again against it.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Acting as a user vs. app-only
|
|
101
|
+
|
|
102
|
+
| | `device-code` (default) | `client-credentials` |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| Identity | you | the application |
|
|
105
|
+
| Sign-in | once, interactive | none |
|
|
106
|
+
| Reading chats/channels | yes | only via protected APIs |
|
|
107
|
+
| **Sending messages** | **yes** | **no** — refused with an explanation |
|
|
108
|
+
| Presence, calendar | yes | partially |
|
|
109
|
+
|
|
110
|
+
Microsoft Graph only permits app-only writes to chats and channels for
|
|
111
|
+
migration scenarios (`Teamwork.Migrate.All`), so pi-teams refuses those calls
|
|
112
|
+
up front instead of letting them fail deep inside a Graph request. Use
|
|
113
|
+
`client-credentials` for unattended read-only jobs, `device-code` for
|
|
114
|
+
everything that speaks in your name.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Configuration
|
|
119
|
+
|
|
120
|
+
Everything lives in `~/.pi/agent/pi-teams.json` (mode `0600`, created from a
|
|
121
|
+
template on first run). `teams_setup` writes the same file — use whichever you
|
|
122
|
+
prefer.
|
|
123
|
+
|
|
124
|
+
```json
|
|
125
|
+
{
|
|
126
|
+
"accounts": [
|
|
127
|
+
{
|
|
128
|
+
"name": "work",
|
|
129
|
+
"displayName": "NeoImpulse",
|
|
130
|
+
"tenantId": "neoimpulse.onmicrosoft.com",
|
|
131
|
+
"clientId": "00000000-0000-0000-0000-000000000000",
|
|
132
|
+
"authMode": "device-code",
|
|
133
|
+
"safetyLevel": "confirm",
|
|
134
|
+
"permissions": {
|
|
135
|
+
"read": {
|
|
136
|
+
"teams": { "allow": ["*"] },
|
|
137
|
+
"chats": { "allow": ["*"] }
|
|
138
|
+
},
|
|
139
|
+
"write": {
|
|
140
|
+
"channels": {
|
|
141
|
+
"allow": ["Engineering/*", "Project Alpha/General"],
|
|
142
|
+
"deny": ["*/Announcements"]
|
|
143
|
+
},
|
|
144
|
+
"chats": { "allow": ["*"] },
|
|
145
|
+
"people": { "deny": ["ceo@neoimpulse.de"] }
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"tenants": [
|
|
149
|
+
{
|
|
150
|
+
"name": "customer-alpha",
|
|
151
|
+
"tenantId": "customeralpha.onmicrosoft.com",
|
|
152
|
+
"safetyLevel": "readonly",
|
|
153
|
+
"permissions": {
|
|
154
|
+
"read": { "channels": { "allow": ["Project Alpha/*"] } }
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
]
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "private",
|
|
161
|
+
"tenantId": "othercompany.onmicrosoft.com",
|
|
162
|
+
"clientId": "11111111-1111-1111-1111-111111111111",
|
|
163
|
+
"safetyLevel": "open"
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
"defaultAccount": "work",
|
|
167
|
+
"safetyLevel": "confirm",
|
|
168
|
+
"maxMessages": 25,
|
|
169
|
+
"audit": true
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Accounts and tenants
|
|
174
|
+
|
|
175
|
+
- An **account** is a Teams identity you sign in as. Each has its own saved
|
|
176
|
+
session.
|
|
177
|
+
- A **tenant** beneath an account is another directory that identity reaches:
|
|
178
|
+
a customer, a subsidiary, a guest tenant. It gets its own sign-in, its own
|
|
179
|
+
safety level and its own rules.
|
|
180
|
+
|
|
181
|
+
Every tool takes `account` and `tenant`:
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
teams_send_channel_message:
|
|
185
|
+
account: work
|
|
186
|
+
tenant: customer-alpha
|
|
187
|
+
channel: "Project Alpha/General"
|
|
188
|
+
body: "Sprint review moved to Thursday."
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Safety levels
|
|
192
|
+
|
|
193
|
+
| Level | Effect |
|
|
194
|
+
|-------|--------|
|
|
195
|
+
| `open` | writes go through without asking |
|
|
196
|
+
| `confirm` | every write shows a confirmation with the destination and the exact text (**default**) |
|
|
197
|
+
| `readonly` | writes are blocked entirely |
|
|
198
|
+
|
|
199
|
+
Set globally, per account, or per tenant — the most specific setting wins. So
|
|
200
|
+
your own company can be `confirm` while a customer tenant stays `readonly`.
|
|
201
|
+
|
|
202
|
+
### Scope rules
|
|
203
|
+
|
|
204
|
+
Rules exist per **category** (`teams`, `channels`, `chats`, `people`) and per
|
|
205
|
+
**mode** (`read`, `write`). Category keys written directly in the block apply
|
|
206
|
+
to both modes; a `read` or `write` block refines one of them.
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
"permissions": {
|
|
210
|
+
"channels": { "deny": ["*/Announcements"] },
|
|
211
|
+
"write": { "channels": { "allow": ["Engineering/*"] } }
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- **`deny` accumulates** across global → account → tenant and always wins.
|
|
216
|
+
- **`allow` narrows**: the most specific level that defines one wins. Nothing
|
|
217
|
+
configured means everything the signed-in user can reach.
|
|
218
|
+
- Patterns are case-insensitive globs: `*` matches any run of characters
|
|
219
|
+
(including `/`), `?` matches one.
|
|
220
|
+
- A target matches if **any** of its identifiers does: channels match on
|
|
221
|
+
`Team/Channel`, their name or their ID; chats match on topic, participant
|
|
222
|
+
names and e-mail addresses; people match on name, UPN and e-mail.
|
|
223
|
+
|
|
224
|
+
Check a rule without touching anything:
|
|
225
|
+
|
|
226
|
+
```yaml
|
|
227
|
+
teams_permissions:
|
|
228
|
+
action: test
|
|
229
|
+
category: channels
|
|
230
|
+
mode: write
|
|
231
|
+
target: "Engineering/Announcements"
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Other settings
|
|
235
|
+
|
|
236
|
+
| Key | Default | Meaning |
|
|
237
|
+
|-----|---------|---------|
|
|
238
|
+
| `defaultAccount` | first account | account used when a tool omits `account` |
|
|
239
|
+
| `maxMessages` | `25` | default page size for message listings |
|
|
240
|
+
| `audit` | `true` | append every write to `~/.pi/agent/pi-teams-audit.jsonl` |
|
|
241
|
+
| `scopes` | see above | override the requested Graph scopes per account/tenant |
|
|
242
|
+
| `graphBaseUrl` / `authorityHost` | Microsoft public cloud | sovereign cloud endpoints |
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Tools
|
|
247
|
+
|
|
248
|
+
### Session and configuration
|
|
249
|
+
|
|
250
|
+
| Tool | Description |
|
|
251
|
+
|------|-------------|
|
|
252
|
+
| `teams_setup` | Write an account or tenant to the config file |
|
|
253
|
+
| `teams_login` | Sign in as yourself (device code flow) |
|
|
254
|
+
| `teams_logout` | Remove a saved session |
|
|
255
|
+
| `teams_accounts` | List accounts, switch the default, change a safety level, delete |
|
|
256
|
+
| `teams_status` | Which account is active, who pi acts as, what it may do |
|
|
257
|
+
| `teams_permissions` | Show, test or change the allow/deny rules |
|
|
258
|
+
| `teams_doctor` | Diagnose config, sign-in, consent and connectivity |
|
|
259
|
+
|
|
260
|
+
### Reading
|
|
261
|
+
|
|
262
|
+
| Tool | Description |
|
|
263
|
+
|------|-------------|
|
|
264
|
+
| `teams_inbox` | Chats with new messages plus recent mentions of you |
|
|
265
|
+
| `teams_list_chats` | Recent chats with a preview of the last message |
|
|
266
|
+
| `teams_read_chat` | Messages of one chat |
|
|
267
|
+
| `teams_list_teams` | Teams you belong to |
|
|
268
|
+
| `teams_list_channels` | Channels of a team |
|
|
269
|
+
| `teams_read_channel` | Posts in a channel |
|
|
270
|
+
| `teams_read_thread` | One thread: opener plus every reply |
|
|
271
|
+
| `teams_search_messages` | Full-text search across chats and channels |
|
|
272
|
+
| `teams_list_members` | Members of a team or channel |
|
|
273
|
+
| `teams_find_user` | Directory lookup by name, e-mail or UPN |
|
|
274
|
+
| `teams_list_files` | Files in a channel's SharePoint folder |
|
|
275
|
+
| `teams_get_presence` | Your status, a colleague's, or a whole chat's |
|
|
276
|
+
| `teams_list_meetings` | Calendar events and meetings in a range |
|
|
277
|
+
| `teams_get_meeting` | One meeting in full |
|
|
278
|
+
|
|
279
|
+
### Writing
|
|
280
|
+
|
|
281
|
+
| Tool | Description |
|
|
282
|
+
|------|-------------|
|
|
283
|
+
| `teams_send_chat_message` | Send a chat message as you |
|
|
284
|
+
| `teams_create_chat` | Start a 1:1 or group chat, optionally with the first message |
|
|
285
|
+
| `teams_send_channel_message` | Post in a channel as you |
|
|
286
|
+
| `teams_reply_channel_message` | Reply inside an existing thread |
|
|
287
|
+
| `teams_react` | Add or remove an emoji reaction |
|
|
288
|
+
| `teams_delete_message` | Soft-delete (or restore) a message you sent |
|
|
289
|
+
| `teams_create_channel` | Add a channel to a team |
|
|
290
|
+
| `teams_set_presence` | Set your Teams status |
|
|
291
|
+
| `teams_set_status_message` | Set the note under your name |
|
|
292
|
+
| `teams_create_meeting` | Schedule a Teams meeting with invitations |
|
|
293
|
+
| `teams_update_meeting` | Change a meeting you organize |
|
|
294
|
+
| `teams_cancel_meeting` | Cancel a meeting |
|
|
295
|
+
|
|
296
|
+
## Commands
|
|
297
|
+
|
|
298
|
+
| Command | Description |
|
|
299
|
+
|---------|-------------|
|
|
300
|
+
| `/teams-status` | Show the connection and permissions |
|
|
301
|
+
| `/teams-login` | Start the device code sign-in |
|
|
302
|
+
| `/teams-inbox` | What needs your attention right now |
|
|
303
|
+
| `/teams-permissions` | What pi may and may not do |
|
|
304
|
+
| `/teams-doctor` | Diagnose the setup |
|
|
305
|
+
|
|
306
|
+
## Prompts
|
|
307
|
+
|
|
308
|
+
| Prompt | Description |
|
|
309
|
+
|--------|-------------|
|
|
310
|
+
| `/teams-catch-up` | Summarize what happened while you were away |
|
|
311
|
+
| `/teams-triage` | Work through unanswered messages one by one |
|
|
312
|
+
| `/teams-channel-digest` | Decisions, open questions and action items in a channel |
|
|
313
|
+
| `/teams-standup` | Draft and post a standup update |
|
|
314
|
+
| `/teams-meeting-prep` | Context and talking points for your next meeting |
|
|
315
|
+
| `/teams-doctor` | Guided troubleshooting |
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Addressing things
|
|
320
|
+
|
|
321
|
+
**Channels** — a `Team/Channel` path, or `team` and `channel` separately.
|
|
322
|
+
Names resolve case-insensitively; an ambiguous name is reported, never guessed.
|
|
323
|
+
|
|
324
|
+
```yaml
|
|
325
|
+
teams_read_channel:
|
|
326
|
+
channel: "Engineering/General"
|
|
327
|
+
limit: 20
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Chats** — an ID, a group chat topic, or a participant's name or e-mail. If
|
|
331
|
+
more than one chat matches, the tool lists the candidates instead of picking
|
|
332
|
+
one.
|
|
333
|
+
|
|
334
|
+
```yaml
|
|
335
|
+
teams_send_chat_message:
|
|
336
|
+
chat: "anna@contoso.com"
|
|
337
|
+
body: "Running five minutes late."
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Mentions** — pass `mentions` and write `@Display Name` in the body where the
|
|
341
|
+
mention belongs:
|
|
342
|
+
|
|
343
|
+
```yaml
|
|
344
|
+
teams_send_channel_message:
|
|
345
|
+
channel: "Engineering/General"
|
|
346
|
+
body: "@Anna Schmidt the pipeline is green again."
|
|
347
|
+
mentions: ["anna@contoso.com"]
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Files pi writes
|
|
353
|
+
|
|
354
|
+
| File | Contents | Mode |
|
|
355
|
+
|------|----------|------|
|
|
356
|
+
| `~/.pi/agent/pi-teams.json` | accounts, tenants, safety levels, scope rules | `0600` |
|
|
357
|
+
| `~/.pi/agent/pi-teams-tokens.json` | access and refresh tokens per account+tenant | `0600` |
|
|
358
|
+
| `~/.pi/agent/pi-teams-audit.jsonl` | one line per write: when, who, where, what | `0600` |
|
|
359
|
+
|
|
360
|
+
Both the config and the token cache are written through a private temp file and
|
|
361
|
+
an atomic rename, so an interrupted write cannot truncate them. To revoke
|
|
362
|
+
pi's access entirely, run `teams_logout` with `all: true` and remove the app's
|
|
363
|
+
consent in Entra ID.
|
|
364
|
+
|
|
365
|
+
## Architecture
|
|
366
|
+
|
|
367
|
+
Data-oriented: plain immutable data, I/O at the edges, pure functions in the
|
|
368
|
+
middle.
|
|
369
|
+
|
|
370
|
+
- **`src/types.ts`** — domain data as plain interfaces. No behavior.
|
|
371
|
+
- **`src/config/index.ts`** — accounts, tenants, the safety cascade, persistence.
|
|
372
|
+
- **`src/config/scope.ts`** — allow/deny matching. Pure, and the most heavily
|
|
373
|
+
tested module in the package.
|
|
374
|
+
- **`src/auth/`** — device code flow, client credentials, token cache, JWT claims.
|
|
375
|
+
- **`src/graph/client.ts`** — fetch wrapper: bearer token, paging, throttling.
|
|
376
|
+
- **`src/graph/*.ts`** — one module per resource area, returning domain types.
|
|
377
|
+
- **`src/graph/mappers.ts`** — Graph JSON → domain types. Pure.
|
|
378
|
+
- **`src/utils/formatting.ts`** — domain types → display strings. Pure.
|
|
379
|
+
- **`src/safety/`** — the three gates and the audit log.
|
|
380
|
+
- **`src/tools/`** — one module per tool.
|
|
381
|
+
- **`src/extension/index.ts`** — registration, commands, the `tool_call` interceptor.
|
|
382
|
+
|
|
383
|
+
## Development
|
|
384
|
+
|
|
385
|
+
```bash
|
|
386
|
+
npm install
|
|
387
|
+
npm run typecheck
|
|
388
|
+
npm test
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Tests cover the pure logic — scope matching, the config cascade, message body
|
|
392
|
+
construction, HTML flattening, the safety gates — and run without a tenant or
|
|
393
|
+
a network connection.
|
|
394
|
+
|
|
395
|
+
## Troubleshooting
|
|
396
|
+
|
|
397
|
+
| Symptom | Cause and fix |
|
|
398
|
+
|---------|---------------|
|
|
399
|
+
| `AADSTS7000218` | "Allow public client flows" is off in the app registration |
|
|
400
|
+
| `AADSTS65001` | Consent missing — sign in again, or have an admin consent |
|
|
401
|
+
| `AADSTS50020` | The account is not a member/guest of that tenant, or the app is single-tenant |
|
|
402
|
+
| "Not signed in" | Run `teams_login`; the refresh token may have been revoked |
|
|
403
|
+
| "Blocked by configuration" | Your own scope rules. `teams_permissions action: test` shows which rule |
|
|
404
|
+
| "cannot run on an app-only token" | Set `authMode: "device-code"` and run `teams_login` |
|
|
405
|
+
| 403 on channel messages | `ChannelMessage.Read.All` / `ChannelMessage.Send` not consented |
|
|
406
|
+
| Throttled | Graph rate limit; the client retries with back-off, then reports it |
|
|
407
|
+
|
|
408
|
+
Run `teams_doctor` first — it checks all of the above at once.
|
|
409
|
+
|
|
410
|
+
## License
|
|
411
|
+
|
|
412
|
+
MIT
|
package/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from "./src/extension/index.ts";
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@patimweb/pi-teams",
|
|
3
|
+
"version": "1.0.0",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"microsoft-teams",
|
|
8
|
+
"teams",
|
|
9
|
+
"microsoft-graph",
|
|
10
|
+
"pi-extension"
|
|
11
|
+
],
|
|
12
|
+
"author": "Patrick Weppelmann",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=22.0.0"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/Smotherer007/pi-teams.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/Smotherer007/pi-teams/issues"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "echo 'No build step defined'",
|
|
27
|
+
"test": "node scripts/test.mjs",
|
|
28
|
+
"test:watch": "node scripts/test.mjs --watch",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"typebox": "^1.2.8"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@earendil-works/pi-coding-agent": ">=0.70.0",
|
|
36
|
+
"typebox": ">=1.0.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
40
|
+
"@earendil-works/pi-tui": "^0.80.3",
|
|
41
|
+
"@semantic-release/git": "^10.0.0",
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"semantic-release": "^25.0.0",
|
|
44
|
+
"typescript": "^5.7.0"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"pi": {
|
|
50
|
+
"extensions": [
|
|
51
|
+
"./index.ts"
|
|
52
|
+
],
|
|
53
|
+
"skills": [
|
|
54
|
+
"./skills"
|
|
55
|
+
],
|
|
56
|
+
"prompts": [
|
|
57
|
+
"./prompts"
|
|
58
|
+
],
|
|
59
|
+
"image": "https://raw.githubusercontent.com/Smotherer007/pi-teams/main/screenshot.png"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Summarize what happened in Microsoft Teams while you were away
|
|
3
|
+
argument-hint: "[hours, e.g. 24]"
|
|
4
|
+
---
|
|
5
|
+
Catch me up on Microsoft Teams.
|
|
6
|
+
|
|
7
|
+
1. Run `teams_inbox` with `hours` set to $@ (default 24 if nothing was given).
|
|
8
|
+
|
|
9
|
+
2. For every chat with new messages, run `teams_read_chat` to read the actual
|
|
10
|
+
messages — the preview alone is not enough to judge whether something needs
|
|
11
|
+
an answer.
|
|
12
|
+
|
|
13
|
+
3. For every mention, run `teams_read_thread` (channel) or `teams_read_chat`
|
|
14
|
+
(chat) to get the surrounding context.
|
|
15
|
+
|
|
16
|
+
4. Summarize as:
|
|
17
|
+
- **Needs a reply from me** — who is waiting, on what, and since when
|
|
18
|
+
- **FYI** — decisions, announcements and progress I should know about
|
|
19
|
+
- **Noise** — one line, just the count
|
|
20
|
+
|
|
21
|
+
Keep each item to one or two sentences and name the people involved.
|
|
22
|
+
|
|
23
|
+
5. For anything in "Needs a reply", propose a short draft reply, but do not
|
|
24
|
+
send anything. Ask me which drafts to send.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Summarize what happened in a Teams channel
|
|
3
|
+
argument-hint: "<Team/Channel> [days]"
|
|
4
|
+
---
|
|
5
|
+
Summarize a Microsoft Teams channel: $@
|
|
6
|
+
|
|
7
|
+
1. Resolve the channel with `teams_read_channel` (a `Team/Channel` path works
|
|
8
|
+
directly). If no channel was named, ask which one — do not guess.
|
|
9
|
+
|
|
10
|
+
2. Read the recent posts. For every post that has replies, run
|
|
11
|
+
`teams_read_thread` so the summary reflects the discussion, not just the
|
|
12
|
+
opening message.
|
|
13
|
+
|
|
14
|
+
3. Produce:
|
|
15
|
+
- **Decisions** — what was decided, by whom
|
|
16
|
+
- **Open questions** — what is still unanswered, and who asked
|
|
17
|
+
- **Action items** — who owes what, with any dates mentioned
|
|
18
|
+
- **Notable** — anything else worth knowing
|
|
19
|
+
|
|
20
|
+
4. Reference the people by name and include the `messageId` for anything I
|
|
21
|
+
might want to reply to.
|
|
22
|
+
|
|
23
|
+
Do not post anything in the channel.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Diagnose and fix the Microsoft Teams connection
|
|
3
|
+
---
|
|
4
|
+
Something is wrong with my Teams connection.
|
|
5
|
+
|
|
6
|
+
1. Run `teams_doctor` for every configured account.
|
|
7
|
+
|
|
8
|
+
2. Explain each problem in plain language, and say who can fix it — me or a
|
|
9
|
+
tenant administrator. In particular:
|
|
10
|
+
- missing consent → name the exact Graph scope and what it is needed for
|
|
11
|
+
- "Allow public client flows" disabled → the setting is in Entra ID →
|
|
12
|
+
App registrations → Authentication
|
|
13
|
+
- expired or revoked sign-in → I need to run `teams_login` again
|
|
14
|
+
- a guest tenant that rejects the sign-in → the app registration must be
|
|
15
|
+
multi-tenant and I must be invited to that tenant
|
|
16
|
+
|
|
17
|
+
3. Run `teams_permissions` with `action: "show"` and tell me, in one short
|
|
18
|
+
paragraph, what you are currently allowed to do and what you are not.
|
|
19
|
+
|
|
20
|
+
4. Propose the concrete next step. Do not change my configuration without
|
|
21
|
+
asking first.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Prepare for your next Teams meeting
|
|
3
|
+
argument-hint: "[meeting subject or 'next']"
|
|
4
|
+
---
|
|
5
|
+
Prepare me for a Teams meeting: $@
|
|
6
|
+
|
|
7
|
+
1. Run `teams_list_meetings` to find the meeting. If nothing was specified,
|
|
8
|
+
take the next one starting from now. Show me which meeting you picked.
|
|
9
|
+
|
|
10
|
+
2. Run `teams_get_meeting` for the full details — attendees, agenda, join link.
|
|
11
|
+
|
|
12
|
+
3. Search Teams for context with `teams_search_messages`: the meeting subject,
|
|
13
|
+
and the names of the attendees. Read the relevant threads with
|
|
14
|
+
`teams_read_thread` or `teams_read_chat`.
|
|
15
|
+
|
|
16
|
+
4. Give me:
|
|
17
|
+
- **When and who** — time, join link, attendees and their roles if visible
|
|
18
|
+
- **Context** — what has been discussed about this recently, with dates
|
|
19
|
+
- **Open items** — questions left unanswered in the threads
|
|
20
|
+
- **Suggested talking points** — three to five, grounded in what you read
|
|
21
|
+
|
|
22
|
+
Do not message anyone.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Draft and post a standup update to a Teams channel
|
|
3
|
+
argument-hint: "<Team/Channel>"
|
|
4
|
+
---
|
|
5
|
+
Help me post a standup update to: $@
|
|
6
|
+
|
|
7
|
+
1. If no channel was given, ask for one.
|
|
8
|
+
|
|
9
|
+
2. Read the channel's last few posts with `teams_read_channel` so the update
|
|
10
|
+
fits what the team has been discussing, and so I do not repeat something
|
|
11
|
+
that was already said.
|
|
12
|
+
|
|
13
|
+
3. Ask me for anything you cannot know: what I did yesterday, what I am doing
|
|
14
|
+
today, and what is blocking me. Do not invent progress, and do not carry
|
|
15
|
+
over items from an old update as if they were new.
|
|
16
|
+
|
|
17
|
+
4. Draft the update in my voice — short, plain, no headings unless the channel
|
|
18
|
+
uses them. Match the format of the previous standups in that channel.
|
|
19
|
+
|
|
20
|
+
5. Show me the draft and the exact destination channel. Only after I approve,
|
|
21
|
+
post it with `teams_send_channel_message`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Work through unanswered Teams messages one by one
|
|
3
|
+
argument-hint: "[hours]"
|
|
4
|
+
---
|
|
5
|
+
Triage my Microsoft Teams messages from the last $@ hours (default 24).
|
|
6
|
+
|
|
7
|
+
1. Run `teams_inbox` to find what is waiting.
|
|
8
|
+
|
|
9
|
+
2. For each item, read enough context (`teams_read_chat` or
|
|
10
|
+
`teams_read_thread`) to judge it, then classify it as:
|
|
11
|
+
- **Answer now** — a short reply unblocks someone
|
|
12
|
+
- **Needs me** — requires a decision or information only I have
|
|
13
|
+
- **Delegate** — someone else should handle it; say who
|
|
14
|
+
- **Ignore** — no action needed
|
|
15
|
+
|
|
16
|
+
3. Present the list as a table: who, what, classification, and the chat or
|
|
17
|
+
channel it lives in.
|
|
18
|
+
|
|
19
|
+
4. For everything in "Answer now", draft a reply in my voice. Show me all the
|
|
20
|
+
drafts together, then send only the ones I approve, one tool call each.
|
|
21
|
+
|
|
22
|
+
5. Do not react, reply or change anything before I approve it.
|