agent-slack 0.6.0 → 0.6.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 CHANGED
@@ -293,7 +293,7 @@ When to use which:
293
293
 
294
294
  ### Files (snippets/images/attachments)
295
295
 
296
- `message get/list` auto-download attached files to an agent-friendly temp directory and return absolute paths in `message.files[].path`:
296
+ `message get/list` auto-download attached files to an agent-friendly temp directory and return file metadata in `message.files[]`, including `name` when Slack provides the original filename and `path` for the local download. Failed downloads keep the attachment entry, preserve `message.files[].path` with a local `.download-error.txt` file, and include `message.files[].error`. `search messages` and `search all` use the same attachment shape for message results, while `search files` skips entries whose download fails.
297
297
 
298
298
  - macOS default: `~/.agent-slack/tmp/downloads/`
299
299
 
package/dist/index.js CHANGED
@@ -2417,6 +2417,14 @@ function registerAuthCommand(input) {
2417
2417
  import { mkdir as mkdir3, writeFile as writeFile2 } from "node:fs/promises";
2418
2418
  import { basename, join as join9, resolve } from "node:path";
2419
2419
  import { existsSync as existsSync7 } from "node:fs";
2420
+ class SlackDownloadError extends Error {
2421
+ httpStatus;
2422
+ constructor(message, httpStatus) {
2423
+ super(message);
2424
+ this.httpStatus = httpStatus;
2425
+ this.name = "SlackDownloadError";
2426
+ }
2427
+ }
2420
2428
  async function downloadSlackFile(input) {
2421
2429
  const { auth, url, destDir, preferredName, options } = input;
2422
2430
  const absDir = resolve(destDir);
@@ -2435,19 +2443,54 @@ async function downloadSlackFile(input) {
2435
2443
  headers.Referer = "https://app.slack.com/";
2436
2444
  headers["User-Agent"] = getUserAgent();
2437
2445
  }
2438
- const resp = await fetch(url, { headers });
2446
+ let resp;
2447
+ try {
2448
+ resp = await fetch(url, { headers });
2449
+ } catch (err) {
2450
+ throw new SlackDownloadError(`Network error: ${err instanceof Error ? err.message : String(err)}`);
2451
+ }
2439
2452
  if (!resp.ok) {
2440
- throw new Error(`Failed to download file (${resp.status})`);
2453
+ throw new SlackDownloadError(`Failed to download file (${resp.status})`, resp.status);
2441
2454
  }
2442
2455
  const contentType = resp.headers.get("content-type") || "";
2443
2456
  if (!options?.allowHtml && contentType.includes("text/html")) {
2444
- const text = await resp.text();
2445
- throw new Error(`Downloaded HTML instead of file (auth likely failed). First bytes: ${JSON.stringify(text.slice(0, 120))}`);
2457
+ let text;
2458
+ try {
2459
+ text = await resp.text();
2460
+ } catch (err) {
2461
+ throw new SlackDownloadError(`Failed to read download response body: ${err instanceof Error ? err.message : String(err)}`, resp.status);
2462
+ }
2463
+ throw new SlackDownloadError(`Downloaded HTML instead of file (auth likely failed). First bytes: ${JSON.stringify(text.slice(0, 120))}`, resp.status);
2464
+ }
2465
+ let arrayBuffer;
2466
+ try {
2467
+ arrayBuffer = await resp.arrayBuffer();
2468
+ } catch (err) {
2469
+ throw new SlackDownloadError(`Failed to read download response body: ${err instanceof Error ? err.message : String(err)}`, resp.status);
2446
2470
  }
2447
- const buf = Buffer.from(await resp.arrayBuffer());
2471
+ const buf = Buffer.from(arrayBuffer);
2448
2472
  await writeFile2(path, buf);
2449
2473
  return path;
2450
2474
  }
2475
+ async function tryDownloadSlackFile(input) {
2476
+ try {
2477
+ const path = await downloadSlackFile(input);
2478
+ return { ok: true, path };
2479
+ } catch (err) {
2480
+ if (err instanceof SlackDownloadError) {
2481
+ return { ok: false, error: err.message, httpStatus: err.httpStatus };
2482
+ }
2483
+ throw err;
2484
+ }
2485
+ }
2486
+ async function writeDownloadErrorFile(input) {
2487
+ const absDir = resolve(input.destDir);
2488
+ await mkdir3(absDir, { recursive: true });
2489
+ const path = join9(absDir, sanitizeFilename(`${input.fileId}.download-error.txt`));
2490
+ await writeFile2(path, `${input.error}
2491
+ `, "utf8");
2492
+ return path;
2493
+ }
2451
2494
  function sanitizeFilename(name) {
2452
2495
  return name.replace(/[\\/<>:"|?*]/g, "_");
2453
2496
  }
@@ -3129,14 +3172,16 @@ function toCompactMessage(msg, input) {
3129
3172
  const content = maxBodyChars >= 0 && rendered.length > maxBodyChars ? `${rendered.slice(0, maxBodyChars)}
3130
3173
  …` : rendered;
3131
3174
  const files = msg.files?.map((f) => {
3132
- const path = input?.downloadedPaths?.[f.id];
3133
- if (!path) {
3175
+ const entry = input?.downloadedPaths?.[f.id];
3176
+ if (!entry) {
3134
3177
  return null;
3135
3178
  }
3136
- return {
3179
+ return entry.ok ? { name: f.name, mimetype: f.mimetype, mode: f.mode, path: entry.path } : {
3180
+ name: f.name,
3137
3181
  mimetype: f.mimetype,
3138
3182
  mode: f.mode,
3139
- path
3183
+ path: entry.path,
3184
+ error: entry.error
3140
3185
  };
3141
3186
  }).filter((f) => Boolean(f));
3142
3187
  return {
@@ -3743,7 +3788,7 @@ async function downloadCanvasAsMarkdown(input) {
3743
3788
  });
3744
3789
  const html = await readFile6(htmlPath, "utf8");
3745
3790
  if (looksLikeAuthPage(html)) {
3746
- throw new Error("Downloaded auth/login page instead of canvas content (token may be expired)");
3791
+ throw new SlackDownloadError("Downloaded auth/login page instead of canvas content (token may be expired)");
3747
3792
  }
3748
3793
  const markdown = htmlToMarkdown(html).trim();
3749
3794
  const safeName = `${input.fileId.replace(/[\\/<>"|?*]/g, "_")}.md`;
@@ -3764,25 +3809,53 @@ async function downloadMessageFiles(input) {
3764
3809
  if (!url) {
3765
3810
  continue;
3766
3811
  }
3767
- try {
3768
- if (isCanvas) {
3769
- downloadedPaths[file.id] = await downloadCanvasAsMarkdown({
3812
+ if (isCanvas) {
3813
+ try {
3814
+ const path = await downloadCanvasAsMarkdown({
3770
3815
  auth: input.auth,
3771
3816
  fileId: file.id,
3772
3817
  url,
3773
3818
  destDir: downloadsDir
3774
3819
  });
3775
- } else {
3776
- const ext = inferFileExtension(file);
3777
- downloadedPaths[file.id] = await downloadSlackFile({
3778
- auth: input.auth,
3779
- url,
3820
+ downloadedPaths[file.id] = { ok: true, path };
3821
+ } catch (err) {
3822
+ if (!(err instanceof SlackDownloadError)) {
3823
+ throw err;
3824
+ }
3825
+ const path = await writeDownloadErrorFile({
3780
3826
  destDir: downloadsDir,
3781
- preferredName: `${file.id}${ext ? `.${ext}` : ""}`
3827
+ fileId: file.id,
3828
+ error: err.message
3782
3829
  });
3830
+ downloadedPaths[file.id] = {
3831
+ ok: false,
3832
+ error: err.message,
3833
+ httpStatus: err.httpStatus,
3834
+ path
3835
+ };
3836
+ console.error(`Warning: skipping file ${file.id}: ${err.message}`);
3837
+ }
3838
+ } else {
3839
+ const ext = inferFileExtension(file);
3840
+ const result = await tryDownloadSlackFile({
3841
+ auth: input.auth,
3842
+ url,
3843
+ destDir: downloadsDir,
3844
+ preferredName: `${file.id}${ext ? `.${ext}` : ""}`
3845
+ });
3846
+ if (!result.ok) {
3847
+ downloadedPaths[file.id] = {
3848
+ ...result,
3849
+ path: await writeDownloadErrorFile({
3850
+ destDir: downloadsDir,
3851
+ fileId: file.id,
3852
+ error: result.error
3853
+ })
3854
+ };
3855
+ console.error(`Warning: skipping file ${file.id}: ${result.error}`);
3856
+ } else {
3857
+ downloadedPaths[file.id] = result;
3783
3858
  }
3784
- } catch (err) {
3785
- console.error(`Warning: skipping file ${file.id}: ${err instanceof Error ? err.message : String(err)}`);
3786
3859
  }
3787
3860
  }
3788
3861
  }
@@ -6614,18 +6687,22 @@ async function searchFilesViaSearchApi(client, input) {
6614
6687
  if (!id) {
6615
6688
  continue;
6616
6689
  }
6617
- const path = await downloadSlackFile({
6690
+ const result = await tryDownloadSlackFile({
6618
6691
  auth: input.auth,
6619
6692
  url,
6620
6693
  destDir: downloadsDir,
6621
6694
  preferredName: `${id}${ext ? `.${ext}` : ""}`
6622
6695
  });
6696
+ if (!result.ok) {
6697
+ console.warn(`Warning: skipping file ${id}: ${result.error}`);
6698
+ continue;
6699
+ }
6623
6700
  const title = (getString(f.title) || getString(f.name) || "").trim();
6624
6701
  out.push({
6625
6702
  title: title || undefined,
6626
6703
  mimetype,
6627
6704
  mode,
6628
- path
6705
+ path: result.path
6629
6706
  });
6630
6707
  if (out.length >= input.limit) {
6631
6708
  break;
@@ -6680,17 +6757,21 @@ async function searchFilesInChannelsFallback(client, input) {
6680
6757
  if (!id) {
6681
6758
  continue;
6682
6759
  }
6683
- const path = await downloadSlackFile({
6760
+ const result = await tryDownloadSlackFile({
6684
6761
  auth: input.auth,
6685
6762
  url,
6686
6763
  destDir: downloadsDir,
6687
6764
  preferredName: `${id}${ext ? `.${ext}` : ""}`
6688
6765
  });
6766
+ if (!result.ok) {
6767
+ console.warn(`Warning: skipping file ${id}: ${result.error}`);
6768
+ continue;
6769
+ }
6689
6770
  out.push({
6690
6771
  title: title || undefined,
6691
6772
  mimetype,
6692
6773
  mode,
6693
- path
6774
+ path: result.path
6694
6775
  });
6695
6776
  if (out.length >= input.limit) {
6696
6777
  return out;
@@ -6906,13 +6987,25 @@ async function downloadFilesForMessage(input) {
6906
6987
  continue;
6907
6988
  }
6908
6989
  const ext = inferExt(f);
6909
- const path = await downloadSlackFile({
6990
+ const result = await tryDownloadSlackFile({
6910
6991
  auth: input.auth,
6911
6992
  url,
6912
6993
  destDir: input.downloadsDir,
6913
6994
  preferredName: `${f.id}${ext ? `.${ext}` : ""}`
6914
6995
  });
6915
- input.downloadedPaths[f.id] = path;
6996
+ if (!result.ok) {
6997
+ input.downloadedPaths[f.id] = {
6998
+ ...result,
6999
+ path: await writeDownloadErrorFile({
7000
+ destDir: input.downloadsDir,
7001
+ fileId: f.id,
7002
+ error: result.error
7003
+ })
7004
+ };
7005
+ console.warn(`Warning: file ${f.id}: ${result.error}`);
7006
+ } else {
7007
+ input.downloadedPaths[f.id] = result;
7008
+ }
6916
7009
  }
6917
7010
  }
6918
7011
  function messageSummaryFromApiMessage(channelId, msg) {
@@ -7848,5 +7941,5 @@ if (subcommand && subcommand !== "update") {
7848
7941
  backgroundUpdateCheck();
7849
7942
  }
7850
7943
 
7851
- //# debugId=5ADF75C59137BC5064756E2164756E21
7944
+ //# debugId=3EBC31FAC3ADA2FD64756E2164756E21
7852
7945
  //# sourceMappingURL=index.js.map