agent-slack 0.6.1 → 0.7.1
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 +5 -0
- package/dist/index.js +870 -1
- package/dist/index.js.map +12 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1819,6 +1819,41 @@ class SlackApiClient {
|
|
|
1819
1819
|
this.web = new WebClient(auth.token);
|
|
1820
1820
|
}
|
|
1821
1821
|
}
|
|
1822
|
+
async apiMultipart(method, params = {}) {
|
|
1823
|
+
if (this.auth.auth_type === "standard") {
|
|
1824
|
+
return this.api(method, params);
|
|
1825
|
+
}
|
|
1826
|
+
if (!this.workspaceUrl) {
|
|
1827
|
+
throw new Error("Browser auth requires workspace URL.");
|
|
1828
|
+
}
|
|
1829
|
+
const auth = this.auth;
|
|
1830
|
+
const url = `${this.workspaceUrl.replace(/\/$/, "")}/api/${method}`;
|
|
1831
|
+
const fd = new FormData;
|
|
1832
|
+
fd.append("token", auth.xoxc_token);
|
|
1833
|
+
for (const [k, v] of Object.entries(params)) {
|
|
1834
|
+
if (v !== undefined) {
|
|
1835
|
+
fd.append(k, typeof v === "object" ? JSON.stringify(v) : String(v));
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
const response = await fetch(url, {
|
|
1839
|
+
method: "POST",
|
|
1840
|
+
headers: {
|
|
1841
|
+
Cookie: `d=${encodeURIComponent(auth.xoxd_cookie)}`,
|
|
1842
|
+
Origin: "https://app.slack.com",
|
|
1843
|
+
"User-Agent": getUserAgent()
|
|
1844
|
+
},
|
|
1845
|
+
body: fd
|
|
1846
|
+
});
|
|
1847
|
+
const data = await response.json().catch(() => ({}));
|
|
1848
|
+
if (!response.ok) {
|
|
1849
|
+
throw new Error(`Slack HTTP ${response.status} calling ${method}`);
|
|
1850
|
+
}
|
|
1851
|
+
if (!isRecord3(data) || data.ok !== true) {
|
|
1852
|
+
const error = isRecord3(data) && typeof data.error === "string" ? data.error : null;
|
|
1853
|
+
throw new Error(error || `Slack API error calling ${method}`);
|
|
1854
|
+
}
|
|
1855
|
+
return data;
|
|
1856
|
+
}
|
|
1822
1857
|
async api(method, params = {}) {
|
|
1823
1858
|
if (this.auth.auth_type === "standard") {
|
|
1824
1859
|
if (!this.web) {
|
|
@@ -7171,6 +7206,569 @@ function registerSearchCommand(input) {
|
|
|
7171
7206
|
create({ kind: "files", name: "files", desc: "Search files" });
|
|
7172
7207
|
}
|
|
7173
7208
|
|
|
7209
|
+
// src/slack/later.ts
|
|
7210
|
+
async function fetchLaterItems(client, options) {
|
|
7211
|
+
const stateFilter = options?.state ?? "in_progress";
|
|
7212
|
+
const limit = options?.limit ?? 20;
|
|
7213
|
+
const maxBodyChars = options?.maxBodyChars ?? 4000;
|
|
7214
|
+
const countsOnly = options?.countsOnly ?? false;
|
|
7215
|
+
let currentCursor = options?.cursor;
|
|
7216
|
+
let allRawItems = [];
|
|
7217
|
+
let counts = {};
|
|
7218
|
+
let nextCursor;
|
|
7219
|
+
while (true) {
|
|
7220
|
+
const resp = await client.api("saved.list", {
|
|
7221
|
+
limit: 50,
|
|
7222
|
+
cursor: currentCursor
|
|
7223
|
+
});
|
|
7224
|
+
if (!currentCursor) {
|
|
7225
|
+
counts = isRecord5(resp.counts) ? resp.counts : {};
|
|
7226
|
+
}
|
|
7227
|
+
const pageRawItems = asArray2(resp.saved_items).filter(isRecord5);
|
|
7228
|
+
allRawItems.push(...pageRawItems);
|
|
7229
|
+
const meta = isRecord5(resp.response_metadata) ? resp.response_metadata : null;
|
|
7230
|
+
nextCursor = meta ? getString4(meta.next_cursor) : undefined;
|
|
7231
|
+
if (countsOnly) {
|
|
7232
|
+
break;
|
|
7233
|
+
}
|
|
7234
|
+
let filtered2 = allRawItems.filter((item) => getString4(item.item_type) === "message");
|
|
7235
|
+
if (stateFilter !== "all") {
|
|
7236
|
+
filtered2 = filtered2.filter((item) => getString4(item.state) === stateFilter);
|
|
7237
|
+
}
|
|
7238
|
+
if (filtered2.length >= limit || !nextCursor) {
|
|
7239
|
+
break;
|
|
7240
|
+
}
|
|
7241
|
+
currentCursor = nextCursor;
|
|
7242
|
+
}
|
|
7243
|
+
const result = {
|
|
7244
|
+
counts: {
|
|
7245
|
+
in_progress: getNumber3(counts.uncompleted_count) ?? 0,
|
|
7246
|
+
archived: getNumber3(counts.archived_count) ?? 0,
|
|
7247
|
+
completed: getNumber3(counts.completed_count) ?? 0,
|
|
7248
|
+
total: getNumber3(counts.total_count) ?? 0
|
|
7249
|
+
},
|
|
7250
|
+
items: [],
|
|
7251
|
+
next_cursor: nextCursor
|
|
7252
|
+
};
|
|
7253
|
+
if (countsOnly) {
|
|
7254
|
+
return result;
|
|
7255
|
+
}
|
|
7256
|
+
let filtered = allRawItems.filter((item) => getString4(item.item_type) === "message");
|
|
7257
|
+
if (stateFilter !== "all") {
|
|
7258
|
+
filtered = filtered.filter((item) => getString4(item.state) === stateFilter);
|
|
7259
|
+
}
|
|
7260
|
+
filtered = filtered.slice(0, limit);
|
|
7261
|
+
result.items = await Promise.all(filtered.map(async (item) => {
|
|
7262
|
+
const channelId = getString4(item.item_id) ?? "";
|
|
7263
|
+
const ts = getString4(item.ts) ?? "";
|
|
7264
|
+
const state = getString4(item.state) ?? "in_progress";
|
|
7265
|
+
const dateSaved = getNumber3(item.date_created) ?? 0;
|
|
7266
|
+
const dateCompleted = getNumber3(item.date_completed);
|
|
7267
|
+
let channelName;
|
|
7268
|
+
let message;
|
|
7269
|
+
try {
|
|
7270
|
+
const info = await client.api("conversations.info", {
|
|
7271
|
+
channel: channelId
|
|
7272
|
+
});
|
|
7273
|
+
const ch = isRecord5(info.channel) ? info.channel : null;
|
|
7274
|
+
if (ch) {
|
|
7275
|
+
channelName = getString4(ch.name) ?? getString4(ch.name_normalized) ?? undefined;
|
|
7276
|
+
if (ch.is_im && !channelName) {
|
|
7277
|
+
const userId = getString4(ch.user);
|
|
7278
|
+
if (userId) {
|
|
7279
|
+
try {
|
|
7280
|
+
const userInfo = await client.api("users.info", { user: userId });
|
|
7281
|
+
const u = isRecord5(userInfo.user) ? userInfo.user : null;
|
|
7282
|
+
const profile = u && isRecord5(u.profile) ? u.profile : null;
|
|
7283
|
+
channelName = getString4(profile?.display_name) || getString4(u?.real_name) || getString4(u?.name) || undefined;
|
|
7284
|
+
} catch {}
|
|
7285
|
+
}
|
|
7286
|
+
}
|
|
7287
|
+
}
|
|
7288
|
+
} catch {}
|
|
7289
|
+
if (ts) {
|
|
7290
|
+
try {
|
|
7291
|
+
const history = await client.api("conversations.history", {
|
|
7292
|
+
channel: channelId,
|
|
7293
|
+
latest: ts,
|
|
7294
|
+
inclusive: true,
|
|
7295
|
+
limit: 1
|
|
7296
|
+
});
|
|
7297
|
+
const msgs = asArray2(history.messages).filter(isRecord5);
|
|
7298
|
+
const msg = msgs.find((m) => getString4(m.ts) === ts);
|
|
7299
|
+
if (msg) {
|
|
7300
|
+
const rendered = renderSlackMessageContent(msg);
|
|
7301
|
+
const content = maxBodyChars >= 0 && rendered.length > maxBodyChars ? `${rendered.slice(0, maxBodyChars)}
|
|
7302
|
+
…` : rendered;
|
|
7303
|
+
message = {
|
|
7304
|
+
author: getString4(msg.user) || getString4(msg.bot_id) ? {
|
|
7305
|
+
user_id: getString4(msg.user) ?? undefined,
|
|
7306
|
+
bot_id: getString4(msg.bot_id) ?? undefined
|
|
7307
|
+
} : undefined,
|
|
7308
|
+
content: content || undefined,
|
|
7309
|
+
thread_ts: getString4(msg.thread_ts) ?? undefined,
|
|
7310
|
+
reply_count: getNumber3(msg.reply_count) ?? undefined
|
|
7311
|
+
};
|
|
7312
|
+
}
|
|
7313
|
+
} catch {}
|
|
7314
|
+
}
|
|
7315
|
+
return {
|
|
7316
|
+
channel_id: channelId,
|
|
7317
|
+
channel_name: channelName,
|
|
7318
|
+
ts,
|
|
7319
|
+
state,
|
|
7320
|
+
date_saved: dateSaved,
|
|
7321
|
+
date_completed: dateCompleted && dateCompleted > 0 ? dateCompleted : undefined,
|
|
7322
|
+
message
|
|
7323
|
+
};
|
|
7324
|
+
}));
|
|
7325
|
+
return result;
|
|
7326
|
+
}
|
|
7327
|
+
async function updateLaterMark(client, input) {
|
|
7328
|
+
await client.apiMultipart("saved.update", {
|
|
7329
|
+
item_id: input.channelId,
|
|
7330
|
+
item_type: "message",
|
|
7331
|
+
ts: input.ts,
|
|
7332
|
+
mark: input.mark
|
|
7333
|
+
});
|
|
7334
|
+
}
|
|
7335
|
+
async function saveLater(client, input) {
|
|
7336
|
+
await client.api("saved.add", {
|
|
7337
|
+
item_id: input.channelId,
|
|
7338
|
+
item_type: "message",
|
|
7339
|
+
ts: input.ts
|
|
7340
|
+
});
|
|
7341
|
+
}
|
|
7342
|
+
async function removeLater(client, input) {
|
|
7343
|
+
await client.api("saved.delete", {
|
|
7344
|
+
item_id: input.channelId,
|
|
7345
|
+
item_type: "message",
|
|
7346
|
+
ts: input.ts
|
|
7347
|
+
});
|
|
7348
|
+
}
|
|
7349
|
+
async function setLaterReminder(client, input) {
|
|
7350
|
+
await client.apiMultipart("saved.update", {
|
|
7351
|
+
item_id: input.channelId,
|
|
7352
|
+
item_type: "message",
|
|
7353
|
+
ts: input.ts,
|
|
7354
|
+
date_due: String(input.dateDue)
|
|
7355
|
+
});
|
|
7356
|
+
}
|
|
7357
|
+
function parseReminderDuration(input) {
|
|
7358
|
+
const now = Math.floor(Date.now() / 1000);
|
|
7359
|
+
const trimmed = input.trim().toLowerCase();
|
|
7360
|
+
const relMatch = trimmed.match(/^(\d+(?:\.\d+)?)\s*(m|min|mins|minutes?|h|hr|hrs|hours?|d|days?)$/);
|
|
7361
|
+
if (relMatch) {
|
|
7362
|
+
const amount = Number.parseFloat(relMatch[1]);
|
|
7363
|
+
const unit = relMatch[2].charAt(0);
|
|
7364
|
+
if (unit === "m") {
|
|
7365
|
+
return now + amount * 60;
|
|
7366
|
+
}
|
|
7367
|
+
if (unit === "h") {
|
|
7368
|
+
return now + amount * 3600;
|
|
7369
|
+
}
|
|
7370
|
+
if (unit === "d") {
|
|
7371
|
+
return now + amount * 86400;
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
const tomorrow9am = getNext9am(1);
|
|
7375
|
+
if (trimmed === "tomorrow") {
|
|
7376
|
+
return tomorrow9am;
|
|
7377
|
+
}
|
|
7378
|
+
const dayNames = ["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"];
|
|
7379
|
+
const dayIndex = dayNames.indexOf(trimmed);
|
|
7380
|
+
if (dayIndex >= 0) {
|
|
7381
|
+
const today = new Date;
|
|
7382
|
+
const currentDay = today.getDay();
|
|
7383
|
+
let daysUntil = dayIndex - currentDay;
|
|
7384
|
+
if (daysUntil <= 0) {
|
|
7385
|
+
daysUntil += 7;
|
|
7386
|
+
}
|
|
7387
|
+
return getNext9am(daysUntil);
|
|
7388
|
+
}
|
|
7389
|
+
const asNum = Number(trimmed);
|
|
7390
|
+
if (!Number.isNaN(asNum) && asNum > 1e9) {
|
|
7391
|
+
return asNum;
|
|
7392
|
+
}
|
|
7393
|
+
throw new Error(`Invalid duration: "${input}". Use: 30m, 1h, 3h, 2d, tomorrow, monday, or a unix timestamp.`);
|
|
7394
|
+
}
|
|
7395
|
+
function getNext9am(daysFromNow) {
|
|
7396
|
+
const date = new Date;
|
|
7397
|
+
date.setDate(date.getDate() + daysFromNow);
|
|
7398
|
+
date.setHours(9, 0, 0, 0);
|
|
7399
|
+
return Math.floor(date.getTime() / 1000);
|
|
7400
|
+
}
|
|
7401
|
+
function isRecord5(value) {
|
|
7402
|
+
return typeof value === "object" && value !== null;
|
|
7403
|
+
}
|
|
7404
|
+
function asArray2(value) {
|
|
7405
|
+
return Array.isArray(value) ? value : [];
|
|
7406
|
+
}
|
|
7407
|
+
function getString4(value) {
|
|
7408
|
+
return typeof value === "string" ? value : undefined;
|
|
7409
|
+
}
|
|
7410
|
+
function getNumber3(value) {
|
|
7411
|
+
return typeof value === "number" ? value : undefined;
|
|
7412
|
+
}
|
|
7413
|
+
|
|
7414
|
+
// src/cli/later-command.ts
|
|
7415
|
+
function resolveTargetRef(input) {
|
|
7416
|
+
const { targetInput, options, ctx } = input;
|
|
7417
|
+
const target = parseMsgTarget(targetInput);
|
|
7418
|
+
if (target.kind === "url") {
|
|
7419
|
+
return {
|
|
7420
|
+
workspaceUrl: target.ref.workspace_url,
|
|
7421
|
+
getChannelAndTs: async () => ({
|
|
7422
|
+
channelId: target.ref.channel_id,
|
|
7423
|
+
ts: target.ref.message_ts
|
|
7424
|
+
})
|
|
7425
|
+
};
|
|
7426
|
+
}
|
|
7427
|
+
const workspaceUrl = ctx.effectiveWorkspaceUrl(options.workspace);
|
|
7428
|
+
const ts = options.ts?.trim();
|
|
7429
|
+
if (!ts) {
|
|
7430
|
+
throw new Error('When targeting a channel, you must pass --ts "<seconds>.<micros>"');
|
|
7431
|
+
}
|
|
7432
|
+
return {
|
|
7433
|
+
workspaceUrl,
|
|
7434
|
+
getChannelAndTs: async (client) => {
|
|
7435
|
+
const channelId = await resolveChannelId(client, target.kind === "channel" ? target.channel : targetInput);
|
|
7436
|
+
return { channelId, ts };
|
|
7437
|
+
}
|
|
7438
|
+
};
|
|
7439
|
+
}
|
|
7440
|
+
function registerLaterCommand(input) {
|
|
7441
|
+
const laterCmd = input.program.command("later").description("Manage saved-for-later messages (Slack's Later tab)");
|
|
7442
|
+
laterCmd.command("list", { isDefault: true }).description("List saved-for-later messages").option("--workspace <url>", "Workspace URL (defaults to your configured workspace)").option("--state <state>", "Filter by state: in_progress (default), archived, completed, all", "in_progress").option("--limit <n>", "Max items to show (default 20)", "20").option("--max-body-chars <n>", "Max content characters per message (default 4000, -1 for unlimited)", "4000").option("--counts-only", "Only show counts per state, skip message content").action(async (options) => {
|
|
7443
|
+
try {
|
|
7444
|
+
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
|
|
7445
|
+
const state = parseState(options.state ?? "in_progress");
|
|
7446
|
+
const payload = await input.ctx.withAutoRefresh({
|
|
7447
|
+
workspaceUrl,
|
|
7448
|
+
work: async () => {
|
|
7449
|
+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
|
|
7450
|
+
return fetchLaterItems(client, {
|
|
7451
|
+
state,
|
|
7452
|
+
limit: Number.parseInt(options.limit ?? "20", 10),
|
|
7453
|
+
maxBodyChars: Number.parseInt(options.maxBodyChars ?? "4000", 10),
|
|
7454
|
+
countsOnly: options.countsOnly
|
|
7455
|
+
});
|
|
7456
|
+
}
|
|
7457
|
+
});
|
|
7458
|
+
console.log(JSON.stringify(pruneEmpty(payload), null, 2));
|
|
7459
|
+
} catch (err) {
|
|
7460
|
+
console.error(input.ctx.errorMessage(err));
|
|
7461
|
+
process.exitCode = 1;
|
|
7462
|
+
}
|
|
7463
|
+
});
|
|
7464
|
+
laterCmd.command("complete").description("Mark a saved message as completed").argument("<target>", "Slack message URL or channel ID").option("--workspace <url>", "Workspace URL").option("--ts <ts>", "Message ts (required when using channel ID)").action(async (...args) => {
|
|
7465
|
+
const [targetInput, options] = args;
|
|
7466
|
+
try {
|
|
7467
|
+
const ref = resolveTargetRef({ targetInput, options, ctx: input.ctx });
|
|
7468
|
+
await input.ctx.withAutoRefresh({
|
|
7469
|
+
workspaceUrl: ref.workspaceUrl,
|
|
7470
|
+
work: async () => {
|
|
7471
|
+
const { client } = await input.ctx.getClientForWorkspace(ref.workspaceUrl);
|
|
7472
|
+
const { channelId, ts } = await ref.getChannelAndTs(client);
|
|
7473
|
+
await updateLaterMark(client, { channelId, ts, mark: "completed" });
|
|
7474
|
+
}
|
|
7475
|
+
});
|
|
7476
|
+
console.log(JSON.stringify({ ok: true }));
|
|
7477
|
+
} catch (err) {
|
|
7478
|
+
console.error(input.ctx.errorMessage(err));
|
|
7479
|
+
process.exitCode = 1;
|
|
7480
|
+
}
|
|
7481
|
+
});
|
|
7482
|
+
laterCmd.command("archive").description("Archive a saved message").argument("<target>", "Slack message URL or channel ID").option("--workspace <url>", "Workspace URL").option("--ts <ts>", "Message ts (required when using channel ID)").action(async (...args) => {
|
|
7483
|
+
const [targetInput, options] = args;
|
|
7484
|
+
try {
|
|
7485
|
+
const ref = resolveTargetRef({ targetInput, options, ctx: input.ctx });
|
|
7486
|
+
await input.ctx.withAutoRefresh({
|
|
7487
|
+
workspaceUrl: ref.workspaceUrl,
|
|
7488
|
+
work: async () => {
|
|
7489
|
+
const { client } = await input.ctx.getClientForWorkspace(ref.workspaceUrl);
|
|
7490
|
+
const { channelId, ts } = await ref.getChannelAndTs(client);
|
|
7491
|
+
await updateLaterMark(client, { channelId, ts, mark: "archived" });
|
|
7492
|
+
}
|
|
7493
|
+
});
|
|
7494
|
+
console.log(JSON.stringify({ ok: true }));
|
|
7495
|
+
} catch (err) {
|
|
7496
|
+
console.error(input.ctx.errorMessage(err));
|
|
7497
|
+
process.exitCode = 1;
|
|
7498
|
+
}
|
|
7499
|
+
});
|
|
7500
|
+
laterCmd.command("reopen").description("Move a saved message back to in-progress").argument("<target>", "Slack message URL or channel ID").option("--workspace <url>", "Workspace URL").option("--ts <ts>", "Message ts (required when using channel ID)").action(async (...args) => {
|
|
7501
|
+
const [targetInput, options] = args;
|
|
7502
|
+
try {
|
|
7503
|
+
const ref = resolveTargetRef({ targetInput, options, ctx: input.ctx });
|
|
7504
|
+
await input.ctx.withAutoRefresh({
|
|
7505
|
+
workspaceUrl: ref.workspaceUrl,
|
|
7506
|
+
work: async () => {
|
|
7507
|
+
const { client } = await input.ctx.getClientForWorkspace(ref.workspaceUrl);
|
|
7508
|
+
const { channelId, ts } = await ref.getChannelAndTs(client);
|
|
7509
|
+
await Promise.allSettled([
|
|
7510
|
+
updateLaterMark(client, { channelId, ts, mark: "uncompleted" }),
|
|
7511
|
+
updateLaterMark(client, { channelId, ts, mark: "unarchived" })
|
|
7512
|
+
]);
|
|
7513
|
+
}
|
|
7514
|
+
});
|
|
7515
|
+
console.log(JSON.stringify({ ok: true }));
|
|
7516
|
+
} catch (err) {
|
|
7517
|
+
console.error(input.ctx.errorMessage(err));
|
|
7518
|
+
process.exitCode = 1;
|
|
7519
|
+
}
|
|
7520
|
+
});
|
|
7521
|
+
laterCmd.command("save").description("Save a message for later").argument("<target>", "Slack message URL or channel ID").option("--workspace <url>", "Workspace URL").option("--ts <ts>", "Message ts (required when using channel ID)").action(async (...args) => {
|
|
7522
|
+
const [targetInput, options] = args;
|
|
7523
|
+
try {
|
|
7524
|
+
const ref = resolveTargetRef({ targetInput, options, ctx: input.ctx });
|
|
7525
|
+
await input.ctx.withAutoRefresh({
|
|
7526
|
+
workspaceUrl: ref.workspaceUrl,
|
|
7527
|
+
work: async () => {
|
|
7528
|
+
const { client } = await input.ctx.getClientForWorkspace(ref.workspaceUrl);
|
|
7529
|
+
const { channelId, ts } = await ref.getChannelAndTs(client);
|
|
7530
|
+
await saveLater(client, { channelId, ts });
|
|
7531
|
+
}
|
|
7532
|
+
});
|
|
7533
|
+
console.log(JSON.stringify({ ok: true }));
|
|
7534
|
+
} catch (err) {
|
|
7535
|
+
console.error(input.ctx.errorMessage(err));
|
|
7536
|
+
process.exitCode = 1;
|
|
7537
|
+
}
|
|
7538
|
+
});
|
|
7539
|
+
laterCmd.command("remove").description("Remove a message from Later entirely").argument("<target>", "Slack message URL or channel ID").option("--workspace <url>", "Workspace URL").option("--ts <ts>", "Message ts (required when using channel ID)").action(async (...args) => {
|
|
7540
|
+
const [targetInput, options] = args;
|
|
7541
|
+
try {
|
|
7542
|
+
const ref = resolveTargetRef({ targetInput, options, ctx: input.ctx });
|
|
7543
|
+
await input.ctx.withAutoRefresh({
|
|
7544
|
+
workspaceUrl: ref.workspaceUrl,
|
|
7545
|
+
work: async () => {
|
|
7546
|
+
const { client } = await input.ctx.getClientForWorkspace(ref.workspaceUrl);
|
|
7547
|
+
const { channelId, ts } = await ref.getChannelAndTs(client);
|
|
7548
|
+
await removeLater(client, { channelId, ts });
|
|
7549
|
+
}
|
|
7550
|
+
});
|
|
7551
|
+
console.log(JSON.stringify({ ok: true }));
|
|
7552
|
+
} catch (err) {
|
|
7553
|
+
console.error(input.ctx.errorMessage(err));
|
|
7554
|
+
process.exitCode = 1;
|
|
7555
|
+
}
|
|
7556
|
+
});
|
|
7557
|
+
laterCmd.command("remind").description("Set a reminder for a saved message").argument("<target>", "Slack message URL or channel ID").requiredOption("--in <duration>", "When to remind: 30m, 1h, 3h, 2d, tomorrow, monday, etc.").option("--workspace <url>", "Workspace URL").option("--ts <ts>", "Message ts (required when using channel ID)").action(async (...args) => {
|
|
7558
|
+
const [targetInput, options] = args;
|
|
7559
|
+
try {
|
|
7560
|
+
const ref = resolveTargetRef({ targetInput, options, ctx: input.ctx });
|
|
7561
|
+
const reminderTime = parseReminderDuration(options.in);
|
|
7562
|
+
await input.ctx.withAutoRefresh({
|
|
7563
|
+
workspaceUrl: ref.workspaceUrl,
|
|
7564
|
+
work: async () => {
|
|
7565
|
+
const { client } = await input.ctx.getClientForWorkspace(ref.workspaceUrl);
|
|
7566
|
+
const { channelId, ts } = await ref.getChannelAndTs(client);
|
|
7567
|
+
await setLaterReminder(client, { channelId, ts, dateDue: reminderTime });
|
|
7568
|
+
}
|
|
7569
|
+
});
|
|
7570
|
+
console.log(JSON.stringify({
|
|
7571
|
+
ok: true,
|
|
7572
|
+
remind_at: reminderTime
|
|
7573
|
+
}));
|
|
7574
|
+
} catch (err) {
|
|
7575
|
+
console.error(input.ctx.errorMessage(err));
|
|
7576
|
+
process.exitCode = 1;
|
|
7577
|
+
}
|
|
7578
|
+
});
|
|
7579
|
+
}
|
|
7580
|
+
function parseState(value) {
|
|
7581
|
+
const v = value.toLowerCase().trim();
|
|
7582
|
+
if (v === "in_progress" || v === "in-progress" || v === "active" || v === "open") {
|
|
7583
|
+
return "in_progress";
|
|
7584
|
+
}
|
|
7585
|
+
if (v === "archived" || v === "archive") {
|
|
7586
|
+
return "archived";
|
|
7587
|
+
}
|
|
7588
|
+
if (v === "completed" || v === "complete" || v === "done") {
|
|
7589
|
+
return "completed";
|
|
7590
|
+
}
|
|
7591
|
+
if (v === "all") {
|
|
7592
|
+
return "all";
|
|
7593
|
+
}
|
|
7594
|
+
return "in_progress";
|
|
7595
|
+
}
|
|
7596
|
+
|
|
7597
|
+
// src/slack/unreads.ts
|
|
7598
|
+
async function fetchUnreads(client, options) {
|
|
7599
|
+
const includeMessages = options?.includeMessages ?? true;
|
|
7600
|
+
const maxMessages = options?.maxMessagesPerChannel ?? 10;
|
|
7601
|
+
const maxBodyChars = options?.maxBodyChars ?? 4000;
|
|
7602
|
+
const skipSystem = options?.skipSystemMessages ?? true;
|
|
7603
|
+
const resp = await client.api("client.counts", {
|
|
7604
|
+
thread_count_by_channel: true
|
|
7605
|
+
});
|
|
7606
|
+
const channels = asArray3(resp.channels).filter(isRecord6);
|
|
7607
|
+
const mpims = asArray3(resp.mpims).filter(isRecord6);
|
|
7608
|
+
const ims = asArray3(resp.ims).filter(isRecord6);
|
|
7609
|
+
const allEntries = [
|
|
7610
|
+
...channels.map((c) => ({ ...c, type: "channel" })),
|
|
7611
|
+
...mpims.map((c) => ({ ...c, type: "mpim" })),
|
|
7612
|
+
...ims.map((c) => ({ ...c, type: "dm" }))
|
|
7613
|
+
];
|
|
7614
|
+
const withUnreads = allEntries.filter((c) => c.has_unreads);
|
|
7615
|
+
const channelInfos = await Promise.all(withUnreads.map(async (entry) => {
|
|
7616
|
+
const channelInfoPromise = (async () => {
|
|
7617
|
+
let name;
|
|
7618
|
+
let { type } = entry;
|
|
7619
|
+
try {
|
|
7620
|
+
const info = await client.api("conversations.info", {
|
|
7621
|
+
channel: entry.id
|
|
7622
|
+
});
|
|
7623
|
+
const ch = isRecord6(info.channel) ? info.channel : null;
|
|
7624
|
+
if (ch) {
|
|
7625
|
+
name = getString5(ch.name) ?? getString5(ch.name_normalized) ?? undefined;
|
|
7626
|
+
if (ch.is_im) {
|
|
7627
|
+
type = "dm";
|
|
7628
|
+
const userId = getString5(ch.user);
|
|
7629
|
+
if (userId && !name) {
|
|
7630
|
+
try {
|
|
7631
|
+
const userInfo = await client.api("users.info", { user: userId });
|
|
7632
|
+
const u = isRecord6(userInfo.user) ? userInfo.user : null;
|
|
7633
|
+
const profile = u && isRecord6(u.profile) ? u.profile : null;
|
|
7634
|
+
name = getString5(profile?.display_name) || getString5(u?.real_name) || getString5(u?.name) || undefined;
|
|
7635
|
+
} catch {}
|
|
7636
|
+
}
|
|
7637
|
+
} else if (ch.is_mpim) {
|
|
7638
|
+
type = "mpim";
|
|
7639
|
+
} else if (ch.is_group || ch.is_private) {
|
|
7640
|
+
type = "group";
|
|
7641
|
+
} else {
|
|
7642
|
+
type = "channel";
|
|
7643
|
+
}
|
|
7644
|
+
}
|
|
7645
|
+
} catch {}
|
|
7646
|
+
return { name, type };
|
|
7647
|
+
})();
|
|
7648
|
+
const historyPromise = (async () => {
|
|
7649
|
+
let messages;
|
|
7650
|
+
let unreadCount = entry.unread_count_display ?? entry.unread_count ?? (entry.has_unreads ? 1 : 0);
|
|
7651
|
+
if (includeMessages && entry.last_read) {
|
|
7652
|
+
try {
|
|
7653
|
+
const history = await client.api("conversations.history", {
|
|
7654
|
+
channel: entry.id,
|
|
7655
|
+
oldest: entry.last_read,
|
|
7656
|
+
limit: maxMessages,
|
|
7657
|
+
inclusive: false
|
|
7658
|
+
});
|
|
7659
|
+
let msgs = asArray3(history.messages).filter(isRecord6);
|
|
7660
|
+
if (skipSystem) {
|
|
7661
|
+
msgs = msgs.filter((m) => {
|
|
7662
|
+
const subtype = getString5(m.subtype);
|
|
7663
|
+
if (!subtype) {
|
|
7664
|
+
return true;
|
|
7665
|
+
}
|
|
7666
|
+
const systemSubtypes = [
|
|
7667
|
+
"channel_join",
|
|
7668
|
+
"channel_leave",
|
|
7669
|
+
"channel_topic",
|
|
7670
|
+
"channel_purpose",
|
|
7671
|
+
"channel_name",
|
|
7672
|
+
"channel_archive",
|
|
7673
|
+
"channel_unarchive",
|
|
7674
|
+
"group_join",
|
|
7675
|
+
"group_leave",
|
|
7676
|
+
"group_topic",
|
|
7677
|
+
"group_purpose",
|
|
7678
|
+
"group_name",
|
|
7679
|
+
"group_archive",
|
|
7680
|
+
"group_unarchive"
|
|
7681
|
+
];
|
|
7682
|
+
return !systemSubtypes.includes(subtype);
|
|
7683
|
+
});
|
|
7684
|
+
}
|
|
7685
|
+
if (entry.unread_count_display === undefined && entry.unread_count === undefined) {
|
|
7686
|
+
unreadCount = msgs.length;
|
|
7687
|
+
if (history.has_more) {
|
|
7688
|
+
unreadCount = Math.max(unreadCount, 2);
|
|
7689
|
+
}
|
|
7690
|
+
}
|
|
7691
|
+
messages = msgs.map((m) => {
|
|
7692
|
+
const rendered = renderSlackMessageContent(m);
|
|
7693
|
+
const content = maxBodyChars >= 0 && rendered.length > maxBodyChars ? `${rendered.slice(0, maxBodyChars)}
|
|
7694
|
+
...` : rendered;
|
|
7695
|
+
return {
|
|
7696
|
+
ts: getString5(m.ts) ?? "",
|
|
7697
|
+
author: getString5(m.user) || getString5(m.bot_id) ? {
|
|
7698
|
+
user_id: getString5(m.user) ?? undefined,
|
|
7699
|
+
bot_id: getString5(m.bot_id) ?? undefined
|
|
7700
|
+
} : undefined,
|
|
7701
|
+
content: content || undefined,
|
|
7702
|
+
thread_ts: getString5(m.thread_ts) ?? undefined,
|
|
7703
|
+
reply_count: getNumber4(m.reply_count) ?? undefined
|
|
7704
|
+
};
|
|
7705
|
+
});
|
|
7706
|
+
messages.sort((a, b) => Number.parseFloat(a.ts) - Number.parseFloat(b.ts));
|
|
7707
|
+
} catch {}
|
|
7708
|
+
}
|
|
7709
|
+
return { messages, unreadCount };
|
|
7710
|
+
})();
|
|
7711
|
+
const [channelData, historyData] = await Promise.all([channelInfoPromise, historyPromise]);
|
|
7712
|
+
return {
|
|
7713
|
+
channel_id: entry.id,
|
|
7714
|
+
channel_name: channelData.name,
|
|
7715
|
+
channel_type: channelData.type,
|
|
7716
|
+
unread_count: historyData.unreadCount,
|
|
7717
|
+
mention_count: entry.mention_count ?? 0,
|
|
7718
|
+
messages: historyData.messages
|
|
7719
|
+
};
|
|
7720
|
+
}));
|
|
7721
|
+
channelInfos.sort((a, b) => {
|
|
7722
|
+
if (a.mention_count !== b.mention_count) {
|
|
7723
|
+
return b.mention_count - a.mention_count;
|
|
7724
|
+
}
|
|
7725
|
+
return b.unread_count - a.unread_count;
|
|
7726
|
+
});
|
|
7727
|
+
const threads = isRecord6(resp.threads) ? resp.threads : null;
|
|
7728
|
+
const threadInfo = threads?.has_unreads ? {
|
|
7729
|
+
has_unreads: true,
|
|
7730
|
+
mention_count: threads.mention_count ?? 0
|
|
7731
|
+
} : null;
|
|
7732
|
+
return { channels: channelInfos, threads: threadInfo };
|
|
7733
|
+
}
|
|
7734
|
+
function isRecord6(value) {
|
|
7735
|
+
return typeof value === "object" && value !== null;
|
|
7736
|
+
}
|
|
7737
|
+
function asArray3(value) {
|
|
7738
|
+
return Array.isArray(value) ? value : [];
|
|
7739
|
+
}
|
|
7740
|
+
function getString5(value) {
|
|
7741
|
+
return typeof value === "string" ? value : undefined;
|
|
7742
|
+
}
|
|
7743
|
+
function getNumber4(value) {
|
|
7744
|
+
return typeof value === "number" ? value : undefined;
|
|
7745
|
+
}
|
|
7746
|
+
|
|
7747
|
+
// src/cli/unreads-command.ts
|
|
7748
|
+
function registerUnreadsCommand(input) {
|
|
7749
|
+
input.program.command("unreads").description("Show all unread messages across channels, DMs, and threads").option("--workspace <url>", "Workspace URL (defaults to your configured workspace)").option("--counts-only", "Only show unread counts, do not fetch message content").option("--max-messages <n>", "Max unread messages to fetch per channel (default 10)", "10").option("--max-body-chars <n>", "Max content characters per message (default 4000, -1 for unlimited)", "4000").option("--include-system", "Include system messages (joins, leaves, topic changes, etc.)").action(async (options) => {
|
|
7750
|
+
try {
|
|
7751
|
+
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
|
|
7752
|
+
const payload = await input.ctx.withAutoRefresh({
|
|
7753
|
+
workspaceUrl,
|
|
7754
|
+
work: async () => {
|
|
7755
|
+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
|
|
7756
|
+
return fetchUnreads(client, {
|
|
7757
|
+
includeMessages: !options.countsOnly,
|
|
7758
|
+
maxMessagesPerChannel: Number.parseInt(options.maxMessages ?? "10", 10),
|
|
7759
|
+
maxBodyChars: Number.parseInt(options.maxBodyChars ?? "4000", 10),
|
|
7760
|
+
skipSystemMessages: !options.includeSystem
|
|
7761
|
+
});
|
|
7762
|
+
}
|
|
7763
|
+
});
|
|
7764
|
+
console.log(JSON.stringify(pruneEmpty(payload), null, 2));
|
|
7765
|
+
} catch (err) {
|
|
7766
|
+
console.error(input.ctx.errorMessage(err));
|
|
7767
|
+
process.exitCode = 1;
|
|
7768
|
+
}
|
|
7769
|
+
});
|
|
7770
|
+
}
|
|
7771
|
+
|
|
7174
7772
|
// src/lib/update.ts
|
|
7175
7773
|
import { execSync as execSync2 } from "node:child_process";
|
|
7176
7774
|
import { createHash } from "node:crypto";
|
|
@@ -7921,6 +8519,274 @@ function registerChannelCommand(input) {
|
|
|
7921
8519
|
});
|
|
7922
8520
|
}
|
|
7923
8521
|
|
|
8522
|
+
// src/slack/workflows.ts
|
|
8523
|
+
async function listChannelWorkflows(client, channelId) {
|
|
8524
|
+
const [bookmarked, featured] = await Promise.all([
|
|
8525
|
+
listBookmarkedWorkflows(client, channelId),
|
|
8526
|
+
listFeaturedWorkflows(client, channelId)
|
|
8527
|
+
]);
|
|
8528
|
+
const featuredIds = new Set(featured.map((f) => f.trigger_id));
|
|
8529
|
+
const seen = new Set;
|
|
8530
|
+
const workflows = [];
|
|
8531
|
+
for (const bk of bookmarked) {
|
|
8532
|
+
if (bk.trigger_id) {
|
|
8533
|
+
seen.add(bk.trigger_id);
|
|
8534
|
+
}
|
|
8535
|
+
workflows.push({
|
|
8536
|
+
title: bk.title,
|
|
8537
|
+
trigger_id: bk.trigger_id ?? "",
|
|
8538
|
+
link: bk.link,
|
|
8539
|
+
app_id: bk.app_id,
|
|
8540
|
+
featured: bk.trigger_id ? featuredIds.has(bk.trigger_id) : false
|
|
8541
|
+
});
|
|
8542
|
+
}
|
|
8543
|
+
for (const ft of featured) {
|
|
8544
|
+
if (!seen.has(ft.trigger_id)) {
|
|
8545
|
+
workflows.push({
|
|
8546
|
+
title: ft.title,
|
|
8547
|
+
trigger_id: ft.trigger_id,
|
|
8548
|
+
featured: true
|
|
8549
|
+
});
|
|
8550
|
+
}
|
|
8551
|
+
}
|
|
8552
|
+
return { channel_id: channelId, workflows };
|
|
8553
|
+
}
|
|
8554
|
+
async function listBookmarkedWorkflows(client, channelId) {
|
|
8555
|
+
const resp = await client.api("bookmarks.list", {
|
|
8556
|
+
channel_id: channelId
|
|
8557
|
+
});
|
|
8558
|
+
return asArray(resp.bookmarks).filter(isRecord).filter((b) => {
|
|
8559
|
+
const link = getString(b.link) ?? "";
|
|
8560
|
+
const shortcutId = getString(b.shortcut_id);
|
|
8561
|
+
return shortcutId || link.includes("slack.com/shortcuts/");
|
|
8562
|
+
}).map((b) => {
|
|
8563
|
+
const link = getString(b.link);
|
|
8564
|
+
const shortcutId = getString(b.shortcut_id);
|
|
8565
|
+
const triggerId = shortcutId || extractTriggerId(link);
|
|
8566
|
+
return {
|
|
8567
|
+
title: getString(b.title) ?? "",
|
|
8568
|
+
trigger_id: triggerId,
|
|
8569
|
+
link,
|
|
8570
|
+
app_id: getString(b.app_id)
|
|
8571
|
+
};
|
|
8572
|
+
});
|
|
8573
|
+
}
|
|
8574
|
+
async function listFeaturedWorkflows(client, channelId) {
|
|
8575
|
+
try {
|
|
8576
|
+
const resp = await client.api("workflows.featured.list", {
|
|
8577
|
+
channel_ids: JSON.stringify([channelId])
|
|
8578
|
+
});
|
|
8579
|
+
const entries = asArray(resp.featured_workflows).filter(isRecord);
|
|
8580
|
+
const entry = entries.find((e) => getString(e.channel_id) === channelId);
|
|
8581
|
+
if (!entry) {
|
|
8582
|
+
return [];
|
|
8583
|
+
}
|
|
8584
|
+
return asArray(entry.triggers).filter(isRecord).map((t) => ({
|
|
8585
|
+
trigger_id: getString(t.id) ?? "",
|
|
8586
|
+
title: getString(t.title) ?? ""
|
|
8587
|
+
})).filter((t) => t.trigger_id);
|
|
8588
|
+
} catch {
|
|
8589
|
+
return [];
|
|
8590
|
+
}
|
|
8591
|
+
}
|
|
8592
|
+
async function previewWorkflow(client, triggerId) {
|
|
8593
|
+
const resp = await client.api("workflows.triggers.preview", {
|
|
8594
|
+
trigger_ids: triggerId
|
|
8595
|
+
});
|
|
8596
|
+
const triggers = asArray(resp.triggers).filter(isRecord);
|
|
8597
|
+
if (triggers.length === 0) {
|
|
8598
|
+
const rejected = asArray(resp.rejected_triggers);
|
|
8599
|
+
if (rejected.length > 0) {
|
|
8600
|
+
throw new Error(`Trigger ${triggerId} was rejected — you may not have access`);
|
|
8601
|
+
}
|
|
8602
|
+
throw new Error(`No preview data returned for trigger ${triggerId}`);
|
|
8603
|
+
}
|
|
8604
|
+
const t = triggers[0];
|
|
8605
|
+
const wf = isRecord(t.workflow) ? t.workflow : {};
|
|
8606
|
+
const wfApp = isRecord(wf.app) ? wf.app : {};
|
|
8607
|
+
const details = isRecord(t.workflow_details) ? t.workflow_details : {};
|
|
8608
|
+
return {
|
|
8609
|
+
trigger_id: getString(t.id) ?? triggerId,
|
|
8610
|
+
type: getString(t.type) ?? "",
|
|
8611
|
+
name: getString(t.name) ?? "",
|
|
8612
|
+
description: getString(t.description) ?? "",
|
|
8613
|
+
shortcut_url: getString(t.shortcut_url),
|
|
8614
|
+
workflow: {
|
|
8615
|
+
id: getString(wf.workflow_id) ?? "",
|
|
8616
|
+
title: getString(wf.title) ?? "",
|
|
8617
|
+
description: getString(wf.description) ?? "",
|
|
8618
|
+
app_id: getString(wf.app_id) ?? getString(wfApp.id) ?? "",
|
|
8619
|
+
app_name: getString(wfApp.name) ?? ""
|
|
8620
|
+
},
|
|
8621
|
+
collaborators: asArray(details.collaborators).map((c) => typeof c === "string" ? c : "").filter(Boolean)
|
|
8622
|
+
};
|
|
8623
|
+
}
|
|
8624
|
+
async function runWorkflow(client, input) {
|
|
8625
|
+
const clientToken = `cli-${Date.now()}`;
|
|
8626
|
+
const resp = await client.api("workflows.triggers.trip", {
|
|
8627
|
+
url: input.shortcutUrl,
|
|
8628
|
+
client_token: clientToken,
|
|
8629
|
+
context: JSON.stringify({
|
|
8630
|
+
location: "bookmark",
|
|
8631
|
+
channel_id: input.channelId,
|
|
8632
|
+
trigger_id: input.triggerId
|
|
8633
|
+
}),
|
|
8634
|
+
run_precheck: true
|
|
8635
|
+
});
|
|
8636
|
+
return {
|
|
8637
|
+
function_execution_id: getString(resp.function_execution_id) ?? "",
|
|
8638
|
+
trigger_execution_id: getString(resp.trigger_execution_id) ?? "",
|
|
8639
|
+
is_slow_workflow: resp.is_slow_workflow === true
|
|
8640
|
+
};
|
|
8641
|
+
}
|
|
8642
|
+
async function resolveShortcutUrl(client, input) {
|
|
8643
|
+
const { channelId, triggerId } = input;
|
|
8644
|
+
const resp = await client.api("bookmarks.list", {
|
|
8645
|
+
channel_id: channelId
|
|
8646
|
+
});
|
|
8647
|
+
const bookmarks = asArray(resp.bookmarks).filter(isRecord);
|
|
8648
|
+
for (const b of bookmarks) {
|
|
8649
|
+
const shortcutId = getString(b.shortcut_id);
|
|
8650
|
+
if (shortcutId === triggerId) {
|
|
8651
|
+
const link = getString(b.link);
|
|
8652
|
+
if (link) {
|
|
8653
|
+
return link;
|
|
8654
|
+
}
|
|
8655
|
+
}
|
|
8656
|
+
}
|
|
8657
|
+
throw new Error(`Could not find shortcut URL for trigger ${triggerId} in channel bookmarks`);
|
|
8658
|
+
}
|
|
8659
|
+
async function getWorkflowSchema(client, workflowId) {
|
|
8660
|
+
const resp = await client.api("workflows.get", { workflow_id: workflowId });
|
|
8661
|
+
const wf = isRecord(resp.workflow) ? resp.workflow : null;
|
|
8662
|
+
if (!wf) {
|
|
8663
|
+
throw new Error(`No workflow found for ID ${workflowId}`);
|
|
8664
|
+
}
|
|
8665
|
+
const steps = asArray(wf.steps).filter(isRecord);
|
|
8666
|
+
const stepSummaries = [];
|
|
8667
|
+
let fields = [];
|
|
8668
|
+
let formTitle;
|
|
8669
|
+
for (const step of steps) {
|
|
8670
|
+
const fn = isRecord(step.function) ? step.function : {};
|
|
8671
|
+
const callbackId = getString(fn.callback_id) ?? "";
|
|
8672
|
+
const title = getString(fn.title) ?? callbackId;
|
|
8673
|
+
stepSummaries.push(title);
|
|
8674
|
+
if (callbackId === "open_form") {
|
|
8675
|
+
const inputs = isRecord(step.inputs) ? step.inputs : {};
|
|
8676
|
+
const titleInput = isRecord(inputs.title) ? inputs.title : {};
|
|
8677
|
+
formTitle = getString(titleInput.value);
|
|
8678
|
+
const fieldsInput = isRecord(inputs.fields) ? inputs.fields : {};
|
|
8679
|
+
const fieldsValue = isRecord(fieldsInput.value) ? fieldsInput.value : {};
|
|
8680
|
+
const elements = asArray(fieldsValue.elements).filter(isRecord);
|
|
8681
|
+
const required = new Set(asArray(fieldsValue.required).map((r) => typeof r === "string" ? r : "").filter(Boolean));
|
|
8682
|
+
fields = elements.map((el) => ({
|
|
8683
|
+
name: getString(el.name) ?? "",
|
|
8684
|
+
title: getString(el.title) ?? "",
|
|
8685
|
+
type: getString(el.type) ?? "string",
|
|
8686
|
+
description: getString(el.description) ?? "",
|
|
8687
|
+
required: required.has(getString(el.name) ?? ""),
|
|
8688
|
+
long: el.long === true ? true : undefined
|
|
8689
|
+
}));
|
|
8690
|
+
}
|
|
8691
|
+
}
|
|
8692
|
+
return {
|
|
8693
|
+
workflow_id: getString(wf.id) ?? workflowId,
|
|
8694
|
+
title: getString(wf.title) ?? "",
|
|
8695
|
+
description: getString(wf.description) ?? "",
|
|
8696
|
+
form_title: formTitle,
|
|
8697
|
+
fields,
|
|
8698
|
+
steps: stepSummaries
|
|
8699
|
+
};
|
|
8700
|
+
}
|
|
8701
|
+
function extractTriggerId(link) {
|
|
8702
|
+
if (!link) {
|
|
8703
|
+
return;
|
|
8704
|
+
}
|
|
8705
|
+
const match = link.match(/slack\.com\/shortcuts\/(Ft[A-Za-z0-9]+)/);
|
|
8706
|
+
return match?.[1];
|
|
8707
|
+
}
|
|
8708
|
+
|
|
8709
|
+
// src/cli/workflow-command.ts
|
|
8710
|
+
function registerWorkflowCommand(input) {
|
|
8711
|
+
const workflowCmd = input.program.command("workflow").description("Discover and interact with Slack workflows");
|
|
8712
|
+
workflowCmd.command("list").description("List workflows bookmarked or featured in a channel").argument("<channel>", "Channel id or name (#channel, channel, C...)").option("--workspace <url>", "Workspace selector (full URL or unique substring; required if you have multiple workspaces)").action(async (...args) => {
|
|
8713
|
+
const [channel, options] = args;
|
|
8714
|
+
try {
|
|
8715
|
+
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
|
|
8716
|
+
const payload = await input.ctx.withAutoRefresh({
|
|
8717
|
+
workspaceUrl,
|
|
8718
|
+
work: async () => {
|
|
8719
|
+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
|
|
8720
|
+
const channelId = await resolveChannelId(client, channel);
|
|
8721
|
+
return await listChannelWorkflows(client, channelId);
|
|
8722
|
+
}
|
|
8723
|
+
});
|
|
8724
|
+
console.log(JSON.stringify(pruneEmpty(payload), null, 2));
|
|
8725
|
+
} catch (err) {
|
|
8726
|
+
console.error(input.ctx.errorMessage(err));
|
|
8727
|
+
process.exitCode = 1;
|
|
8728
|
+
}
|
|
8729
|
+
});
|
|
8730
|
+
workflowCmd.command("preview").description("Get workflow metadata from a trigger ID (no side effects)").argument("<trigger-id>", "Trigger ID (Ft...)").option("--workspace <url>", "Workspace selector (full URL or unique substring; required if you have multiple workspaces)").action(async (...args) => {
|
|
8731
|
+
const [triggerId, options] = args;
|
|
8732
|
+
try {
|
|
8733
|
+
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
|
|
8734
|
+
const payload = await input.ctx.withAutoRefresh({
|
|
8735
|
+
workspaceUrl,
|
|
8736
|
+
work: async () => {
|
|
8737
|
+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
|
|
8738
|
+
return await previewWorkflow(client, triggerId);
|
|
8739
|
+
}
|
|
8740
|
+
});
|
|
8741
|
+
console.log(JSON.stringify(pruneEmpty(payload), null, 2));
|
|
8742
|
+
} catch (err) {
|
|
8743
|
+
console.error(input.ctx.errorMessage(err));
|
|
8744
|
+
process.exitCode = 1;
|
|
8745
|
+
}
|
|
8746
|
+
});
|
|
8747
|
+
workflowCmd.command("get").description("Get workflow definition including form fields and steps (accepts Ft... or Wf...)").argument("<id>", "Trigger ID (Ft...) or Workflow ID (Wf...)").option("--workspace <url>", "Workspace selector (full URL or unique substring; required if you have multiple workspaces)").action(async (...args) => {
|
|
8748
|
+
const [id, options] = args;
|
|
8749
|
+
try {
|
|
8750
|
+
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
|
|
8751
|
+
const payload = await input.ctx.withAutoRefresh({
|
|
8752
|
+
workspaceUrl,
|
|
8753
|
+
work: async () => {
|
|
8754
|
+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
|
|
8755
|
+
let workflowId = id;
|
|
8756
|
+
if (id.startsWith("Ft")) {
|
|
8757
|
+
const preview = await previewWorkflow(client, id);
|
|
8758
|
+
workflowId = preview.workflow.id;
|
|
8759
|
+
}
|
|
8760
|
+
return await getWorkflowSchema(client, workflowId);
|
|
8761
|
+
}
|
|
8762
|
+
});
|
|
8763
|
+
console.log(JSON.stringify(pruneEmpty(payload), null, 2));
|
|
8764
|
+
} catch (err) {
|
|
8765
|
+
console.error(input.ctx.errorMessage(err));
|
|
8766
|
+
process.exitCode = 1;
|
|
8767
|
+
}
|
|
8768
|
+
});
|
|
8769
|
+
workflowCmd.command("run").description("Trip a workflow trigger").argument("<trigger-id>", "Trigger ID (Ft...)").requiredOption("--channel <id-or-name>", "Channel where the workflow is bookmarked").option("--workspace <url>", "Workspace selector (full URL or unique substring; required if you have multiple workspaces)").action(async (...args) => {
|
|
8770
|
+
const [triggerId, options] = args;
|
|
8771
|
+
try {
|
|
8772
|
+
const workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
|
|
8773
|
+
const payload = await input.ctx.withAutoRefresh({
|
|
8774
|
+
workspaceUrl,
|
|
8775
|
+
work: async () => {
|
|
8776
|
+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
|
|
8777
|
+
const channelId = await resolveChannelId(client, options.channel);
|
|
8778
|
+
const shortcutUrl = await resolveShortcutUrl(client, { channelId, triggerId });
|
|
8779
|
+
return await runWorkflow(client, { shortcutUrl, channelId, triggerId });
|
|
8780
|
+
}
|
|
8781
|
+
});
|
|
8782
|
+
console.log(JSON.stringify(pruneEmpty(payload), null, 2));
|
|
8783
|
+
} catch (err) {
|
|
8784
|
+
console.error(input.ctx.errorMessage(err));
|
|
8785
|
+
process.exitCode = 1;
|
|
8786
|
+
}
|
|
8787
|
+
});
|
|
8788
|
+
}
|
|
8789
|
+
|
|
7924
8790
|
// src/index.ts
|
|
7925
8791
|
var program = new Command;
|
|
7926
8792
|
program.name("agent-slack").description("Slack automation CLI for AI agents").version(getPackageVersion());
|
|
@@ -7929,9 +8795,12 @@ registerAuthCommand({ program, ctx });
|
|
|
7929
8795
|
registerMessageCommand({ program, ctx });
|
|
7930
8796
|
registerCanvasCommand({ program, ctx });
|
|
7931
8797
|
registerSearchCommand({ program, ctx });
|
|
8798
|
+
registerLaterCommand({ program, ctx });
|
|
8799
|
+
registerUnreadsCommand({ program, ctx });
|
|
7932
8800
|
registerUpdateCommand({ program });
|
|
7933
8801
|
registerUserCommand({ program, ctx });
|
|
7934
8802
|
registerChannelCommand({ program, ctx });
|
|
8803
|
+
registerWorkflowCommand({ program, ctx });
|
|
7935
8804
|
program.parse(process.argv);
|
|
7936
8805
|
if (!process.argv.slice(2).length) {
|
|
7937
8806
|
program.outputHelp();
|
|
@@ -7941,5 +8810,5 @@ if (subcommand && subcommand !== "update") {
|
|
|
7941
8810
|
backgroundUpdateCheck();
|
|
7942
8811
|
}
|
|
7943
8812
|
|
|
7944
|
-
//# debugId=
|
|
8813
|
+
//# debugId=517A87768FFFD43D64756E2164756E21
|
|
7945
8814
|
//# sourceMappingURL=index.js.map
|