agent-slack 0.5.5 → 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 +1 -1
- package/dist/index.js +365 -220
- package/dist/index.js.map +16 -15
- package/package.json +1 -1
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,
|
|
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
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
|
807
|
-
var
|
|
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 =
|
|
810
|
-
var SLACK_SUPPORT_DIR_APPSTORE =
|
|
811
|
-
var SLACK_SUPPORT_DIR_LINUX =
|
|
812
|
-
var SLACK_SUPPORT_DIR_LINUX_FLATPAK =
|
|
813
|
-
var SLACK_SUPPORT_DIR_WIN_APPDATA =
|
|
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 =
|
|
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
|
|
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 (
|
|
940
|
+
if (IS_MACOS5) {
|
|
828
941
|
candidates = [SLACK_SUPPORT_DIR_ELECTRON, SLACK_SUPPORT_DIR_APPSTORE];
|
|
829
|
-
} else if (
|
|
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 =
|
|
846
|
-
if (
|
|
847
|
-
const cookiesDbCandidates = [
|
|
848
|
-
const cookiesDb = cookiesDbCandidates.find((candidate) =>
|
|
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) =>
|
|
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 =
|
|
873
|
-
const dest =
|
|
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 = !
|
|
876
|
-
if (
|
|
988
|
+
let useNodeCopy = !IS_MACOS5;
|
|
989
|
+
if (IS_MACOS5) {
|
|
877
990
|
try {
|
|
878
|
-
|
|
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(
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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
|
|
1163
|
-
import { join as
|
|
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 = [
|
|
1275
|
+
const roots = [join7(profilePath, "storage", "default")];
|
|
1271
1276
|
const candidates = [];
|
|
1272
1277
|
for (const root of roots) {
|
|
1273
|
-
if (!
|
|
1278
|
+
if (!existsSync6(root)) {
|
|
1274
1279
|
continue;
|
|
1275
1280
|
}
|
|
1276
|
-
candidates.push(
|
|
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 =
|
|
1284
|
-
if (!
|
|
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 =
|
|
1310
|
-
if (!
|
|
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
|
|
1372
|
-
var AGENT_SLACK_DIR =
|
|
1373
|
-
var CREDENTIALS_FILE =
|
|
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
|
|
1429
|
-
var
|
|
1433
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
1434
|
+
var IS_MACOS6 = platform5() === "darwin";
|
|
1430
1435
|
function keychainGet(account, service) {
|
|
1431
|
-
if (!
|
|
1436
|
+
if (!IS_MACOS6) {
|
|
1432
1437
|
return null;
|
|
1433
1438
|
}
|
|
1434
1439
|
try {
|
|
1435
|
-
const result =
|
|
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 (!
|
|
1447
|
+
if (!IS_MACOS6) {
|
|
1443
1448
|
return false;
|
|
1444
1449
|
}
|
|
1445
1450
|
const { account, value, service } = input;
|
|
1446
1451
|
try {
|
|
1447
1452
|
try {
|
|
1448
|
-
|
|
1453
|
+
execFileSync4("security", ["delete-generic-password", "-s", service, "-a", account], {
|
|
1449
1454
|
stdio: ["pipe", "pipe", "ignore"]
|
|
1450
1455
|
});
|
|
1451
1456
|
} catch {}
|
|
1452
|
-
|
|
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
|
|
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 (
|
|
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,23 @@ 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
|
|
2410
|
-
import { existsSync as
|
|
2418
|
+
import { basename, join as join9, resolve } from "node:path";
|
|
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
|
+
}
|
|
2411
2428
|
async function downloadSlackFile(input) {
|
|
2412
2429
|
const { auth, url, destDir, preferredName, options } = input;
|
|
2413
2430
|
const absDir = resolve(destDir);
|
|
2414
2431
|
await mkdir3(absDir, { recursive: true });
|
|
2415
2432
|
const name = sanitizeFilename(preferredName || basename(new URL(url).pathname) || "file");
|
|
2416
|
-
const path =
|
|
2417
|
-
if (
|
|
2433
|
+
const path = join9(absDir, name);
|
|
2434
|
+
if (existsSync7(path)) {
|
|
2418
2435
|
return path;
|
|
2419
2436
|
}
|
|
2420
2437
|
const headers = {};
|
|
@@ -2426,19 +2443,54 @@ async function downloadSlackFile(input) {
|
|
|
2426
2443
|
headers.Referer = "https://app.slack.com/";
|
|
2427
2444
|
headers["User-Agent"] = getUserAgent();
|
|
2428
2445
|
}
|
|
2429
|
-
|
|
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
|
+
}
|
|
2430
2452
|
if (!resp.ok) {
|
|
2431
|
-
throw new
|
|
2453
|
+
throw new SlackDownloadError(`Failed to download file (${resp.status})`, resp.status);
|
|
2432
2454
|
}
|
|
2433
2455
|
const contentType = resp.headers.get("content-type") || "";
|
|
2434
2456
|
if (!options?.allowHtml && contentType.includes("text/html")) {
|
|
2435
|
-
|
|
2436
|
-
|
|
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);
|
|
2437
2464
|
}
|
|
2438
|
-
|
|
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);
|
|
2470
|
+
}
|
|
2471
|
+
const buf = Buffer.from(arrayBuffer);
|
|
2439
2472
|
await writeFile2(path, buf);
|
|
2440
2473
|
return path;
|
|
2441
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
|
+
}
|
|
2442
2494
|
function sanitizeFilename(name) {
|
|
2443
2495
|
return name.replace(/[\\/<>:"|?*]/g, "_");
|
|
2444
2496
|
}
|
|
@@ -2469,27 +2521,27 @@ function extractTag(html, tag) {
|
|
|
2469
2521
|
}
|
|
2470
2522
|
|
|
2471
2523
|
// src/lib/tmp-paths.ts
|
|
2472
|
-
import { join as
|
|
2524
|
+
import { join as join11, resolve as resolve2 } from "node:path";
|
|
2473
2525
|
import { mkdir as mkdir4 } from "node:fs/promises";
|
|
2474
2526
|
|
|
2475
2527
|
// src/lib/app-dir.ts
|
|
2476
|
-
import { homedir as homedir5, tmpdir as
|
|
2477
|
-
import { join as
|
|
2528
|
+
import { homedir as homedir5, tmpdir as tmpdir4 } from "node:os";
|
|
2529
|
+
import { join as join10 } from "node:path";
|
|
2478
2530
|
function getAppDir() {
|
|
2479
2531
|
const xdg = process.env.XDG_RUNTIME_DIR?.trim();
|
|
2480
2532
|
if (xdg) {
|
|
2481
|
-
return
|
|
2533
|
+
return join10(xdg, "agent-slack");
|
|
2482
2534
|
}
|
|
2483
2535
|
const home = homedir5();
|
|
2484
2536
|
if (home) {
|
|
2485
|
-
return
|
|
2537
|
+
return join10(home, ".agent-slack");
|
|
2486
2538
|
}
|
|
2487
|
-
return
|
|
2539
|
+
return join10(tmpdir4(), "agent-slack");
|
|
2488
2540
|
}
|
|
2489
2541
|
|
|
2490
2542
|
// src/lib/tmp-paths.ts
|
|
2491
2543
|
function getDownloadsDir() {
|
|
2492
|
-
return resolve2(
|
|
2544
|
+
return resolve2(join11(getAppDir(), "tmp", "downloads"));
|
|
2493
2545
|
}
|
|
2494
2546
|
async function ensureDownloadsDir() {
|
|
2495
2547
|
const dir = getDownloadsDir();
|
|
@@ -3120,14 +3172,16 @@ function toCompactMessage(msg, input) {
|
|
|
3120
3172
|
const content = maxBodyChars >= 0 && rendered.length > maxBodyChars ? `${rendered.slice(0, maxBodyChars)}
|
|
3121
3173
|
…` : rendered;
|
|
3122
3174
|
const files = msg.files?.map((f) => {
|
|
3123
|
-
const
|
|
3124
|
-
if (!
|
|
3175
|
+
const entry = input?.downloadedPaths?.[f.id];
|
|
3176
|
+
if (!entry) {
|
|
3125
3177
|
return null;
|
|
3126
3178
|
}
|
|
3127
|
-
return {
|
|
3179
|
+
return entry.ok ? { name: f.name, mimetype: f.mimetype, mode: f.mode, path: entry.path } : {
|
|
3180
|
+
name: f.name,
|
|
3128
3181
|
mimetype: f.mimetype,
|
|
3129
3182
|
mode: f.mode,
|
|
3130
|
-
path
|
|
3183
|
+
path: entry.path,
|
|
3184
|
+
error: entry.error
|
|
3131
3185
|
};
|
|
3132
3186
|
}).filter((f) => Boolean(f));
|
|
3133
3187
|
return {
|
|
@@ -3691,7 +3745,7 @@ async function uploadLocalFileToSlack(input) {
|
|
|
3691
3745
|
|
|
3692
3746
|
// src/cli/message-file-downloads.ts
|
|
3693
3747
|
import { readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
|
|
3694
|
-
import { join as
|
|
3748
|
+
import { join as join12 } from "node:path";
|
|
3695
3749
|
function inferFileExtension(file) {
|
|
3696
3750
|
const mt = (file.mimetype || "").toLowerCase();
|
|
3697
3751
|
const ft = (file.filetype || "").toLowerCase();
|
|
@@ -3734,11 +3788,11 @@ async function downloadCanvasAsMarkdown(input) {
|
|
|
3734
3788
|
});
|
|
3735
3789
|
const html = await readFile6(htmlPath, "utf8");
|
|
3736
3790
|
if (looksLikeAuthPage(html)) {
|
|
3737
|
-
throw new
|
|
3791
|
+
throw new SlackDownloadError("Downloaded auth/login page instead of canvas content (token may be expired)");
|
|
3738
3792
|
}
|
|
3739
3793
|
const markdown = htmlToMarkdown(html).trim();
|
|
3740
3794
|
const safeName = `${input.fileId.replace(/[\\/<>"|?*]/g, "_")}.md`;
|
|
3741
|
-
const markdownPath =
|
|
3795
|
+
const markdownPath = join12(input.destDir, safeName);
|
|
3742
3796
|
await writeFile3(markdownPath, markdown, "utf8");
|
|
3743
3797
|
return markdownPath;
|
|
3744
3798
|
}
|
|
@@ -3755,25 +3809,53 @@ async function downloadMessageFiles(input) {
|
|
|
3755
3809
|
if (!url) {
|
|
3756
3810
|
continue;
|
|
3757
3811
|
}
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3812
|
+
if (isCanvas) {
|
|
3813
|
+
try {
|
|
3814
|
+
const path = await downloadCanvasAsMarkdown({
|
|
3761
3815
|
auth: input.auth,
|
|
3762
3816
|
fileId: file.id,
|
|
3763
3817
|
url,
|
|
3764
3818
|
destDir: downloadsDir
|
|
3765
3819
|
});
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3820
|
+
downloadedPaths[file.id] = { ok: true, path };
|
|
3821
|
+
} catch (err) {
|
|
3822
|
+
if (!(err instanceof SlackDownloadError)) {
|
|
3823
|
+
throw err;
|
|
3824
|
+
}
|
|
3825
|
+
const path = await writeDownloadErrorFile({
|
|
3771
3826
|
destDir: downloadsDir,
|
|
3772
|
-
|
|
3827
|
+
fileId: file.id,
|
|
3828
|
+
error: err.message
|
|
3773
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;
|
|
3774
3858
|
}
|
|
3775
|
-
} catch (err) {
|
|
3776
|
-
console.error(`Warning: skipping file ${file.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3777
3859
|
}
|
|
3778
3860
|
}
|
|
3779
3861
|
}
|
|
@@ -6605,18 +6687,22 @@ async function searchFilesViaSearchApi(client, input) {
|
|
|
6605
6687
|
if (!id) {
|
|
6606
6688
|
continue;
|
|
6607
6689
|
}
|
|
6608
|
-
const
|
|
6690
|
+
const result = await tryDownloadSlackFile({
|
|
6609
6691
|
auth: input.auth,
|
|
6610
6692
|
url,
|
|
6611
6693
|
destDir: downloadsDir,
|
|
6612
6694
|
preferredName: `${id}${ext ? `.${ext}` : ""}`
|
|
6613
6695
|
});
|
|
6696
|
+
if (!result.ok) {
|
|
6697
|
+
console.warn(`Warning: skipping file ${id}: ${result.error}`);
|
|
6698
|
+
continue;
|
|
6699
|
+
}
|
|
6614
6700
|
const title = (getString(f.title) || getString(f.name) || "").trim();
|
|
6615
6701
|
out.push({
|
|
6616
6702
|
title: title || undefined,
|
|
6617
6703
|
mimetype,
|
|
6618
6704
|
mode,
|
|
6619
|
-
path
|
|
6705
|
+
path: result.path
|
|
6620
6706
|
});
|
|
6621
6707
|
if (out.length >= input.limit) {
|
|
6622
6708
|
break;
|
|
@@ -6671,17 +6757,21 @@ async function searchFilesInChannelsFallback(client, input) {
|
|
|
6671
6757
|
if (!id) {
|
|
6672
6758
|
continue;
|
|
6673
6759
|
}
|
|
6674
|
-
const
|
|
6760
|
+
const result = await tryDownloadSlackFile({
|
|
6675
6761
|
auth: input.auth,
|
|
6676
6762
|
url,
|
|
6677
6763
|
destDir: downloadsDir,
|
|
6678
6764
|
preferredName: `${id}${ext ? `.${ext}` : ""}`
|
|
6679
6765
|
});
|
|
6766
|
+
if (!result.ok) {
|
|
6767
|
+
console.warn(`Warning: skipping file ${id}: ${result.error}`);
|
|
6768
|
+
continue;
|
|
6769
|
+
}
|
|
6680
6770
|
out.push({
|
|
6681
6771
|
title: title || undefined,
|
|
6682
6772
|
mimetype,
|
|
6683
6773
|
mode,
|
|
6684
|
-
path
|
|
6774
|
+
path: result.path
|
|
6685
6775
|
});
|
|
6686
6776
|
if (out.length >= input.limit) {
|
|
6687
6777
|
return out;
|
|
@@ -6897,13 +6987,25 @@ async function downloadFilesForMessage(input) {
|
|
|
6897
6987
|
continue;
|
|
6898
6988
|
}
|
|
6899
6989
|
const ext = inferExt(f);
|
|
6900
|
-
const
|
|
6990
|
+
const result = await tryDownloadSlackFile({
|
|
6901
6991
|
auth: input.auth,
|
|
6902
6992
|
url,
|
|
6903
6993
|
destDir: input.downloadsDir,
|
|
6904
6994
|
preferredName: `${f.id}${ext ? `.${ext}` : ""}`
|
|
6905
6995
|
});
|
|
6906
|
-
|
|
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
|
+
}
|
|
6907
7009
|
}
|
|
6908
7010
|
}
|
|
6909
7011
|
function messageSummaryFromApiMessage(channelId, msg) {
|
|
@@ -7073,12 +7175,12 @@ function registerSearchCommand(input) {
|
|
|
7073
7175
|
import { execSync as execSync2 } from "node:child_process";
|
|
7074
7176
|
import { createHash } from "node:crypto";
|
|
7075
7177
|
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
|
|
7077
|
-
import { basename as basename3, join as
|
|
7178
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
7179
|
+
import { basename as basename3, join as join13 } from "node:path";
|
|
7078
7180
|
var REPO = "stablyai/agent-slack";
|
|
7079
7181
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
7080
7182
|
function getCachePath() {
|
|
7081
|
-
return
|
|
7183
|
+
return join13(getAppDir(), "update-check.json");
|
|
7082
7184
|
}
|
|
7083
7185
|
function compareSemver(a, b) {
|
|
7084
7186
|
const pa = a.replace(/^v/, "").split(".").map(Number);
|
|
@@ -7180,10 +7282,10 @@ async function performUpdate(latest) {
|
|
|
7180
7282
|
const asset = detectPlatformAsset();
|
|
7181
7283
|
const tag = `v${latest}`;
|
|
7182
7284
|
const baseUrl = `https://github.com/${REPO}/releases/download/${tag}`;
|
|
7183
|
-
const tmp =
|
|
7285
|
+
const tmp = join13(tmpdir5(), `agent-slack-update-${Date.now()}`);
|
|
7184
7286
|
await mkdir5(tmp, { recursive: true });
|
|
7185
|
-
const binTmp =
|
|
7186
|
-
const sumsTmp =
|
|
7287
|
+
const binTmp = join13(tmp, asset);
|
|
7288
|
+
const sumsTmp = join13(tmp, "checksums-sha256.txt");
|
|
7187
7289
|
try {
|
|
7188
7290
|
const [binResp, sumsResp] = await Promise.all([
|
|
7189
7291
|
fetch(`${baseUrl}/${asset}`, { signal: AbortSignal.timeout(120000) }),
|
|
@@ -7774,6 +7876,49 @@ function registerChannelCommand(input) {
|
|
|
7774
7876
|
process.exitCode = 1;
|
|
7775
7877
|
}
|
|
7776
7878
|
});
|
|
7879
|
+
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) => {
|
|
7880
|
+
const [targetArg, options] = args;
|
|
7881
|
+
try {
|
|
7882
|
+
const target = parseMsgTarget(targetArg);
|
|
7883
|
+
let channelId;
|
|
7884
|
+
let ts;
|
|
7885
|
+
let workspaceUrl;
|
|
7886
|
+
if (target.kind === "url") {
|
|
7887
|
+
if (options.workspace) {
|
|
7888
|
+
throw new Error("--workspace cannot be used with a URL target; the workspace is derived from the URL");
|
|
7889
|
+
}
|
|
7890
|
+
channelId = target.ref.channel_id;
|
|
7891
|
+
ts = options.ts ?? target.ref.message_ts;
|
|
7892
|
+
workspaceUrl = input.ctx.effectiveWorkspaceUrl(target.ref.workspace_url);
|
|
7893
|
+
} else if (target.kind === "channel") {
|
|
7894
|
+
if (!options.ts) {
|
|
7895
|
+
throw new Error("--ts is required when target is a channel name or ID");
|
|
7896
|
+
}
|
|
7897
|
+
({ ts } = options);
|
|
7898
|
+
workspaceUrl = input.ctx.effectiveWorkspaceUrl(options.workspace);
|
|
7899
|
+
channelId = target.channel;
|
|
7900
|
+
} else {
|
|
7901
|
+
throw new Error("User targets are not supported for channel mark");
|
|
7902
|
+
}
|
|
7903
|
+
await input.ctx.assertWorkspaceSpecifiedForChannelNames({
|
|
7904
|
+
workspaceUrl,
|
|
7905
|
+
channels: [channelId]
|
|
7906
|
+
});
|
|
7907
|
+
const resolvedId = await input.ctx.withAutoRefresh({
|
|
7908
|
+
workspaceUrl,
|
|
7909
|
+
work: async () => {
|
|
7910
|
+
const { client } = await input.ctx.getClientForWorkspace(workspaceUrl);
|
|
7911
|
+
const resolved = await resolveChannelId(client, channelId);
|
|
7912
|
+
await markConversation(client, { channelId: resolved, ts });
|
|
7913
|
+
return resolved;
|
|
7914
|
+
}
|
|
7915
|
+
});
|
|
7916
|
+
console.log(JSON.stringify({ ok: true, channel: resolvedId, ts }, null, 2));
|
|
7917
|
+
} catch (err) {
|
|
7918
|
+
console.error(input.ctx.errorMessage(err));
|
|
7919
|
+
process.exitCode = 1;
|
|
7920
|
+
}
|
|
7921
|
+
});
|
|
7777
7922
|
}
|
|
7778
7923
|
|
|
7779
7924
|
// src/index.ts
|
|
@@ -7796,5 +7941,5 @@ if (subcommand && subcommand !== "update") {
|
|
|
7796
7941
|
backgroundUpdateCheck();
|
|
7797
7942
|
}
|
|
7798
7943
|
|
|
7799
|
-
//# debugId=
|
|
7944
|
+
//# debugId=3EBC31FAC3ADA2FD64756E2164756E21
|
|
7800
7945
|
//# sourceMappingURL=index.js.map
|