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 +245 -193
- package/dist/index.js.map +11 -10
- 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,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
|
|
2410
|
-
import { existsSync as
|
|
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 =
|
|
2417
|
-
if (
|
|
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
|
|
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
|
|
2477
|
-
import { join as
|
|
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
|
|
2490
|
+
return join10(xdg, "agent-slack");
|
|
2482
2491
|
}
|
|
2483
2492
|
const home = homedir5();
|
|
2484
2493
|
if (home) {
|
|
2485
|
-
return
|
|
2494
|
+
return join10(home, ".agent-slack");
|
|
2486
2495
|
}
|
|
2487
|
-
return
|
|
2496
|
+
return join10(tmpdir4(), "agent-slack");
|
|
2488
2497
|
}
|
|
2489
2498
|
|
|
2490
2499
|
// src/lib/tmp-paths.ts
|
|
2491
2500
|
function getDownloadsDir() {
|
|
2492
|
-
return resolve2(
|
|
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
|
|
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 =
|
|
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
|
|
7077
|
-
import { basename as basename3, join as
|
|
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
|
|
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 =
|
|
7192
|
+
const tmp = join13(tmpdir5(), `agent-slack-update-${Date.now()}`);
|
|
7184
7193
|
await mkdir5(tmp, { recursive: true });
|
|
7185
|
-
const binTmp =
|
|
7186
|
-
const sumsTmp =
|
|
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=
|
|
7851
|
+
//# debugId=5ADF75C59137BC5064756E2164756E21
|
|
7800
7852
|
//# sourceMappingURL=index.js.map
|