msteams 0.1.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 +107 -0
- package/dist/TeamsClient-D4-exLIC.mjs +225 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +595 -0
- package/dist/index.d.mts +640 -0
- package/dist/index.mjs +3 -0
- package/package.json +32 -0
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as TokenManager, t as TeamsClient } from "../TeamsClient-D4-exLIC.mjs";
|
|
3
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
//#region src/cli/constants.ts
|
|
7
|
+
const DEFAULT_PROFILE_NAME = "default";
|
|
8
|
+
const DEFAULT_PROFILE_DIR = ".teams-cli";
|
|
9
|
+
const DEFAULT_MESSAGE_LIMIT = 20;
|
|
10
|
+
const ANSI_CODES = {
|
|
11
|
+
reset: "\x1B[0m",
|
|
12
|
+
bold: "\x1B[1m",
|
|
13
|
+
dim: "\x1B[2m",
|
|
14
|
+
red: "\x1B[31m",
|
|
15
|
+
green: "\x1B[32m",
|
|
16
|
+
yellow: "\x1B[33m",
|
|
17
|
+
cyan: "\x1B[36m",
|
|
18
|
+
brightCyan: "\x1B[96m"
|
|
19
|
+
};
|
|
20
|
+
const SPINNER_FRAMES = [
|
|
21
|
+
"⠋",
|
|
22
|
+
"⠙",
|
|
23
|
+
"⠹",
|
|
24
|
+
"⠸",
|
|
25
|
+
"⠼",
|
|
26
|
+
"⠴",
|
|
27
|
+
"⠦",
|
|
28
|
+
"⠧",
|
|
29
|
+
"⠇",
|
|
30
|
+
"⠏"
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/cli/output.ts
|
|
35
|
+
function createContext(options) {
|
|
36
|
+
const interactive = process.stdout.isTTY === true && process.stderr.isTTY === true;
|
|
37
|
+
return {
|
|
38
|
+
machine: options.jsonOutput || !interactive,
|
|
39
|
+
color: interactive && !options.noColor && !options.jsonOutput
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async function withSpinner(context, label, task) {
|
|
43
|
+
if (context.machine) return task();
|
|
44
|
+
let frame = 0;
|
|
45
|
+
const timer = setInterval(() => {
|
|
46
|
+
process.stdout.write(`\r${SPINNER_FRAMES[frame]} ${label}`);
|
|
47
|
+
frame = (frame + 1) % SPINNER_FRAMES.length;
|
|
48
|
+
}, 80);
|
|
49
|
+
try {
|
|
50
|
+
const result = await task();
|
|
51
|
+
clearInterval(timer);
|
|
52
|
+
process.stdout.write(`\r${ANSI_CODES.green}✓${ANSI_CODES.reset} ${label}\n`);
|
|
53
|
+
return result;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
clearInterval(timer);
|
|
56
|
+
process.stdout.write(`\r${ANSI_CODES.red}x${ANSI_CODES.reset} ${label}\n`);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function printHelp(context, command, compact = false) {
|
|
61
|
+
const colorize = (color, text) => context.color ? `${ANSI_CODES[color]}${text}${ANSI_CODES.reset}` : text;
|
|
62
|
+
if (command === "messages") {
|
|
63
|
+
console.log("Usage:");
|
|
64
|
+
console.log(" teams messages <conversationId> [--limit <n>]");
|
|
65
|
+
console.log(" Fetch messages in a conversation");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (command === "notifications") {
|
|
69
|
+
console.log("Usage:");
|
|
70
|
+
console.log(" teams notifications [--limit <n>]");
|
|
71
|
+
console.log(" Fetch latest notifications");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (command === "channel" || command === "channel-messages") {
|
|
75
|
+
console.log("Usage:");
|
|
76
|
+
console.log(" teams channel messages <channelId> [--limit <n>]");
|
|
77
|
+
console.log(" Fetch messages in a team channel using channel id");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (command === "me") {
|
|
81
|
+
console.log("Usage:");
|
|
82
|
+
console.log(" teams me");
|
|
83
|
+
console.log(" Show current user snapshot");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (command === "set-refresh-token") {
|
|
87
|
+
console.log("Usage:");
|
|
88
|
+
console.log(" teams set-refresh-token --refresh-token=<token> [options]");
|
|
89
|
+
console.log(" Save refresh token to the selected profile");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (command === "list") {
|
|
93
|
+
console.log("Usage:");
|
|
94
|
+
console.log(" teams list");
|
|
95
|
+
console.log(" List all teams for the current user");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (command === "teams") {
|
|
99
|
+
console.log("Usage:");
|
|
100
|
+
console.log(" teams list");
|
|
101
|
+
console.log(" teams channels <teamId>");
|
|
102
|
+
console.log(" Commands:");
|
|
103
|
+
console.log(" list List all teams for the current user");
|
|
104
|
+
console.log(" channels <teamId> List channels in a team");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (command === "channels") {
|
|
108
|
+
console.log("Usage:");
|
|
109
|
+
console.log(" teams channels <teamId>");
|
|
110
|
+
console.log(" List channels in the team");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
console.log(`${colorize("bold", "teams cli")} - a small Teams helper for human + LLM workflows`);
|
|
114
|
+
if (!compact) {
|
|
115
|
+
console.log("Usage:");
|
|
116
|
+
console.log(" teams [options] <command> [arguments]");
|
|
117
|
+
console.log();
|
|
118
|
+
console.log("Global options:");
|
|
119
|
+
console.log(" --profile=<name> Use ~/.teams-cli/<name>.json (default: default)");
|
|
120
|
+
console.log(" --profile-json=<path> Use custom profile json path");
|
|
121
|
+
console.log(" --refresh-token=<token> Use and persist this refresh token");
|
|
122
|
+
console.log(" --json Output machine-readable JSON only (default in automation)");
|
|
123
|
+
console.log(" --no-color Disable ANSI colors");
|
|
124
|
+
console.log(" --help, -h Show this help");
|
|
125
|
+
console.log();
|
|
126
|
+
console.log("Commands:");
|
|
127
|
+
console.log(" notifications [--limit N] Fetch latest notifications");
|
|
128
|
+
console.log(" messages <conversationId> [--limit N] Fetch conversation messages");
|
|
129
|
+
console.log(" channel messages <channelId> [--limit N] Fetch channel messages");
|
|
130
|
+
console.log(" teams list List all teams for the current user");
|
|
131
|
+
console.log(" teams channels <teamId> List channels in a team");
|
|
132
|
+
console.log(" me Fetch current user snapshot");
|
|
133
|
+
console.log(" set-refresh-token --refresh-token=<token> Save refresh token to selected profile");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function printError(context, message) {
|
|
137
|
+
if (context.machine) console.log(JSON.stringify({ error: message }));
|
|
138
|
+
else {
|
|
139
|
+
const prefix = `${ANSI_CODES.red}${ANSI_CODES.bold}error${ANSI_CODES.reset}`;
|
|
140
|
+
console.log(`${context.color ? prefix : "error"}: ${message}`);
|
|
141
|
+
console.log();
|
|
142
|
+
printHelp(context, void 0, true);
|
|
143
|
+
}
|
|
144
|
+
process.exitCode = 1;
|
|
145
|
+
}
|
|
146
|
+
function printResult(context, result, profile) {
|
|
147
|
+
if (context.machine) {
|
|
148
|
+
console.log(JSON.stringify({
|
|
149
|
+
command: result.command,
|
|
150
|
+
profile,
|
|
151
|
+
data: result.data
|
|
152
|
+
}));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (result.command === "help") return;
|
|
156
|
+
const colorize = (color, text) => context.color ? `${ANSI_CODES[color]}${text}${ANSI_CODES.reset}` : text;
|
|
157
|
+
if (result.command === "set-refresh-token") {
|
|
158
|
+
const payload = result.data;
|
|
159
|
+
console.log(colorize("bold", "Refresh token stored"));
|
|
160
|
+
console.log(` profile: ${colorize("cyan", payload.profile)}`);
|
|
161
|
+
console.log(` file: ${colorize("dim", payload.profilePath)}`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (result.command === "me") {
|
|
165
|
+
const me = result.data;
|
|
166
|
+
const teams = Array.isArray(me?.teams) ? me.teams : [];
|
|
167
|
+
const chats = Array.isArray(me?.chats) ? me.chats : [];
|
|
168
|
+
const privateFeeds = Array.isArray(me?.privateFeeds) ? me.privateFeeds : [];
|
|
169
|
+
console.log(colorize("bold", "Teams profile snapshot"));
|
|
170
|
+
console.log(` ${colorize("cyan", "profile:")} ${colorize("yellow", `${teams.length} teams`)} / ${colorize("yellow", `${chats.length} chats`)} / ${colorize("yellow", `${privateFeeds.length} private feeds`)}`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (result.command === "list") {
|
|
174
|
+
const teams = Array.isArray(result.data) ? result.data : [];
|
|
175
|
+
if (teams.length === 0) {
|
|
176
|
+
console.log(colorize("dim", "Teams: no items found"));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
console.log(colorize("bold", `Teams (${teams.length})`));
|
|
180
|
+
for (const [index, team] of teams.entries()) {
|
|
181
|
+
const name = typeof team?.displayName === "string" ? team.displayName : "Unknown team";
|
|
182
|
+
const id = typeof team?.id === "string" ? team.id : "unknown";
|
|
183
|
+
const line = `${String(index + 1).padStart(2, "0")}. ${colorize("cyan", name)} (${colorize("dim", id)})`;
|
|
184
|
+
console.log(` ${line}`);
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (result.command === "channels") {
|
|
189
|
+
const payload = result.data;
|
|
190
|
+
const channels = Array.isArray(payload?.channels) ? payload.channels : [];
|
|
191
|
+
const label = `Channels (${payload?.teamName ?? "team"})`;
|
|
192
|
+
if (channels.length === 0) {
|
|
193
|
+
console.log(colorize("dim", `${label}: no items found`));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
console.log(colorize("bold", `${label} (${channels.length})`));
|
|
197
|
+
for (const [index, channel] of channels.entries()) {
|
|
198
|
+
const name = typeof channel?.displayName === "string" ? channel.displayName : "Unknown channel";
|
|
199
|
+
const id = typeof channel?.id === "string" ? channel.id : "unknown";
|
|
200
|
+
const line = `${String(index + 1).padStart(2, "0")}. ${colorize("cyan", name)} (${colorize("dim", id)})`;
|
|
201
|
+
console.log(` ${line}`);
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const notifications = result.command === "notifications" || result.command === "messages" ? result.data.messages : result.command === "channel-messages" ? result.data : [];
|
|
206
|
+
if (result.command === "notifications") {
|
|
207
|
+
if (!Array.isArray(notifications) || notifications.length === 0) {
|
|
208
|
+
console.log(colorize("dim", "Notifications: no items found"));
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
console.log(colorize("bold", `Notifications (${notifications.length})`));
|
|
212
|
+
for (const [index, notification] of notifications.entries()) {
|
|
213
|
+
const messagePreview = (typeof notification === "object" && notification !== null ? notification.properties : void 0)?.activity?.messagePreview ?? "";
|
|
214
|
+
const id = pickFirstString(notification, "id", "messageId", "messageID", "id1", "clientMessageId");
|
|
215
|
+
const clumpId = pickFirstString(notification, "clumpId", "clumpID", "converationId", "conversationId");
|
|
216
|
+
const safeMessagePreview = messagePreview.length > 0 ? sanitizeMessageBody(messagePreview) : "no message preview";
|
|
217
|
+
const clippedPreview = safeMessagePreview.length > 160 ? `${safeMessagePreview.slice(0, 157)}...` : safeMessagePreview;
|
|
218
|
+
const safeId = id.length > 0 ? id : "unknown-id";
|
|
219
|
+
console.log(`${colorize("dim", `- msg: ${clumpId}, notify:`)} ${colorize("cyan", safeId)}`);
|
|
220
|
+
console.log(` ${colorize("dim", clippedPreview)}`);
|
|
221
|
+
}
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const label = result.command === "channel-messages" ? "Channel messages" : result.command === "messages" ? "Conversation messages" : "Notifications";
|
|
225
|
+
if (!Array.isArray(notifications) || notifications.length === 0) {
|
|
226
|
+
console.log(colorize("dim", `${label}: no items found`));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
console.log(colorize("bold", `${label} (${notifications.length})`));
|
|
230
|
+
for (const [index, message] of notifications.entries()) {
|
|
231
|
+
const sender = pickFirstString(message, "imdisplayname", "fromDisplayNameInToken", "fromDisplayName", "from");
|
|
232
|
+
const composedAt = pickFirstString(message, "composetime", "composeTime", "originalarrivaltime", "originalArrivalTime");
|
|
233
|
+
const content = pickFirstString(message, "content", "text");
|
|
234
|
+
const safeFrom = sender ? sender : "Unknown";
|
|
235
|
+
const safeTime = toPrettyTime(composedAt) ?? "unknown time";
|
|
236
|
+
const body = sanitizeMessageBody(content);
|
|
237
|
+
const clippedBody = body.length > 240 ? `${body.slice(0, 237)}...` : body;
|
|
238
|
+
const prefix = colorize("dim", `${String(index + 1).padStart(2, "0")}.`);
|
|
239
|
+
console.log(`${prefix} ${safeFrom} [${safeTime}]`);
|
|
240
|
+
console.log(` ${colorize("dim", clippedBody)}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function sanitizeMessageBody(input) {
|
|
244
|
+
return input.replace(/<br\s*\/?>/gi, "\n").replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim();
|
|
245
|
+
}
|
|
246
|
+
function toPrettyTime(input) {
|
|
247
|
+
if (!input) return;
|
|
248
|
+
const date = Number.isNaN(Number(input)) ? new Date(input) : new Date(Number(input));
|
|
249
|
+
if (Number.isNaN(date.getTime())) return;
|
|
250
|
+
return date.toLocaleString();
|
|
251
|
+
}
|
|
252
|
+
function pickFirstString(value, ...keys) {
|
|
253
|
+
if (!value) return "";
|
|
254
|
+
for (const key of keys) {
|
|
255
|
+
const candidate = value[key];
|
|
256
|
+
if (typeof candidate === "string" && candidate.trim().length > 0) return candidate.trim();
|
|
257
|
+
}
|
|
258
|
+
return "";
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/cli/args.ts
|
|
263
|
+
function parseArgs(argv) {
|
|
264
|
+
const parsed = {
|
|
265
|
+
command: "notifications",
|
|
266
|
+
hasCommand: false,
|
|
267
|
+
commandArgs: [],
|
|
268
|
+
profileName: DEFAULT_PROFILE_NAME,
|
|
269
|
+
profileJsonPath: void 0,
|
|
270
|
+
refreshToken: void 0,
|
|
271
|
+
jsonOutput: false,
|
|
272
|
+
noColor: false,
|
|
273
|
+
showHelp: false
|
|
274
|
+
};
|
|
275
|
+
let index = 0;
|
|
276
|
+
while (index < argv.length) {
|
|
277
|
+
const token = argv[index];
|
|
278
|
+
if (!token.startsWith("-")) break;
|
|
279
|
+
if (token === "--help" || token === "-h") {
|
|
280
|
+
parsed.showHelp = true;
|
|
281
|
+
index += 1;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
if (token === "--json") {
|
|
285
|
+
parsed.jsonOutput = true;
|
|
286
|
+
index += 1;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (token === "--no-color") {
|
|
290
|
+
parsed.noColor = true;
|
|
291
|
+
index += 1;
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (token === "--profile" || token.startsWith("--profile=")) {
|
|
295
|
+
const value = token === "--profile" ? argv[index + 1] : token.slice(10);
|
|
296
|
+
if (!value) throw new Error("Missing --profile value");
|
|
297
|
+
parsed.profileName = value;
|
|
298
|
+
index += token === "--profile" ? 2 : 1;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (token === "--profile-json" || token.startsWith("--profile-json=")) {
|
|
302
|
+
const value = token === "--profile-json" ? argv[index + 1] : token.slice(15);
|
|
303
|
+
if (!value) throw new Error("Missing --profile-json path");
|
|
304
|
+
parsed.profileJsonPath = value;
|
|
305
|
+
index += token === "--profile-json" ? 2 : 1;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (token === "--refresh-token" || token.startsWith("--refresh-token=")) {
|
|
309
|
+
const value = token === "--refresh-token" ? argv[index + 1] : token.slice(16);
|
|
310
|
+
if (!value) throw new Error("Missing --refresh-token value");
|
|
311
|
+
parsed.refreshToken = value;
|
|
312
|
+
index += token === "--refresh-token" ? 2 : 1;
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
throw new Error(`Unknown option: ${token}`);
|
|
316
|
+
}
|
|
317
|
+
const commandArg = argv[index];
|
|
318
|
+
if (!commandArg) {
|
|
319
|
+
parsed.hasCommand = false;
|
|
320
|
+
parsed.commandArgs = [];
|
|
321
|
+
return parsed;
|
|
322
|
+
}
|
|
323
|
+
if (commandArg.startsWith("-")) throw new Error(`Unknown command or option: ${commandArg}`);
|
|
324
|
+
parsed.command = parseCommand(commandArg);
|
|
325
|
+
parsed.hasCommand = true;
|
|
326
|
+
parsed.commandArgs = argv.slice(index + 1);
|
|
327
|
+
return parsed;
|
|
328
|
+
}
|
|
329
|
+
function parseCommand(input) {
|
|
330
|
+
if (input === "notifications" || input === "messages" || input === "channel" || input === "me" || input === "teams" || input === "set-refresh-token" || input === "help") return input;
|
|
331
|
+
throw new Error(`Unknown command: ${input}`);
|
|
332
|
+
}
|
|
333
|
+
function parseLimitArgs(args, command) {
|
|
334
|
+
const rest = [];
|
|
335
|
+
let limit = DEFAULT_MESSAGE_LIMIT;
|
|
336
|
+
for (let index = 0; index < args.length; index++) {
|
|
337
|
+
const token = args[index];
|
|
338
|
+
if (token === "--limit") {
|
|
339
|
+
const value = args[index + 1];
|
|
340
|
+
if (!value || value.startsWith("--")) throw new Error(`--limit requires a number for command: teams ${command}`);
|
|
341
|
+
const parsed = Number.parseInt(value, 10);
|
|
342
|
+
if (Number.isNaN(parsed) || parsed <= 0) throw new Error(`--limit requires a positive integer for command: teams ${command}`);
|
|
343
|
+
limit = parsed;
|
|
344
|
+
index += 1;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (token.startsWith("--limit=")) {
|
|
348
|
+
const value = token.slice(8);
|
|
349
|
+
const parsed = Number.parseInt(value, 10);
|
|
350
|
+
if (Number.isNaN(parsed) || parsed <= 0) throw new Error(`--limit requires a positive integer for command: teams ${command}`);
|
|
351
|
+
limit = parsed;
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
if (token.startsWith("-")) throw new Error(`Unknown option: ${token}`);
|
|
355
|
+
rest.push(token);
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
limit,
|
|
359
|
+
rest
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region src/cli/commands.ts
|
|
365
|
+
async function executeCommand(args, client, context) {
|
|
366
|
+
const animationContext = context;
|
|
367
|
+
switch (args.command) {
|
|
368
|
+
case "notifications": {
|
|
369
|
+
if (args.commandArgs.includes("--help") || args.commandArgs.includes("-h")) return {
|
|
370
|
+
command: "help",
|
|
371
|
+
data: "notifications"
|
|
372
|
+
};
|
|
373
|
+
const parsed = parseLimitArgs(args.commandArgs, "notifications");
|
|
374
|
+
if (parsed.rest.length > 0) throw new Error("notifications accepts only --limit option; no positional arguments are supported");
|
|
375
|
+
return {
|
|
376
|
+
command: "notifications",
|
|
377
|
+
data: await withSpinner(animationContext, "Fetching notifications...", () => client.teams.notifications.fetchMessages({ pageSize: parsed.limit }))
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
case "messages": {
|
|
381
|
+
if (args.commandArgs.includes("--help") || args.commandArgs.includes("-h")) return {
|
|
382
|
+
command: "help",
|
|
383
|
+
data: "messages"
|
|
384
|
+
};
|
|
385
|
+
const parsed = parseLimitArgs(args.commandArgs, "messages");
|
|
386
|
+
const [conversationId] = parsed.rest;
|
|
387
|
+
if (!conversationId) throw new Error("Usage: teams messages <conversationId> [--limit <n>]");
|
|
388
|
+
return {
|
|
389
|
+
command: "messages",
|
|
390
|
+
data: await withSpinner(animationContext, `Fetching conversation ${conversationId}...`, () => client.teams.conversations.fetchMessages(conversationId, { pageSize: parsed.limit }))
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
case "channel": {
|
|
394
|
+
const [subCommand, ...subCommandArgs] = args.commandArgs;
|
|
395
|
+
if (!subCommand || subCommand === "--help" || subCommand === "-h") return {
|
|
396
|
+
command: "help",
|
|
397
|
+
data: "channel"
|
|
398
|
+
};
|
|
399
|
+
if (subCommand === "messages") {
|
|
400
|
+
if (subCommandArgs.includes("--help") || subCommandArgs.includes("-h")) return {
|
|
401
|
+
command: "help",
|
|
402
|
+
data: "channel"
|
|
403
|
+
};
|
|
404
|
+
const parsed = parseLimitArgs(subCommandArgs, "channel messages");
|
|
405
|
+
const [channelId] = parsed.rest;
|
|
406
|
+
if (!channelId) throw new Error("Usage: teams channel messages <channelId> [--limit <n>]");
|
|
407
|
+
return {
|
|
408
|
+
command: "channel-messages",
|
|
409
|
+
data: await withSpinner(animationContext, `Fetching messages for channel=${channelId}...`, async () => {
|
|
410
|
+
const teamIds = resolveTeamIdFromMe(await client.teams.users.me.fetch(), channelId);
|
|
411
|
+
if (teamIds.length === 0) throw new Error(`Channel not found: ${channelId}`);
|
|
412
|
+
if (teamIds.length > 1) throw new Error(`Ambiguous channel: ${channelId} exists in multiple teams`);
|
|
413
|
+
return client.teams.channels.fetchMessages(teamIds[0], channelId, { pageSize: parsed.limit });
|
|
414
|
+
})
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
throw new Error(`Unknown channel subcommand: ${subCommand}`);
|
|
418
|
+
}
|
|
419
|
+
case "me":
|
|
420
|
+
if (args.commandArgs.includes("--help") || args.commandArgs.includes("-h")) return {
|
|
421
|
+
command: "help",
|
|
422
|
+
data: "me"
|
|
423
|
+
};
|
|
424
|
+
if (args.commandArgs.length > 0) throw new Error("me does not accept positional arguments");
|
|
425
|
+
return {
|
|
426
|
+
command: "me",
|
|
427
|
+
data: await withSpinner(animationContext, "Fetching current user...", () => client.teams.users.me.fetch())
|
|
428
|
+
};
|
|
429
|
+
case "teams": {
|
|
430
|
+
const [subCommand, ...subArgs] = args.commandArgs;
|
|
431
|
+
if (subCommand === "--help" || subCommand === "-h") return {
|
|
432
|
+
command: "help",
|
|
433
|
+
data: "teams"
|
|
434
|
+
};
|
|
435
|
+
if (!subCommand) return {
|
|
436
|
+
command: "help",
|
|
437
|
+
data: "teams"
|
|
438
|
+
};
|
|
439
|
+
if (subCommand === "list") {
|
|
440
|
+
if (subArgs.includes("--help") || subArgs.includes("-h")) return {
|
|
441
|
+
command: "help",
|
|
442
|
+
data: "list"
|
|
443
|
+
};
|
|
444
|
+
if (subArgs.length > 0) throw new Error("teams teams list does not accept positional arguments");
|
|
445
|
+
return {
|
|
446
|
+
command: "list",
|
|
447
|
+
data: (await withSpinner(animationContext, "Fetching teams list...", () => client.teams.users.me.fetch())).teams
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
if (subCommand === "channels") {
|
|
451
|
+
if (subArgs.includes("--help") || subArgs.includes("-h")) return {
|
|
452
|
+
command: "help",
|
|
453
|
+
data: "channels"
|
|
454
|
+
};
|
|
455
|
+
const [teamId] = subArgs;
|
|
456
|
+
if (!teamId) throw new Error("Usage: teams teams channels <teamId>");
|
|
457
|
+
if (subArgs.length > 1) throw new Error("teams teams channels does not accept positional arguments");
|
|
458
|
+
const payload = await withSpinner(animationContext, "Fetching team channels...", () => client.teams.users.me.fetch());
|
|
459
|
+
const target = (Array.isArray(payload.teams) ? payload.teams : []).find((team) => team.id === teamId);
|
|
460
|
+
if (!target) throw new Error(`Team not found: ${teamId}`);
|
|
461
|
+
const channels = Array.isArray(target.channels) ? target.channels : [];
|
|
462
|
+
return {
|
|
463
|
+
command: "channels",
|
|
464
|
+
data: {
|
|
465
|
+
teamId,
|
|
466
|
+
teamName: target.displayName ?? "Unknown team",
|
|
467
|
+
channels
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
throw new Error(`Unknown teams subcommand: ${subCommand}`);
|
|
472
|
+
}
|
|
473
|
+
case "set-refresh-token": throw new Error("set-refresh-token is a profile-only command");
|
|
474
|
+
case "help": return {
|
|
475
|
+
command: "help",
|
|
476
|
+
data: void 0
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
function resolveTeamIdFromMe(me, channelId) {
|
|
481
|
+
const teams = Array.isArray(me?.teams) ? me.teams : [];
|
|
482
|
+
const teamIds = [];
|
|
483
|
+
for (const team of teams) if ((Array.isArray(team?.channels) ? team.channels : []).some((channel) => channel?.id === channelId) && typeof team?.id === "string") teamIds.push(team.id);
|
|
484
|
+
return teamIds;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
//#endregion
|
|
488
|
+
//#region src/cli/profile.ts
|
|
489
|
+
const PROFILE_KEY = "refreshToken";
|
|
490
|
+
function resolveProfilePath(args) {
|
|
491
|
+
if (args.profileJsonPath) return expandHome(args.profileJsonPath);
|
|
492
|
+
return join(getHomeDirectory(), DEFAULT_PROFILE_DIR, `${normalizeProfileName(args.profileName)}.json`);
|
|
493
|
+
}
|
|
494
|
+
function getProfileLabel(args) {
|
|
495
|
+
if (args.profileJsonPath) return "profile-json";
|
|
496
|
+
return normalizeProfileName(args.profileName);
|
|
497
|
+
}
|
|
498
|
+
async function loadRefreshToken(path) {
|
|
499
|
+
try {
|
|
500
|
+
const raw = await readFile(path, "utf8");
|
|
501
|
+
const parsed = JSON.parse(raw);
|
|
502
|
+
if (typeof parsed[PROFILE_KEY] !== "string") return;
|
|
503
|
+
const token = parsed[PROFILE_KEY].trim();
|
|
504
|
+
return token.length > 0 ? token : void 0;
|
|
505
|
+
} catch (_error) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function saveRefreshToken(path, refreshToken) {
|
|
510
|
+
const directory = dirname(path);
|
|
511
|
+
if (directory !== ".") await mkdir(directory, { recursive: true });
|
|
512
|
+
await writeFile(path, JSON.stringify({
|
|
513
|
+
refreshToken: refreshToken.trim(),
|
|
514
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
515
|
+
}, null, 2), "utf8");
|
|
516
|
+
}
|
|
517
|
+
async function resolveProfile(args) {
|
|
518
|
+
const profilePath = resolveProfilePath(args);
|
|
519
|
+
const storedRefreshToken = await loadRefreshToken(profilePath);
|
|
520
|
+
const token = args.refreshToken ?? storedRefreshToken ?? process.env.REFRESH_TOKEN;
|
|
521
|
+
if (!token) throw new Error("No refresh token found. Use --refresh-token, --profile-json file, or set REFRESH_TOKEN in env.");
|
|
522
|
+
return {
|
|
523
|
+
token,
|
|
524
|
+
profilePath,
|
|
525
|
+
profileLabel: getProfileLabel(args)
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
function resolveRefreshTokenForStore(args) {
|
|
529
|
+
const token = args.refreshToken ?? process.env.REFRESH_TOKEN;
|
|
530
|
+
if (!token) throw new Error("set-refresh-token requires --refresh-token or REFRESH_TOKEN.");
|
|
531
|
+
return token;
|
|
532
|
+
}
|
|
533
|
+
function getHomeDirectory() {
|
|
534
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
535
|
+
if (!home) throw new Error("Cannot resolve home directory (HOME/USERPROFILE not set)");
|
|
536
|
+
return home;
|
|
537
|
+
}
|
|
538
|
+
function normalizeProfileName(name) {
|
|
539
|
+
return name.trim().replace(/[^a-zA-Z0-9._-]/g, "_") || DEFAULT_PROFILE_NAME;
|
|
540
|
+
}
|
|
541
|
+
function expandHome(value) {
|
|
542
|
+
if (value === "~") return getHomeDirectory();
|
|
543
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) return join(getHomeDirectory(), value.slice(2));
|
|
544
|
+
return value;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/cli/index.ts
|
|
549
|
+
if (import.meta.main) await runCli(process.argv.slice(2));
|
|
550
|
+
async function runCli(argv) {
|
|
551
|
+
const args = parseArgs(argv);
|
|
552
|
+
const context = createContext({
|
|
553
|
+
jsonOutput: args.jsonOutput,
|
|
554
|
+
noColor: args.noColor
|
|
555
|
+
});
|
|
556
|
+
if (args.showHelp || args.command === "help") {
|
|
557
|
+
printHelp(context);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
if (!args.hasCommand) {
|
|
561
|
+
printHelp(context);
|
|
562
|
+
process.exitCode = 1;
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (args.command === "set-refresh-token") try {
|
|
566
|
+
const profilePath = resolveProfilePath(args);
|
|
567
|
+
await saveRefreshToken(profilePath, resolveRefreshTokenForStore(args));
|
|
568
|
+
printResult(context, {
|
|
569
|
+
command: "set-refresh-token",
|
|
570
|
+
data: {
|
|
571
|
+
profile: getProfileLabel(args),
|
|
572
|
+
profilePath
|
|
573
|
+
}
|
|
574
|
+
}, getProfileLabel(args));
|
|
575
|
+
return;
|
|
576
|
+
} catch (error) {
|
|
577
|
+
printError(context, error instanceof Error ? error.message : String(error));
|
|
578
|
+
}
|
|
579
|
+
try {
|
|
580
|
+
const { token, profilePath, profileLabel } = await resolveProfile(args);
|
|
581
|
+
const tokenManager = new TokenManager(token);
|
|
582
|
+
const result = await executeCommand(args, new TeamsClient(tokenManager), context);
|
|
583
|
+
if (result.command === "help" && typeof result.data === "string") {
|
|
584
|
+
printHelp(context, result.data);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
await saveRefreshToken(profilePath, tokenManager.getRefreshToken());
|
|
588
|
+
printResult(context, result, profileLabel);
|
|
589
|
+
} catch (error) {
|
|
590
|
+
printError(context, error instanceof Error ? error.message : String(error));
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
//#endregion
|
|
595
|
+
export { };
|