@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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +412 -0
  3. package/index.ts +1 -0
  4. package/package.json +61 -0
  5. package/prompts/teams-catch-up.md +24 -0
  6. package/prompts/teams-channel-digest.md +23 -0
  7. package/prompts/teams-doctor.md +21 -0
  8. package/prompts/teams-meeting-prep.md +22 -0
  9. package/prompts/teams-standup.md +21 -0
  10. package/prompts/teams-triage.md +22 -0
  11. package/skills/teams-collaboration/SKILL.md +143 -0
  12. package/src/auth/client-credentials.ts +72 -0
  13. package/src/auth/device-code.ts +276 -0
  14. package/src/auth/index.ts +105 -0
  15. package/src/auth/jwt.ts +50 -0
  16. package/src/auth/token-store.ts +156 -0
  17. package/src/config/index.ts +685 -0
  18. package/src/config/scope.ts +272 -0
  19. package/src/extension/index.ts +336 -0
  20. package/src/graph/calendar.ts +165 -0
  21. package/src/graph/chats.ts +159 -0
  22. package/src/graph/client.ts +234 -0
  23. package/src/graph/mappers.ts +268 -0
  24. package/src/graph/me.ts +80 -0
  25. package/src/graph/messages.ts +383 -0
  26. package/src/graph/presence.ts +160 -0
  27. package/src/graph/search.ts +111 -0
  28. package/src/graph/teams.ts +271 -0
  29. package/src/safety/audit.ts +57 -0
  30. package/src/safety/index.ts +186 -0
  31. package/src/status.ts +90 -0
  32. package/src/tools/resolve.ts +138 -0
  33. package/src/tools/shared.ts +124 -0
  34. package/src/tools/teams-accounts.ts +169 -0
  35. package/src/tools/teams-create-channel.ts +89 -0
  36. package/src/tools/teams-create-chat.ts +112 -0
  37. package/src/tools/teams-delete-message.ts +114 -0
  38. package/src/tools/teams-doctor.ts +182 -0
  39. package/src/tools/teams-find-user.ts +64 -0
  40. package/src/tools/teams-inbox.ts +142 -0
  41. package/src/tools/teams-list-channels.ts +62 -0
  42. package/src/tools/teams-list-chats.ts +74 -0
  43. package/src/tools/teams-list-files.ts +54 -0
  44. package/src/tools/teams-list-members.ts +72 -0
  45. package/src/tools/teams-list-teams.ts +65 -0
  46. package/src/tools/teams-login.ts +199 -0
  47. package/src/tools/teams-meetings.ts +342 -0
  48. package/src/tools/teams-permissions.ts +169 -0
  49. package/src/tools/teams-presence.ts +216 -0
  50. package/src/tools/teams-react.ts +109 -0
  51. package/src/tools/teams-read-channel.ts +75 -0
  52. package/src/tools/teams-read-chat.ts +73 -0
  53. package/src/tools/teams-read-thread.ts +88 -0
  54. package/src/tools/teams-search-messages.ts +71 -0
  55. package/src/tools/teams-send-channel-message.ts +250 -0
  56. package/src/tools/teams-send-chat-message.ts +152 -0
  57. package/src/tools/teams-setup.ts +245 -0
  58. package/src/tools/teams-status.ts +73 -0
  59. package/src/tools/tool-names.ts +30 -0
  60. package/src/types.ts +192 -0
  61. package/src/utils/errors.ts +133 -0
  62. package/src/utils/formatting.ts +356 -0
  63. package/tsconfig.json +16 -0
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Token cache — `~/.pi/agent/pi-teams-tokens.json`, mode 0600.
3
+ *
4
+ * One entry per account+tenant. Holds the access token, its expiry, the
5
+ * refresh token, the granted scopes, and who the token belongs to, so
6
+ * `teams_status` can say *which user* pi is currently acting as without a
7
+ * network round-trip.
8
+ *
9
+ * Writes go through a private temp file and an atomic rename: the file holds
10
+ * long-lived refresh tokens, and a truncated cache would silently sign the
11
+ * user out.
12
+ */
13
+
14
+ import {
15
+ existsSync,
16
+ readFileSync,
17
+ writeFileSync,
18
+ mkdirSync,
19
+ chmodSync,
20
+ statSync,
21
+ renameSync,
22
+ unlinkSync,
23
+ } from "node:fs";
24
+ import { dirname, join } from "node:path";
25
+ import { getAgentDir } from "../config/index.ts";
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Types
29
+ // ---------------------------------------------------------------------------
30
+
31
+ export interface CachedToken {
32
+ /** Bearer token */
33
+ accessToken: string;
34
+ /** Epoch milliseconds when the access token expires */
35
+ expiresAt: number;
36
+ /** Refresh token — absent for client-credentials */
37
+ refreshToken?: string;
38
+ /** Scopes the token was actually granted */
39
+ scopes: string[];
40
+ /** Which flow produced this token */
41
+ authMode: "device-code" | "client-credentials";
42
+ /** Signed-in identity (delegated tokens only) */
43
+ user?: {
44
+ id?: string;
45
+ displayName?: string;
46
+ upn?: string;
47
+ tenantId?: string;
48
+ };
49
+ /** When the entry was last written */
50
+ updatedAt: string;
51
+ }
52
+
53
+ interface TokenFile {
54
+ version: 1;
55
+ tokens: Record<string, CachedToken>;
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Paths & keys
60
+ // ---------------------------------------------------------------------------
61
+
62
+ export function getTokenPath(): string {
63
+ return join(getAgentDir(), "pi-teams-tokens.json");
64
+ }
65
+
66
+ /** Cache key for an account+tenant pair. */
67
+ export function tokenKey(account: string, tenantId: string): string {
68
+ return `${account.toLowerCase()}::${tenantId.toLowerCase()}`;
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // I/O
73
+ // ---------------------------------------------------------------------------
74
+
75
+ function readFile(): TokenFile {
76
+ const path = getTokenPath();
77
+ if (!existsSync(path)) return { version: 1, tokens: {} };
78
+ try {
79
+ try {
80
+ const mode = statSync(path).mode & 0o777;
81
+ if (mode !== 0o600) chmodSync(path, 0o600);
82
+ } catch {
83
+ /* ignore */
84
+ }
85
+ const parsed = JSON.parse(readFileSync(path, "utf-8")) as TokenFile;
86
+ if (!parsed || typeof parsed !== "object" || !parsed.tokens) return { version: 1, tokens: {} };
87
+ return parsed;
88
+ } catch {
89
+ return { version: 1, tokens: {} };
90
+ }
91
+ }
92
+
93
+ function writeFile(data: TokenFile): void {
94
+ const path = getTokenPath();
95
+ const dir = dirname(path);
96
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });
97
+
98
+ const tmpPath = `${path}.${process.pid}.tmp`;
99
+ try {
100
+ writeFileSync(tmpPath, `${JSON.stringify(data, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
101
+ chmodSync(tmpPath, 0o600);
102
+ renameSync(tmpPath, path);
103
+ } catch (err) {
104
+ try {
105
+ unlinkSync(tmpPath);
106
+ } catch {
107
+ /* ignore */
108
+ }
109
+ throw err;
110
+ }
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Access
115
+ // ---------------------------------------------------------------------------
116
+
117
+ export function getToken(account: string, tenantId: string): CachedToken | undefined {
118
+ return readFile().tokens[tokenKey(account, tenantId)];
119
+ }
120
+
121
+ export function saveToken(account: string, tenantId: string, token: CachedToken): void {
122
+ const file = readFile();
123
+ file.tokens[tokenKey(account, tenantId)] = { ...token, updatedAt: new Date().toISOString() };
124
+ writeFile(file);
125
+ }
126
+
127
+ export function deleteToken(account: string, tenantId: string): boolean {
128
+ const file = readFile();
129
+ const key = tokenKey(account, tenantId);
130
+ if (!file.tokens[key]) return false;
131
+ delete file.tokens[key];
132
+ writeFile(file);
133
+ return true;
134
+ }
135
+
136
+ /** Drop every cached token — used by `teams_logout` with `all: true`. */
137
+ export function clearAllTokens(): number {
138
+ const file = readFile();
139
+ const count = Object.keys(file.tokens).length;
140
+ writeFile({ version: 1, tokens: {} });
141
+ return count;
142
+ }
143
+
144
+ export function listTokens(): Array<{ key: string; token: CachedToken }> {
145
+ const file = readFile();
146
+ return Object.entries(file.tokens).map(([key, token]) => ({ key, token }));
147
+ }
148
+
149
+ /**
150
+ * Is the token still usable?
151
+ * A 5-minute skew keeps a long Graph call from failing mid-flight.
152
+ */
153
+ export function isFresh(token: CachedToken | undefined, skewMs = 5 * 60 * 1000): boolean {
154
+ if (!token?.accessToken) return false;
155
+ return token.expiresAt - skewMs > Date.now();
156
+ }