agent-slack 0.5.5 → 0.6.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/dist/index.js CHANGED
@@ -229,10 +229,11 @@ async function copySqliteForRead(dbPath) {
229
229
 
230
230
  // src/auth/chromium-cookie.ts
231
231
  import { pbkdf2Sync, createDecipheriv } from "node:crypto";
232
- function decryptChromiumCookieValue(data, password, iterations) {
232
+ function decryptChromiumCookieValue(data, options) {
233
233
  if (!data || data.length === 0) {
234
234
  return "";
235
235
  }
236
+ const { password, iterations } = options;
236
237
  if (iterations < 1) {
237
238
  throw new RangeError(`iterations must be >= 1, got ${iterations}`);
238
239
  }
@@ -373,7 +374,7 @@ async function extractCookieDFromBrave() {
373
374
  const passwords = getSafeStoragePasswords();
374
375
  for (const password of passwords) {
375
376
  try {
376
- const decrypted = decryptChromiumCookieValue(data, password, 1003);
377
+ const decrypted = decryptChromiumCookieValue(data, { password, iterations: 1003 });
377
378
  const match = decrypted.match(/xoxd-[A-Za-z0-9%/+_=.-]+/);
378
379
  if (match) {
379
380
  return match[0];
@@ -529,18 +530,10 @@ function parseSlackCurlCommand(curlInput) {
529
530
 
530
531
  // src/auth/desktop.ts
531
532
  import { cp, mkdir, rm as rm2, unlink } from "node:fs/promises";
532
- import {
533
- existsSync as existsSync4,
534
- readFileSync as readFileSync2,
535
- readdirSync,
536
- copyFileSync,
537
- writeFileSync,
538
- unlinkSync
539
- } from "node:fs";
540
- import { execFileSync as execFileSync2 } from "node:child_process";
541
- import { createDecipheriv as createDecipheriv2, randomUUID } from "node:crypto";
542
- import { homedir as homedir3, platform as platform4, tmpdir as tmpdir2 } from "node:os";
543
- import { join as join5 } from "node:path";
533
+ import { existsSync as existsSync5, readdirSync, copyFileSync, unlinkSync as unlinkSync2 } from "node:fs";
534
+ import { execFileSync as execFileSync3 } from "node:child_process";
535
+ import { homedir as homedir3, platform as platform4, tmpdir as tmpdir3 } from "node:os";
536
+ import { join as join6 } from "node:path";
544
537
 
545
538
  // src/lib/leveldb-reader.ts
546
539
  import { readdir as readdir2, readFile as readFile2 } from "node:fs/promises";
@@ -801,32 +794,152 @@ async function findKeysContaining(dir, substring) {
801
794
  return allEntries.filter((entry) => entry.key.includes(substring));
802
795
  }
803
796
 
797
+ // src/auth/desktop-crypto.ts
798
+ import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync, unlinkSync } from "node:fs";
799
+ import { execFileSync as execFileSync2 } from "node:child_process";
800
+ import { createDecipheriv as createDecipheriv2, randomUUID } from "node:crypto";
801
+ import { tmpdir as tmpdir2 } from "node:os";
802
+ import { join as join5 } from "node:path";
803
+ var IS_MACOS4 = process.platform === "darwin";
804
+ var IS_LINUX2 = process.platform === "linux";
805
+ function getSafeStoragePasswords2(prefix) {
806
+ if (IS_MACOS4) {
807
+ const keychainQueries = [
808
+ { service: "Slack Safe Storage", account: "Slack Key" },
809
+ { service: "Slack Safe Storage", account: "Slack App Store Key" },
810
+ { service: "Slack Safe Storage" },
811
+ { service: "Chrome Safe Storage" },
812
+ { service: "Chromium Safe Storage" }
813
+ ];
814
+ const passwords = [];
815
+ for (const q of keychainQueries) {
816
+ try {
817
+ const args = ["-w", "-s", q.service];
818
+ if (q.account) {
819
+ args.push("-a", q.account);
820
+ }
821
+ const out = execFileSync2("security", ["find-generic-password", ...args], {
822
+ encoding: "utf8",
823
+ stdio: ["ignore", "pipe", "ignore"]
824
+ }).trim();
825
+ if (out) {
826
+ passwords.push(out);
827
+ }
828
+ } catch {}
829
+ }
830
+ if (passwords.length > 0) {
831
+ return [...new Set(passwords)];
832
+ }
833
+ }
834
+ if (IS_LINUX2) {
835
+ const attributes = [
836
+ ["application", "com.slack.Slack"],
837
+ ["application", "Slack"],
838
+ ["application", "slack"],
839
+ ["service", "Slack Safe Storage"]
840
+ ];
841
+ const passwords = [];
842
+ for (const pair of attributes) {
843
+ try {
844
+ const out = execFileSync2("secret-tool", ["lookup", ...pair], {
845
+ encoding: "utf8",
846
+ stdio: ["ignore", "pipe", "ignore"]
847
+ }).trim();
848
+ if (out) {
849
+ passwords.push(out);
850
+ }
851
+ } catch {}
852
+ }
853
+ if (prefix === "v11") {
854
+ passwords.push("");
855
+ }
856
+ passwords.push("peanuts");
857
+ return [...new Set(passwords)];
858
+ }
859
+ throw new Error("Could not read Safe Storage password from desktop keychain.");
860
+ }
861
+ function decryptCookieWindows(encrypted, slackDataDir) {
862
+ const localStatePath = join5(slackDataDir, "Local State");
863
+ if (!existsSync4(localStatePath)) {
864
+ throw new Error(`Local State file not found: ${localStatePath}`);
865
+ }
866
+ let localState;
867
+ try {
868
+ localState = JSON.parse(readFileSync2(localStatePath, "utf8"));
869
+ } catch (error) {
870
+ throw new Error(`Failed to parse Local State file: ${localStatePath}`, { cause: error });
871
+ }
872
+ const osCrypt = isRecord(localState) ? localState.os_crypt : undefined;
873
+ if (!isRecord(osCrypt) || typeof osCrypt.encrypted_key !== "string") {
874
+ throw new Error("No os_crypt.encrypted_key in Local State");
875
+ }
876
+ const encKeyFull = Buffer.from(osCrypt.encrypted_key, "base64");
877
+ const encKeyBlob = encKeyFull.subarray(5);
878
+ const id = randomUUID();
879
+ const encKeyFile = join5(tmpdir2(), `as-key-enc-${id}.bin`);
880
+ const decKeyFile = join5(tmpdir2(), `as-key-dec-${id}.bin`);
881
+ writeFileSync(encKeyFile, encKeyBlob, { mode: 384 });
882
+ try {
883
+ const psEncKeyFile = encKeyFile.replaceAll("'", "''");
884
+ const psDecKeyFile = decKeyFile.replaceAll("'", "''");
885
+ const psCmd = [
886
+ "Add-Type -AssemblyName System.Security",
887
+ `$e=[System.IO.File]::ReadAllBytes('${psEncKeyFile}')`,
888
+ "$d=[System.Security.Cryptography.ProtectedData]::Unprotect($e,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
889
+ `[System.IO.File]::WriteAllBytes('${psDecKeyFile}',$d)`
890
+ ].join("; ");
891
+ execFileSync2("powershell", ["-ExecutionPolicy", "Bypass", "-Command", psCmd], {
892
+ stdio: "pipe"
893
+ });
894
+ if (!existsSync4(decKeyFile)) {
895
+ throw new Error("DPAPI decryption failed: PowerShell did not produce the decrypted key file");
896
+ }
897
+ const aesKey = readFileSync2(decKeyFile);
898
+ const nonce = encrypted.subarray(3, 15);
899
+ const ciphertextWithTag = encrypted.subarray(15);
900
+ const tag = ciphertextWithTag.subarray(-16);
901
+ const ciphertext = ciphertextWithTag.subarray(0, -16);
902
+ const decipher = createDecipheriv2("aes-256-gcm", aesKey, nonce);
903
+ decipher.setAuthTag(tag);
904
+ let decrypted = decipher.update(ciphertext, undefined, "utf8");
905
+ decrypted += decipher.final("utf8");
906
+ return decrypted;
907
+ } finally {
908
+ try {
909
+ unlinkSync(encKeyFile);
910
+ } catch {}
911
+ try {
912
+ unlinkSync(decKeyFile);
913
+ } catch {}
914
+ }
915
+ }
916
+
804
917
  // src/auth/desktop.ts
805
918
  var PLATFORM2 = platform4();
806
- var IS_MACOS4 = PLATFORM2 === "darwin";
807
- var IS_LINUX2 = PLATFORM2 === "linux";
919
+ var IS_MACOS5 = PLATFORM2 === "darwin";
920
+ var IS_LINUX3 = PLATFORM2 === "linux";
808
921
  var IS_WIN32 = PLATFORM2 === "win32";
809
- var SLACK_SUPPORT_DIR_ELECTRON = join5(homedir3(), "Library", "Application Support", "Slack");
810
- var SLACK_SUPPORT_DIR_APPSTORE = join5(homedir3(), "Library", "Containers", "com.tinyspeck.slackmacgap", "Data", "Library", "Application Support", "Slack");
811
- var SLACK_SUPPORT_DIR_LINUX = join5(homedir3(), ".config", "Slack");
812
- var SLACK_SUPPORT_DIR_LINUX_FLATPAK = join5(homedir3(), ".var", "app", "com.slack.Slack", "config", "Slack");
813
- var SLACK_SUPPORT_DIR_WIN_APPDATA = join5(process.env.APPDATA || join5(homedir3(), "AppData", "Roaming"), "Slack");
922
+ var SLACK_SUPPORT_DIR_ELECTRON = join6(homedir3(), "Library", "Application Support", "Slack");
923
+ var SLACK_SUPPORT_DIR_APPSTORE = join6(homedir3(), "Library", "Containers", "com.tinyspeck.slackmacgap", "Data", "Library", "Application Support", "Slack");
924
+ var SLACK_SUPPORT_DIR_LINUX = join6(homedir3(), ".config", "Slack");
925
+ var SLACK_SUPPORT_DIR_LINUX_FLATPAK = join6(homedir3(), ".var", "app", "com.slack.Slack", "config", "Slack");
926
+ var SLACK_SUPPORT_DIR_WIN_APPDATA = join6(process.env.APPDATA || join6(homedir3(), "AppData", "Roaming"), "Slack");
814
927
  function getWindowsStoreSlackPath() {
815
- const pkgBase = join5(process.env.LOCALAPPDATA || join5(homedir3(), "AppData", "Local"), "Packages");
928
+ const pkgBase = join6(process.env.LOCALAPPDATA || join6(homedir3(), "AppData", "Local"), "Packages");
816
929
  try {
817
930
  const entries = readdirSync(pkgBase);
818
931
  const slackPkg = entries.find((e) => e.startsWith("com.tinyspeck.slackdesktop_"));
819
932
  if (slackPkg) {
820
- return join5(pkgBase, slackPkg, "LocalCache", "Roaming", "Slack");
933
+ return join6(pkgBase, slackPkg, "LocalCache", "Roaming", "Slack");
821
934
  }
822
935
  } catch {}
823
936
  return null;
824
937
  }
825
938
  function getAllSlackPaths() {
826
939
  let candidates;
827
- if (IS_MACOS4) {
940
+ if (IS_MACOS5) {
828
941
  candidates = [SLACK_SUPPORT_DIR_ELECTRON, SLACK_SUPPORT_DIR_APPSTORE];
829
- } else if (IS_LINUX2) {
942
+ } else if (IS_LINUX3) {
830
943
  candidates = [SLACK_SUPPORT_DIR_LINUX_FLATPAK, SLACK_SUPPORT_DIR_LINUX];
831
944
  } else if (IS_WIN32) {
832
945
  candidates = [SLACK_SUPPORT_DIR_WIN_APPDATA];
@@ -842,16 +955,16 @@ function getAllSlackPaths() {
842
955
  }
843
956
  const results = [];
844
957
  for (const dir of candidates) {
845
- const leveldbDir = join5(dir, "Local Storage", "leveldb");
846
- if (existsSync4(leveldbDir)) {
847
- const cookiesDbCandidates = [join5(dir, "Network", "Cookies"), join5(dir, "Cookies")];
848
- const cookiesDb = cookiesDbCandidates.find((candidate) => existsSync4(candidate)) || cookiesDbCandidates[0];
958
+ const leveldbDir = join6(dir, "Local Storage", "leveldb");
959
+ if (existsSync5(leveldbDir)) {
960
+ const cookiesDbCandidates = [join6(dir, "Network", "Cookies"), join6(dir, "Cookies")];
961
+ const cookiesDb = cookiesDbCandidates.find((candidate) => existsSync5(candidate)) || cookiesDbCandidates[0];
849
962
  results.push({ leveldbDir, cookiesDb, baseDir: dir });
850
963
  }
851
964
  }
852
965
  if (results.length === 0) {
853
966
  throw new Error(`Slack Desktop data not found. Checked:
854
- - ${candidates.map((d) => join5(d, "Local Storage", "leveldb")).join(`
967
+ - ${candidates.map((d) => join6(d, "Local Storage", "leveldb")).join(`
855
968
  - `)}`);
856
969
  }
857
970
  return results;
@@ -869,13 +982,13 @@ function toDesktopTeam(value) {
869
982
  return { url, name, token };
870
983
  }
871
984
  async function snapshotLevelDb(srcDir) {
872
- const base = join5(homedir3(), ".config", "agent-slack", "cache", "leveldb-snapshots");
873
- const dest = join5(base, `${Date.now()}`);
985
+ const base = join6(homedir3(), ".config", "agent-slack", "cache", "leveldb-snapshots");
986
+ const dest = join6(base, `${Date.now()}`);
874
987
  await mkdir(base, { recursive: true });
875
- let useNodeCopy = !IS_MACOS4;
876
- if (IS_MACOS4) {
988
+ let useNodeCopy = !IS_MACOS5;
989
+ if (IS_MACOS5) {
877
990
  try {
878
- execFileSync2("cp", ["-cR", srcDir, dest], {
991
+ execFileSync3("cp", ["-cR", srcDir, dest], {
879
992
  stdio: ["ignore", "ignore", "ignore"]
880
993
  });
881
994
  } catch {
@@ -886,7 +999,7 @@ async function snapshotLevelDb(srcDir) {
886
999
  await cp(srcDir, dest, { recursive: true, force: true });
887
1000
  }
888
1001
  try {
889
- await unlink(join5(dest, "LOCK"));
1002
+ await unlink(join6(dest, "LOCK"));
890
1003
  } catch {}
891
1004
  return dest;
892
1005
  }
@@ -928,7 +1041,7 @@ function parseLocalConfig(raw) {
928
1041
  throw lastErr || new Error("localConfig not parseable");
929
1042
  }
930
1043
  async function extractTeamsFromSlackLevelDb(leveldbDir) {
931
- if (!existsSync4(leveldbDir)) {
1044
+ if (!existsSync5(leveldbDir)) {
932
1045
  throw new Error(`Slack LevelDB not found: ${leveldbDir}`);
933
1046
  }
934
1047
  const snap = await snapshotLevelDb(leveldbDir);
@@ -969,124 +1082,13 @@ async function extractTeamsFromSlackLevelDb(leveldbDir) {
969
1082
  } catch {}
970
1083
  }
971
1084
  }
972
- function getSafeStoragePasswords2(prefix) {
973
- if (IS_MACOS4) {
974
- const keychainQueries = [
975
- { service: "Slack Safe Storage", account: "Slack Key" },
976
- { service: "Slack Safe Storage", account: "Slack App Store Key" },
977
- { service: "Slack Safe Storage" },
978
- { service: "Chrome Safe Storage" },
979
- { service: "Chromium Safe Storage" }
980
- ];
981
- const passwords = [];
982
- for (const q of keychainQueries) {
983
- try {
984
- const args = ["-w", "-s", q.service];
985
- if (q.account) {
986
- args.push("-a", q.account);
987
- }
988
- const out = execFileSync2("security", ["find-generic-password", ...args], {
989
- encoding: "utf8",
990
- stdio: ["ignore", "pipe", "ignore"]
991
- }).trim();
992
- if (out) {
993
- passwords.push(out);
994
- }
995
- } catch {}
996
- }
997
- if (passwords.length > 0) {
998
- return [...new Set(passwords)];
999
- }
1000
- }
1001
- if (IS_LINUX2) {
1002
- const attributes = [
1003
- ["application", "com.slack.Slack"],
1004
- ["application", "Slack"],
1005
- ["application", "slack"],
1006
- ["service", "Slack Safe Storage"]
1007
- ];
1008
- const passwords = [];
1009
- for (const pair of attributes) {
1010
- try {
1011
- const out = execFileSync2("secret-tool", ["lookup", ...pair], {
1012
- encoding: "utf8",
1013
- stdio: ["ignore", "pipe", "ignore"]
1014
- }).trim();
1015
- if (out) {
1016
- passwords.push(out);
1017
- }
1018
- } catch {}
1019
- }
1020
- if (prefix === "v11") {
1021
- passwords.push("");
1022
- }
1023
- passwords.push("peanuts");
1024
- return [...new Set(passwords)];
1025
- }
1026
- throw new Error("Could not read Safe Storage password from desktop keychain.");
1027
- }
1028
- function decryptCookieWindows(encrypted, slackDataDir) {
1029
- const localStatePath = join5(slackDataDir, "Local State");
1030
- if (!existsSync4(localStatePath)) {
1031
- throw new Error(`Local State file not found: ${localStatePath}`);
1032
- }
1033
- let localState;
1034
- try {
1035
- localState = JSON.parse(readFileSync2(localStatePath, "utf8"));
1036
- } catch (error) {
1037
- throw new Error(`Failed to parse Local State file: ${localStatePath}`, { cause: error });
1038
- }
1039
- const osCrypt = isRecord(localState) ? localState.os_crypt : undefined;
1040
- if (!isRecord(osCrypt) || typeof osCrypt.encrypted_key !== "string") {
1041
- throw new Error("No os_crypt.encrypted_key in Local State");
1042
- }
1043
- const encKeyFull = Buffer.from(osCrypt.encrypted_key, "base64");
1044
- const encKeyBlob = encKeyFull.subarray(5);
1045
- const id = randomUUID();
1046
- const encKeyFile = join5(tmpdir2(), `as-key-enc-${id}.bin`);
1047
- const decKeyFile = join5(tmpdir2(), `as-key-dec-${id}.bin`);
1048
- writeFileSync(encKeyFile, encKeyBlob, { mode: 384 });
1049
- try {
1050
- const psEncKeyFile = encKeyFile.replaceAll("'", "''");
1051
- const psDecKeyFile = decKeyFile.replaceAll("'", "''");
1052
- const psCmd = [
1053
- "Add-Type -AssemblyName System.Security",
1054
- `$e=[System.IO.File]::ReadAllBytes('${psEncKeyFile}')`,
1055
- "$d=[System.Security.Cryptography.ProtectedData]::Unprotect($e,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser)",
1056
- `[System.IO.File]::WriteAllBytes('${psDecKeyFile}',$d)`
1057
- ].join("; ");
1058
- execFileSync2("powershell", ["-ExecutionPolicy", "Bypass", "-Command", psCmd], {
1059
- stdio: "pipe"
1060
- });
1061
- if (!existsSync4(decKeyFile)) {
1062
- throw new Error("DPAPI decryption failed: PowerShell did not produce the decrypted key file");
1063
- }
1064
- const aesKey = readFileSync2(decKeyFile);
1065
- const nonce = encrypted.subarray(3, 15);
1066
- const ciphertextWithTag = encrypted.subarray(15);
1067
- const tag = ciphertextWithTag.subarray(-16);
1068
- const ciphertext = ciphertextWithTag.subarray(0, -16);
1069
- const decipher = createDecipheriv2("aes-256-gcm", aesKey, nonce);
1070
- decipher.setAuthTag(tag);
1071
- let decrypted = decipher.update(ciphertext, undefined, "utf8");
1072
- decrypted += decipher.final("utf8");
1073
- return decrypted;
1074
- } finally {
1075
- try {
1076
- unlinkSync(encKeyFile);
1077
- } catch {}
1078
- try {
1079
- unlinkSync(decKeyFile);
1080
- } catch {}
1081
- }
1082
- }
1083
1085
  async function extractCookieDFromSlackCookiesDb(cookiesPath, slackDataDir) {
1084
- if (!existsSync4(cookiesPath)) {
1086
+ if (!existsSync5(cookiesPath)) {
1085
1087
  throw new Error(`Slack Cookies DB not found: ${cookiesPath}`);
1086
1088
  }
1087
1089
  let dbPathToQuery = cookiesPath;
1088
1090
  if (IS_WIN32) {
1089
- const tmpCopy = join5(tmpdir2(), `agent-slack-cookies-${Date.now()}`);
1091
+ const tmpCopy = join6(tmpdir3(), `agent-slack-cookies-${Date.now()}`);
1090
1092
  copyFileSync(cookiesPath, tmpCopy);
1091
1093
  dbPathToQuery = tmpCopy;
1092
1094
  }
@@ -1096,7 +1098,7 @@ async function extractCookieDFromSlackCookiesDb(cookiesPath, slackDataDir) {
1096
1098
  } finally {
1097
1099
  if (IS_WIN32 && dbPathToQuery !== cookiesPath) {
1098
1100
  try {
1099
- unlinkSync(dbPathToQuery);
1101
+ unlinkSync2(dbPathToQuery);
1100
1102
  } catch {}
1101
1103
  }
1102
1104
  }
@@ -1128,7 +1130,10 @@ async function extractCookieDFromSlackCookiesDb(cookiesPath, slackDataDir) {
1128
1130
  const passwords = getSafeStoragePasswords2(prefix);
1129
1131
  for (const password of passwords) {
1130
1132
  try {
1131
- const decrypted = decryptChromiumCookieValue(data, password, IS_LINUX2 ? 1 : 1003);
1133
+ const decrypted = decryptChromiumCookieValue(data, {
1134
+ password,
1135
+ iterations: IS_LINUX3 ? 1 : 1003
1136
+ });
1132
1137
  const match = decrypted.match(/xoxd-[A-Za-z0-9%/+_=.-]+/);
1133
1138
  if (match) {
1134
1139
  return match[0];
@@ -1159,8 +1164,8 @@ async function extractFromSlackDesktop() {
1159
1164
  }
1160
1165
 
1161
1166
  // src/auth/firefox.ts
1162
- import { existsSync as existsSync5 } from "node:fs";
1163
- import { join as join6 } from "node:path";
1167
+ import { existsSync as existsSync6 } from "node:fs";
1168
+ import { join as join7 } from "node:path";
1164
1169
  var CONTROL_CHAR_RE = /[\u0000-\u001F]/g;
1165
1170
  function toStringValue(value) {
1166
1171
  if (typeof value === "string") {
@@ -1267,21 +1272,21 @@ function extractTeamsFromRawText(raw) {
1267
1272
  return teams;
1268
1273
  }
1269
1274
  function getLocalStorageDirs(profilePath) {
1270
- const roots = [join6(profilePath, "storage", "default")];
1275
+ const roots = [join7(profilePath, "storage", "default")];
1271
1276
  const candidates = [];
1272
1277
  for (const root of roots) {
1273
- if (!existsSync5(root)) {
1278
+ if (!existsSync6(root)) {
1274
1279
  continue;
1275
1280
  }
1276
- candidates.push(join6(root, "https+++app.slack.com", "ls"));
1281
+ candidates.push(join7(root, "https+++app.slack.com", "ls"));
1277
1282
  }
1278
1283
  return candidates;
1279
1284
  }
1280
1285
  async function extractTeamsFromProfile(profilePath) {
1281
1286
  const lsDirs = getLocalStorageDirs(profilePath);
1282
1287
  for (const lsDir of lsDirs) {
1283
- const dbPath = join6(lsDir, "data.sqlite");
1284
- if (!existsSync5(dbPath)) {
1288
+ const dbPath = join7(lsDir, "data.sqlite");
1289
+ if (!existsSync6(dbPath)) {
1285
1290
  continue;
1286
1291
  }
1287
1292
  const copied = await copySqliteForRead(dbPath);
@@ -1306,8 +1311,8 @@ async function extractTeamsFromProfile(profilePath) {
1306
1311
  return null;
1307
1312
  }
1308
1313
  async function extractCookieDFromProfile(profilePath) {
1309
- const dbPath = join6(profilePath, "cookies.sqlite");
1310
- if (!existsSync5(dbPath)) {
1314
+ const dbPath = join7(profilePath, "cookies.sqlite");
1315
+ if (!existsSync6(dbPath)) {
1311
1316
  return null;
1312
1317
  }
1313
1318
  const copied = await copySqliteForRead(dbPath);
@@ -1368,9 +1373,9 @@ async function extractFromFirefox(input) {
1368
1373
 
1369
1374
  // src/auth/paths.ts
1370
1375
  import { homedir as homedir4 } from "node:os";
1371
- import { join as join7 } from "node:path";
1372
- var AGENT_SLACK_DIR = join7(homedir4(), ".config", "agent-slack");
1373
- var CREDENTIALS_FILE = join7(AGENT_SLACK_DIR, "credentials.json");
1376
+ import { join as join8 } from "node:path";
1377
+ var AGENT_SLACK_DIR = join8(homedir4(), ".config", "agent-slack");
1378
+ var CREDENTIALS_FILE = join8(AGENT_SLACK_DIR, "credentials.json");
1374
1379
  var KEYCHAIN_SERVICE = "agent-slack";
1375
1380
 
1376
1381
  // src/lib/fs.ts
@@ -1425,31 +1430,31 @@ var CredentialsSchema = z.object({
1425
1430
 
1426
1431
  // src/auth/keychain.ts
1427
1432
  import { platform as platform5 } from "node:os";
1428
- import { execFileSync as execFileSync3 } from "node:child_process";
1429
- var IS_MACOS5 = platform5() === "darwin";
1433
+ import { execFileSync as execFileSync4 } from "node:child_process";
1434
+ var IS_MACOS6 = platform5() === "darwin";
1430
1435
  function keychainGet(account, service) {
1431
- if (!IS_MACOS5) {
1436
+ if (!IS_MACOS6) {
1432
1437
  return null;
1433
1438
  }
1434
1439
  try {
1435
- const result = execFileSync3("security", ["find-generic-password", "-s", service, "-a", account, "-w"], { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] });
1440
+ const result = execFileSync4("security", ["find-generic-password", "-s", service, "-a", account, "-w"], { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"] });
1436
1441
  return result.trim() || null;
1437
1442
  } catch {
1438
1443
  return null;
1439
1444
  }
1440
1445
  }
1441
1446
  function keychainSet(input) {
1442
- if (!IS_MACOS5) {
1447
+ if (!IS_MACOS6) {
1443
1448
  return false;
1444
1449
  }
1445
1450
  const { account, value, service } = input;
1446
1451
  try {
1447
1452
  try {
1448
- execFileSync3("security", ["delete-generic-password", "-s", service, "-a", account], {
1453
+ execFileSync4("security", ["delete-generic-password", "-s", service, "-a", account], {
1449
1454
  stdio: ["pipe", "pipe", "ignore"]
1450
1455
  });
1451
1456
  } catch {}
1452
- execFileSync3("security", ["add-generic-password", "-s", service, "-a", account, "-w", value], {
1457
+ execFileSync4("security", ["add-generic-password", "-s", service, "-a", account, "-w", value], {
1453
1458
  stdio: "pipe"
1454
1459
  });
1455
1460
  return true;
@@ -1461,7 +1466,7 @@ function keychainSet(input) {
1461
1466
  // src/auth/store.ts
1462
1467
  import { platform as platform6 } from "node:os";
1463
1468
  var KEYCHAIN_PLACEHOLDER = "__KEYCHAIN__";
1464
- var IS_MACOS6 = platform6() === "darwin";
1469
+ var IS_MACOS7 = platform6() === "darwin";
1465
1470
  function normalizeWorkspaceUrl(workspaceUrl) {
1466
1471
  const u = new URL(workspaceUrl);
1467
1472
  return `${u.protocol}//${u.host}`;
@@ -1515,7 +1520,7 @@ async function saveCredentials(credentials) {
1515
1520
  }))
1516
1521
  };
1517
1522
  const filePayload = structuredClone(payload);
1518
- if (IS_MACOS6) {
1523
+ if (IS_MACOS7) {
1519
1524
  const firstBrowser = payload.workspaces.find((w) => w.auth.auth_type === "browser");
1520
1525
  let xoxdStored = false;
1521
1526
  if (firstBrowser?.auth.auth_type === "browser" && !isPlaceholderSecret(firstBrowser.auth.xoxd_cookie)) {
@@ -1752,6 +1757,10 @@ function normalizeConversationsPage(resp) {
1752
1757
  function normalizeConversationsLimit(value) {
1753
1758
  return Math.min(Math.max(value ?? 100, 1), 1000);
1754
1759
  }
1760
+ async function markConversation(client, options) {
1761
+ const { channelId, ts } = options;
1762
+ await client.api("conversations.mark", { channel: channelId, ts });
1763
+ }
1755
1764
 
1756
1765
  // src/cli/workspace-selector.ts
1757
1766
  function normalizeUrl(u) {
@@ -2406,15 +2415,15 @@ function registerAuthCommand(input) {
2406
2415
 
2407
2416
  // src/slack/files.ts
2408
2417
  import { mkdir as mkdir3, writeFile as writeFile2 } from "node:fs/promises";
2409
- import { basename, join as join8, resolve } from "node:path";
2410
- import { existsSync as existsSync6 } from "node:fs";
2418
+ import { basename, join as join9, resolve } from "node:path";
2419
+ import { existsSync as existsSync7 } from "node:fs";
2411
2420
  async function downloadSlackFile(input) {
2412
2421
  const { auth, url, destDir, preferredName, options } = input;
2413
2422
  const absDir = resolve(destDir);
2414
2423
  await mkdir3(absDir, { recursive: true });
2415
2424
  const name = sanitizeFilename(preferredName || basename(new URL(url).pathname) || "file");
2416
- const path = join8(absDir, name);
2417
- if (existsSync6(path)) {
2425
+ const path = join9(absDir, name);
2426
+ if (existsSync7(path)) {
2418
2427
  return path;
2419
2428
  }
2420
2429
  const headers = {};
@@ -2469,27 +2478,27 @@ function extractTag(html, tag) {
2469
2478
  }
2470
2479
 
2471
2480
  // src/lib/tmp-paths.ts
2472
- import { join as join10, resolve as resolve2 } from "node:path";
2481
+ import { join as join11, resolve as resolve2 } from "node:path";
2473
2482
  import { mkdir as mkdir4 } from "node:fs/promises";
2474
2483
 
2475
2484
  // src/lib/app-dir.ts
2476
- import { homedir as homedir5, tmpdir as tmpdir3 } from "node:os";
2477
- import { join as join9 } from "node:path";
2485
+ import { homedir as homedir5, tmpdir as tmpdir4 } from "node:os";
2486
+ import { join as join10 } from "node:path";
2478
2487
  function getAppDir() {
2479
2488
  const xdg = process.env.XDG_RUNTIME_DIR?.trim();
2480
2489
  if (xdg) {
2481
- return join9(xdg, "agent-slack");
2490
+ return join10(xdg, "agent-slack");
2482
2491
  }
2483
2492
  const home = homedir5();
2484
2493
  if (home) {
2485
- return join9(home, ".agent-slack");
2494
+ return join10(home, ".agent-slack");
2486
2495
  }
2487
- return join9(tmpdir3(), "agent-slack");
2496
+ return join10(tmpdir4(), "agent-slack");
2488
2497
  }
2489
2498
 
2490
2499
  // src/lib/tmp-paths.ts
2491
2500
  function getDownloadsDir() {
2492
- return resolve2(join10(getAppDir(), "tmp", "downloads"));
2501
+ return resolve2(join11(getAppDir(), "tmp", "downloads"));
2493
2502
  }
2494
2503
  async function ensureDownloadsDir() {
2495
2504
  const dir = getDownloadsDir();
@@ -3691,7 +3700,7 @@ async function uploadLocalFileToSlack(input) {
3691
3700
 
3692
3701
  // src/cli/message-file-downloads.ts
3693
3702
  import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
3694
- import { join as join11 } from "node:path";
3703
+ import { join as join12 } from "node:path";
3695
3704
  function inferFileExtension(file) {
3696
3705
  const mt = (file.mimetype || "").toLowerCase();
3697
3706
  const ft = (file.filetype || "").toLowerCase();
@@ -3738,7 +3747,7 @@ async function downloadCanvasAsMarkdown(input) {
3738
3747
  }
3739
3748
  const markdown = htmlToMarkdown(html).trim();
3740
3749
  const safeName = `${input.fileId.replace(/[\\/<>"|?*]/g, "_")}.md`;
3741
- const markdownPath = join11(input.destDir, safeName);
3750
+ const markdownPath = join12(input.destDir, safeName);
3742
3751
  await writeFile3(markdownPath, markdown, "utf8");
3743
3752
  return markdownPath;
3744
3753
  }
@@ -7073,12 +7082,12 @@ function registerSearchCommand(input) {
7073
7082
  import { execSync as execSync2 } from "node:child_process";
7074
7083
  import { createHash } from "node:crypto";
7075
7084
  import { chmod, copyFile as copyFile2, mkdir as mkdir5, readFile as readFile7, rename, rm as rm3, writeFile as writeFile4 } from "node:fs/promises";
7076
- import { tmpdir as tmpdir4 } from "node:os";
7077
- import { basename as basename3, join as join12 } from "node:path";
7085
+ import { tmpdir as tmpdir5 } from "node:os";
7086
+ import { basename as basename3, join as join13 } from "node:path";
7078
7087
  var REPO = "stablyai/agent-slack";
7079
7088
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
7080
7089
  function getCachePath() {
7081
- return join12(getAppDir(), "update-check.json");
7090
+ return join13(getAppDir(), "update-check.json");
7082
7091
  }
7083
7092
  function compareSemver(a, b) {
7084
7093
  const pa = a.replace(/^v/, "").split(".").map(Number);
@@ -7180,10 +7189,10 @@ async function performUpdate(latest) {
7180
7189
  const asset = detectPlatformAsset();
7181
7190
  const tag = `v${latest}`;
7182
7191
  const baseUrl = `https://github.com/${REPO}/releases/download/${tag}`;
7183
- const tmp = join12(tmpdir4(), `agent-slack-update-${Date.now()}`);
7192
+ const tmp = join13(tmpdir5(), `agent-slack-update-${Date.now()}`);
7184
7193
  await mkdir5(tmp, { recursive: true });
7185
- const binTmp = join12(tmp, asset);
7186
- const sumsTmp = join12(tmp, "checksums-sha256.txt");
7194
+ const binTmp = join13(tmp, asset);
7195
+ const sumsTmp = join13(tmp, "checksums-sha256.txt");
7187
7196
  try {
7188
7197
  const [binResp, sumsResp] = await Promise.all([
7189
7198
  fetch(`${baseUrl}/${asset}`, { signal: AbortSignal.timeout(120000) }),
@@ -7774,6 +7783,49 @@ function registerChannelCommand(input) {
7774
7783
  process.exitCode = 1;
7775
7784
  }
7776
7785
  });
7786
+ channelCmd.command("mark").description("Mark a channel/DM as read up to a given message").argument("<target>", "Slack message URL, #channel, or channel ID").option("--ts <ts>", "Message ts to mark as read (required when target is a channel name/ID)").option("--workspace <url>", "Workspace selector (full URL or unique substring; required if you have multiple workspaces)").action(async (...args) => {
7787
+ const [targetArg, options] = args;
7788
+ try {
7789
+ const target = parseMsgTarget(targetArg);
7790
+ let channelId;
7791
+ let ts;
7792
+ let workspaceUrl;
7793
+ if (target.kind === "url") {
7794
+ if (options.workspace) {
7795
+ throw new Error("--workspace cannot be used with a URL target; the workspace is derived from the URL");
7796
+ }
7797
+ channelId = target.ref.channel_id;
7798
+ ts = options.ts ?? target.ref.message_ts;
7799
+ workspaceUrl = input.ctx.effectiveWorkspaceUrl(target.ref.workspace_url);
7800
+ } else if (target.kind === "channel") {
7801
+ if (!options.ts) {
7802
+ throw new Error("--ts is required when target is a channel name or ID");
7803
+ }
7804
+ ({ ts } = options);
7805
+ workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
7806
+ channelId = target.channel;
7807
+ } else {
7808
+ throw new Error("User targets are not supported for channel mark");
7809
+ }
7810
+ await input.ctx.assertWorkspaceSpecifiedForChannelNames({
7811
+ workspaceUrl,
7812
+ channels: [channelId]
7813
+ });
7814
+ const resolvedId = await input.ctx.withAutoRefresh({
7815
+ workspaceUrl,
7816
+ work: async () => {
7817
+ const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
7818
+ const resolved = await resolveChannelId(client, channelId);
7819
+ await markConversation(client, { channelId: resolved, ts });
7820
+ return resolved;
7821
+ }
7822
+ });
7823
+ console.log(JSON.stringify({ ok: true, channel: resolvedId, ts }, null, 2));
7824
+ } catch (err) {
7825
+ console.error(input.ctx.errorMessage(err));
7826
+ process.exitCode = 1;
7827
+ }
7828
+ });
7777
7829
  }
7778
7830
 
7779
7831
  // src/index.ts
@@ -7796,5 +7848,5 @@ if (subcommand && subcommand !== "update") {
7796
7848
  backgroundUpdateCheck();
7797
7849
  }
7798
7850
 
7799
- //# debugId=0E45A4F2F731E07C64756E2164756E21
7851
+ //# debugId=5ADF75C59137BC5064756E2164756E21
7800
7852
  //# sourceMappingURL=index.js.map