openmates 0.27.0-alpha.62 → 0.27.0-alpha.63
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/{chunk-CFP7IA76.js → chunk-D6TL3ILK.js} +643 -34
- package/dist/{chunk-5SFXCJJ4.js → chunk-Q6PGA2B3.js} +41 -0
- package/dist/{cli-CPgxlEQg.d.ts → cli-bEgF9_RV.d.ts} +10 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +2 -2
- package/dist/privacyDaemon.js +1 -1
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-base.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml-cpu.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so.0 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/libggml.so.0.15.1 +0 -0
- package/fixtures/privacy-runtime/linux-arm64/manifest.json +9 -9
- package/package.json +3 -2
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getCredentialStorageMode,
|
|
9
9
|
installPrivacyModel,
|
|
10
10
|
loadAnonymousId,
|
|
11
|
+
loadCachedDirectChatAccountKey,
|
|
11
12
|
loadLocalTeamKey,
|
|
12
13
|
loadSession,
|
|
13
14
|
loadStoredSessionApiUrl,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
readPrivacyPreferences,
|
|
26
27
|
resolveStateDir,
|
|
27
28
|
saveAnonymousId,
|
|
29
|
+
saveCachedDirectChatAccountKey,
|
|
28
30
|
saveLocalTeamKey,
|
|
29
31
|
saveSession,
|
|
30
32
|
saveSyncCache,
|
|
@@ -34,7 +36,7 @@ import {
|
|
|
34
36
|
verifyInstalledModel,
|
|
35
37
|
verifyPrivacyRuntime,
|
|
36
38
|
withSessionRefreshLock
|
|
37
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-Q6PGA2B3.js";
|
|
38
40
|
import {
|
|
39
41
|
RemoteAccessReplayGuard,
|
|
40
42
|
createRemoteAccessHandshake,
|
|
@@ -1634,6 +1636,183 @@ async function prepareCliMessagePrivacy(text7, memories, existing = [], scope2 =
|
|
|
1634
1636
|
|
|
1635
1637
|
// src/client.ts
|
|
1636
1638
|
import { createHash as createHash13, randomBytes as randomBytes4, randomUUID as randomUUID9, timingSafeEqual } from "crypto";
|
|
1639
|
+
|
|
1640
|
+
// ../pairing-crypto/src/directChatCrypto.ts
|
|
1641
|
+
import nacl from "tweetnacl";
|
|
1642
|
+
var PREFIX = "OMDM1.";
|
|
1643
|
+
var MAX_ENVELOPE_LENGTH = 2048;
|
|
1644
|
+
var MAX_PAYLOAD_BYTES = 768;
|
|
1645
|
+
var BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
1646
|
+
var KEY_ID = /^[0-9a-f]{64}$/;
|
|
1647
|
+
var encoder = new TextEncoder();
|
|
1648
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
1649
|
+
function ownedBuffer(bytes2) {
|
|
1650
|
+
const copy = new Uint8Array(bytes2.length);
|
|
1651
|
+
copy.set(bytes2);
|
|
1652
|
+
return copy.buffer;
|
|
1653
|
+
}
|
|
1654
|
+
function directChatBase64Url(bytes2) {
|
|
1655
|
+
let binary = "";
|
|
1656
|
+
for (let offset = 0; offset < bytes2.length; offset += 8192) {
|
|
1657
|
+
binary += String.fromCharCode(...bytes2.subarray(offset, offset + 8192));
|
|
1658
|
+
}
|
|
1659
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
1660
|
+
}
|
|
1661
|
+
function directChatBase64UrlBytes(value, length, field3 = "key", maxLength = length ?? MAX_PAYLOAD_BYTES) {
|
|
1662
|
+
if (typeof value !== "string" || value.length < 1 || value.length > Math.ceil(maxLength * 4 / 3) + 2 || !BASE64URL.test(value)) {
|
|
1663
|
+
throw new Error(`Invalid direct-chat ${field3}`);
|
|
1664
|
+
}
|
|
1665
|
+
let decoded;
|
|
1666
|
+
try {
|
|
1667
|
+
const binary = atob(value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4));
|
|
1668
|
+
decoded = Uint8Array.from(binary, (character) => character.charCodeAt(0));
|
|
1669
|
+
} catch {
|
|
1670
|
+
throw new Error(`Invalid direct-chat ${field3}`);
|
|
1671
|
+
}
|
|
1672
|
+
if (length !== void 0 && decoded.length !== length || decoded.length > maxLength || directChatBase64Url(decoded) !== value) throw new Error(`Invalid direct-chat ${field3}`);
|
|
1673
|
+
return decoded;
|
|
1674
|
+
}
|
|
1675
|
+
function boundedIdentity(identity3) {
|
|
1676
|
+
if (!identity3 || typeof identity3 !== "object") throw new Error("Invalid direct-chat identity");
|
|
1677
|
+
for (const value of [identity3.teamId, identity3.chatId, identity3.recipientHash]) {
|
|
1678
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 128 || !/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
1679
|
+
throw new Error("Invalid direct-chat identity");
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
if (!KEY_ID.test(identity3.keyId)) throw new Error("Invalid direct-chat key ID");
|
|
1683
|
+
}
|
|
1684
|
+
function validNonzeroKey(value, field3) {
|
|
1685
|
+
if (value.length !== 32 || value.every((byte) => byte === 0)) throw new Error(`Invalid direct-chat ${field3}`);
|
|
1686
|
+
}
|
|
1687
|
+
function validatePublicKey(value) {
|
|
1688
|
+
const bytes2 = directChatBase64UrlBytes(value, 32, "public key");
|
|
1689
|
+
validNonzeroKey(bytes2, "public key");
|
|
1690
|
+
const probe = nacl.scalarMult(new Uint8Array(32).fill(7), bytes2);
|
|
1691
|
+
if (probe.every((byte) => byte === 0)) throw new Error("Invalid direct-chat public key");
|
|
1692
|
+
return bytes2;
|
|
1693
|
+
}
|
|
1694
|
+
async function directChatKeyId(publicKey) {
|
|
1695
|
+
const bytes2 = validatePublicKey(publicKey);
|
|
1696
|
+
const digest4 = new Uint8Array(await crypto.subtle.digest("SHA-256", ownedBuffer(bytes2)));
|
|
1697
|
+
return [...digest4].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1698
|
+
}
|
|
1699
|
+
async function generateIdentity() {
|
|
1700
|
+
const pair = nacl.box.keyPair();
|
|
1701
|
+
const publicKey = directChatBase64Url(pair.publicKey);
|
|
1702
|
+
return { publicKey, privateKey: directChatBase64Url(pair.secretKey), keyId: await directChatKeyId(publicKey) };
|
|
1703
|
+
}
|
|
1704
|
+
async function sealDirectChatKey(rawKey, identity3, recipientPublicKey) {
|
|
1705
|
+
boundedIdentity(identity3);
|
|
1706
|
+
validNonzeroKey(rawKey, "chat key");
|
|
1707
|
+
const recipient = validatePublicKey(recipientPublicKey);
|
|
1708
|
+
if (identity3.keyId !== await directChatKeyId(recipientPublicKey)) throw new Error("Direct-chat recipient key ID mismatch");
|
|
1709
|
+
const ephemeral = nacl.box.keyPair();
|
|
1710
|
+
const nonce = nacl.randomBytes(24);
|
|
1711
|
+
const payload = encoder.encode(JSON.stringify({
|
|
1712
|
+
v: 1,
|
|
1713
|
+
team_id: identity3.teamId,
|
|
1714
|
+
chat_id: identity3.chatId,
|
|
1715
|
+
recipient_hash: identity3.recipientHash,
|
|
1716
|
+
key_id: identity3.keyId,
|
|
1717
|
+
key: directChatBase64Url(rawKey)
|
|
1718
|
+
}));
|
|
1719
|
+
const ciphertext = nacl.box(payload, nonce, recipient, ephemeral.secretKey);
|
|
1720
|
+
const envelope = PREFIX + directChatBase64Url(encoder.encode(JSON.stringify({
|
|
1721
|
+
v: 1,
|
|
1722
|
+
ephemeral_public_key: directChatBase64Url(ephemeral.publicKey),
|
|
1723
|
+
nonce: directChatBase64Url(nonce),
|
|
1724
|
+
ciphertext: directChatBase64Url(ciphertext)
|
|
1725
|
+
})));
|
|
1726
|
+
ephemeral.secretKey.fill(0);
|
|
1727
|
+
if (envelope.length > MAX_ENVELOPE_LENGTH) throw new Error("Direct-chat envelope too large");
|
|
1728
|
+
return envelope;
|
|
1729
|
+
}
|
|
1730
|
+
async function openDirectChatKey(envelope, expected, privateKey) {
|
|
1731
|
+
boundedIdentity(expected);
|
|
1732
|
+
if (typeof envelope !== "string" || !envelope.startsWith(PREFIX) || envelope.length > MAX_ENVELOPE_LENGTH) {
|
|
1733
|
+
throw new Error("Invalid direct-chat envelope");
|
|
1734
|
+
}
|
|
1735
|
+
const encoded = envelope.slice(PREFIX.length);
|
|
1736
|
+
const decoded = directChatBase64UrlBytes(encoded, void 0, "envelope");
|
|
1737
|
+
if (decoded.length > MAX_PAYLOAD_BYTES) throw new Error("Direct-chat envelope too large");
|
|
1738
|
+
let parsed;
|
|
1739
|
+
try {
|
|
1740
|
+
parsed = JSON.parse(decoder.decode(decoded));
|
|
1741
|
+
} catch {
|
|
1742
|
+
throw new Error("Invalid direct-chat envelope");
|
|
1743
|
+
}
|
|
1744
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Invalid direct-chat envelope");
|
|
1745
|
+
const outer = parsed;
|
|
1746
|
+
if (Object.keys(outer).sort().join(",") !== "ciphertext,ephemeral_public_key,nonce,v" || outer.v !== 1) {
|
|
1747
|
+
throw new Error("Invalid direct-chat envelope version or fields");
|
|
1748
|
+
}
|
|
1749
|
+
const sender = validatePublicKey(String(outer.ephemeral_public_key));
|
|
1750
|
+
const nonce = directChatBase64UrlBytes(String(outer.nonce), 24, "nonce");
|
|
1751
|
+
const ciphertext = directChatBase64UrlBytes(String(outer.ciphertext), void 0, "ciphertext", 512);
|
|
1752
|
+
if (ciphertext.length < 17) throw new Error("Invalid direct-chat ciphertext");
|
|
1753
|
+
const secret = directChatBase64UrlBytes(privateKey, 32, "private key");
|
|
1754
|
+
validNonzeroKey(secret, "private key");
|
|
1755
|
+
const ownPublic = directChatBase64Url(nacl.scalarMult.base(secret));
|
|
1756
|
+
if (await directChatKeyId(ownPublic) !== expected.keyId) throw new Error("Direct-chat private key ID mismatch");
|
|
1757
|
+
const plaintext = nacl.box.open(ciphertext, nonce, sender, secret);
|
|
1758
|
+
if (!plaintext) throw new Error("Direct-chat key authentication failed");
|
|
1759
|
+
let inner;
|
|
1760
|
+
try {
|
|
1761
|
+
inner = JSON.parse(decoder.decode(plaintext));
|
|
1762
|
+
} catch {
|
|
1763
|
+
throw new Error("Invalid direct-chat key payload");
|
|
1764
|
+
}
|
|
1765
|
+
if (!inner || typeof inner !== "object" || Array.isArray(inner)) throw new Error("Invalid direct-chat key payload");
|
|
1766
|
+
const payload = inner;
|
|
1767
|
+
if (Object.keys(payload).sort().join(",") !== "chat_id,key,key_id,recipient_hash,team_id,v" || payload.v !== 1 || payload.team_id !== expected.teamId || payload.chat_id !== expected.chatId || payload.recipient_hash !== expected.recipientHash || payload.key_id !== expected.keyId) {
|
|
1768
|
+
throw new Error("Direct-chat key binding mismatch");
|
|
1769
|
+
}
|
|
1770
|
+
const key = directChatBase64UrlBytes(String(payload.key), 32, "chat key");
|
|
1771
|
+
validNonzeroKey(key, "chat key");
|
|
1772
|
+
return key;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// src/directChats.ts
|
|
1776
|
+
function validatePublicChatEncryptionKey(value) {
|
|
1777
|
+
const row3 = value;
|
|
1778
|
+
if (!row3 || typeof row3.public_key !== "string" || typeof row3.key_id !== "string" || !/^[0-9a-f]{64}$/.test(row3.key_id)) {
|
|
1779
|
+
throw new Error("Invalid member chat encryption key");
|
|
1780
|
+
}
|
|
1781
|
+
directChatBase64UrlBytes(row3.public_key, 32, "public key");
|
|
1782
|
+
return { key_id: row3.key_id, public_key: row3.public_key };
|
|
1783
|
+
}
|
|
1784
|
+
async function validatePublicChatEncryptionKeyId(value) {
|
|
1785
|
+
const key = validatePublicChatEncryptionKey(value);
|
|
1786
|
+
if (await directChatKeyId(key.public_key) !== key.key_id) throw new Error("Member chat encryption key ID mismatch");
|
|
1787
|
+
return key;
|
|
1788
|
+
}
|
|
1789
|
+
async function recoverAccountChatIdentity(record10, masterKey) {
|
|
1790
|
+
if (record10.version !== 1 || record10.algorithm !== "x25519-xsalsa20poly1305") throw new Error("Unsupported account chat encryption key");
|
|
1791
|
+
const publicKey = await validatePublicChatEncryptionKeyId(record10);
|
|
1792
|
+
if (typeof record10.encrypted_private_key !== "string" || record10.encrypted_private_key.length > 2048) throw new Error("Invalid account chat private key backup");
|
|
1793
|
+
const encoded = record10.encrypted_private_key;
|
|
1794
|
+
if (!/^[A-Za-z0-9+/_-]+={0,2}$/.test(encoded)) throw new Error("Invalid account chat private key backup");
|
|
1795
|
+
const privateKey = await decryptWithAesGcmCombined(Buffer.from(encoded, "base64url").toString("base64"), masterKey);
|
|
1796
|
+
if (!privateKey) throw new Error("Could not decrypt account chat private key");
|
|
1797
|
+
const secret = directChatBase64UrlBytes(privateKey, 32, "private key");
|
|
1798
|
+
if (secret.every((byte) => byte === 0)) throw new Error("Invalid account chat private key");
|
|
1799
|
+
const nacl2 = (await import("tweetnacl")).default;
|
|
1800
|
+
if (directChatBase64Url(nacl2.scalarMult.base(secret)) !== publicKey.public_key) throw new Error("Account chat key pair mismatch");
|
|
1801
|
+
return { publicKey: publicKey.public_key, privateKey, keyId: publicKey.key_id };
|
|
1802
|
+
}
|
|
1803
|
+
async function createAccountChatIdentity(masterKey) {
|
|
1804
|
+
const identity3 = await generateIdentity();
|
|
1805
|
+
const backup = await encryptWithAesGcmCombined(identity3.privateKey, masterKey);
|
|
1806
|
+
return { identity: identity3, record: {
|
|
1807
|
+
version: 1,
|
|
1808
|
+
algorithm: "x25519-xsalsa20poly1305",
|
|
1809
|
+
key_id: identity3.keyId,
|
|
1810
|
+
public_key: identity3.publicKey,
|
|
1811
|
+
encrypted_private_key: Buffer.from(backup, "base64").toString("base64url")
|
|
1812
|
+
} };
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// src/client.ts
|
|
1637
1816
|
import { arch, platform, release } from "os";
|
|
1638
1817
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
1639
1818
|
import { stdin as stdin2, stdout } from "process";
|
|
@@ -2475,13 +2654,13 @@ var OpenMatesHttpClient = class {
|
|
|
2475
2654
|
throw new Error("SSE response body is not readable");
|
|
2476
2655
|
}
|
|
2477
2656
|
const reader = response.body.getReader();
|
|
2478
|
-
const
|
|
2657
|
+
const decoder3 = new TextDecoder();
|
|
2479
2658
|
let buffer = "";
|
|
2480
2659
|
try {
|
|
2481
2660
|
while (true) {
|
|
2482
2661
|
const { done, value } = await reader.read();
|
|
2483
2662
|
if (done) break;
|
|
2484
|
-
buffer +=
|
|
2663
|
+
buffer += decoder3.decode(value, { stream: true });
|
|
2485
2664
|
let separatorIndex = buffer.indexOf("\n\n");
|
|
2486
2665
|
while (separatorIndex >= 0) {
|
|
2487
2666
|
const rawEvent = buffer.slice(0, separatorIndex);
|
|
@@ -2701,12 +2880,12 @@ import { randomUUID as randomUUID3 } from "crypto";
|
|
|
2701
2880
|
|
|
2702
2881
|
// ../ui/src/utils/projectIgnoredReadGrant.ts
|
|
2703
2882
|
var GRANT_LIFETIME_MS = 5 * 60 * 1e3;
|
|
2704
|
-
var
|
|
2883
|
+
var encoder2 = new TextEncoder();
|
|
2705
2884
|
function validScope(scope2) {
|
|
2706
2885
|
return [scope2.projectId, scope2.sourceId, scope2.requestId, scope2.chatId, scope2.operationId].every((value) => typeof value === "string" && value.length > 0 && value.length <= 128) && typeof scope2.path === "string" && scope2.path.length > 0 && scope2.path.length <= 4096 && !scope2.path.includes("\\") && !scope2.path.startsWith("/") && !/^[a-z]:/i.test(scope2.path) && !Array.from(scope2.path).some((value) => value.charCodeAt(0) < 32 || value.charCodeAt(0) === 127) && scope2.path.split("/").every((part) => part && part !== "." && part !== "..");
|
|
2707
2886
|
}
|
|
2708
2887
|
function material(grant) {
|
|
2709
|
-
return
|
|
2888
|
+
return encoder2.encode(JSON.stringify([
|
|
2710
2889
|
"openmates-ignored-read-v1",
|
|
2711
2890
|
grant.projectId,
|
|
2712
2891
|
grant.sourceId,
|
|
@@ -9984,13 +10163,13 @@ import { client, ready, server } from "@serenity-kit/opaque";
|
|
|
9984
10163
|
var PAIR_PIN_ALPHABET = "ABCDEFGHJKLMNPQRTUVWXY3468";
|
|
9985
10164
|
var PIN_LENGTH = 6;
|
|
9986
10165
|
var KSF = { "argon2id-custom": { memory: 8192, iterations: 3, parallelism: 1 } };
|
|
9987
|
-
var
|
|
9988
|
-
var
|
|
9989
|
-
var BUNDLE_INFO =
|
|
9990
|
-
var
|
|
10166
|
+
var encoder3 = new TextEncoder();
|
|
10167
|
+
var decoder2 = new TextDecoder("utf-8", { fatal: true });
|
|
10168
|
+
var BUNDLE_INFO = encoder3.encode("openmates-pair-v2/bundle");
|
|
10169
|
+
var BASE64URL2 = /^[A-Za-z0-9_-]+$/;
|
|
9991
10170
|
var HEX_256 = /^[0-9a-f]{64}$/;
|
|
9992
10171
|
var BUNDLE_KEYS = /* @__PURE__ */ new Set(["protocol_version", "master_key_exported", "grant_secret", "user_email_salt", "hashed_email", "user_id", "account_context"]);
|
|
9993
|
-
function
|
|
10172
|
+
function ownedBuffer2(bytes2) {
|
|
9994
10173
|
return bytes2;
|
|
9995
10174
|
}
|
|
9996
10175
|
function requireAscii(value, name, max = 128) {
|
|
@@ -10030,7 +10209,7 @@ function validateContext(context) {
|
|
|
10030
10209
|
auto_logout_minutes: parsed[6]
|
|
10031
10210
|
};
|
|
10032
10211
|
if (createPairContext(fields) !== context) throw new Error("Noncanonical pairing context");
|
|
10033
|
-
return { text: context, bytes:
|
|
10212
|
+
return { text: context, bytes: encoder3.encode(context), fields };
|
|
10034
10213
|
}
|
|
10035
10214
|
function identifiers(context) {
|
|
10036
10215
|
return { client: `openmates-pair-v2/client/${context}`, server: `openmates-pair-v2/server/${context}` };
|
|
@@ -10046,7 +10225,7 @@ function base64url(bytes2) {
|
|
|
10046
10225
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
10047
10226
|
}
|
|
10048
10227
|
function decodeBase64url(value, maxBytes) {
|
|
10049
|
-
if (typeof value !== "string" || value.length < 1 || value.length > Math.ceil(maxBytes * 4 / 3) + 2 || !
|
|
10228
|
+
if (typeof value !== "string" || value.length < 1 || value.length > Math.ceil(maxBytes * 4 / 3) + 2 || !BASE64URL2.test(value)) {
|
|
10050
10229
|
throw new Error("Invalid pairing encoding");
|
|
10051
10230
|
}
|
|
10052
10231
|
let bytes2;
|
|
@@ -10073,7 +10252,7 @@ async function hashPairSecret(secret) {
|
|
|
10073
10252
|
const bytes2 = decodeBase64url(secret, 32);
|
|
10074
10253
|
if (bytes2.length !== 32) throw new Error("Invalid pairing secret");
|
|
10075
10254
|
try {
|
|
10076
|
-
const digest4 = new Uint8Array(await crypto.subtle.digest("SHA-256",
|
|
10255
|
+
const digest4 = new Uint8Array(await crypto.subtle.digest("SHA-256", ownedBuffer2(bytes2)));
|
|
10077
10256
|
return [...digest4].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
10078
10257
|
} finally {
|
|
10079
10258
|
bytes2.fill(0);
|
|
@@ -10089,10 +10268,10 @@ async function deriveBundleKey(sessionKey, contextBytes) {
|
|
|
10089
10268
|
const raw = decodeBase64url(sessionKey, 64);
|
|
10090
10269
|
if (raw.length !== 64) throw new Error("Invalid pairing session key");
|
|
10091
10270
|
try {
|
|
10092
|
-
const salt = await crypto.subtle.digest("SHA-256",
|
|
10093
|
-
const ikm = await crypto.subtle.importKey("raw",
|
|
10271
|
+
const salt = await crypto.subtle.digest("SHA-256", ownedBuffer2(contextBytes));
|
|
10272
|
+
const ikm = await crypto.subtle.importKey("raw", ownedBuffer2(raw), "HKDF", false, ["deriveKey"]);
|
|
10094
10273
|
return crypto.subtle.deriveKey(
|
|
10095
|
-
{ name: "HKDF", hash: "SHA-256", salt, info:
|
|
10274
|
+
{ name: "HKDF", hash: "SHA-256", salt, info: ownedBuffer2(BUNDLE_INFO) },
|
|
10096
10275
|
ikm,
|
|
10097
10276
|
{ name: "AES-GCM", length: 256 },
|
|
10098
10277
|
false,
|
|
@@ -10168,13 +10347,13 @@ async function createPairReceiver(context, pin) {
|
|
|
10168
10347
|
if (nonce.length !== 12) throw new Error("Invalid pairing IV");
|
|
10169
10348
|
const ciphertext = decodeBase64url(encryptedBundle, 65536);
|
|
10170
10349
|
const plaintext = new Uint8Array(await crypto.subtle.decrypt(
|
|
10171
|
-
{ name: "AES-GCM", iv:
|
|
10350
|
+
{ name: "AES-GCM", iv: ownedBuffer2(nonce), additionalData: ownedBuffer2(binding.bytes), tagLength: 128 },
|
|
10172
10351
|
key,
|
|
10173
|
-
|
|
10352
|
+
ownedBuffer2(ciphertext)
|
|
10174
10353
|
));
|
|
10175
10354
|
try {
|
|
10176
10355
|
assertActive();
|
|
10177
|
-
return validateBundle(JSON.parse(
|
|
10356
|
+
return validateBundle(JSON.parse(decoder2.decode(plaintext)), binding.fields.authorizer_user_id);
|
|
10178
10357
|
} finally {
|
|
10179
10358
|
nonce.fill(0);
|
|
10180
10359
|
ciphertext.fill(0);
|
|
@@ -11366,6 +11545,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11366
11545
|
session;
|
|
11367
11546
|
http;
|
|
11368
11547
|
explicitSession;
|
|
11548
|
+
directChatIdentity;
|
|
11549
|
+
directChatUserHash;
|
|
11369
11550
|
constructor(options = {}) {
|
|
11370
11551
|
this.explicitSession = options.session !== void 0;
|
|
11371
11552
|
const diskSession = options.session ?? (options.loadStoredSession === false ? null : this.getValidSessionFromDisk());
|
|
@@ -11631,6 +11812,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11631
11812
|
if (!response.ok) throw new Error(`Team list failed with HTTP ${response.status}`);
|
|
11632
11813
|
if (!owner2.isCurrent()) throw new Error("Account changed while loading Teams.");
|
|
11633
11814
|
const teams = response.data.teams ?? [];
|
|
11815
|
+
if (teams.length) await this.ensureDirectChatIdentity();
|
|
11816
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while loading Teams.");
|
|
11634
11817
|
await Promise.all(teams.map((team) => this.cacheTeamKeyFromRecord(team, owner2.isCurrent)));
|
|
11635
11818
|
if (!owner2.isCurrent()) throw new Error("Account changed while loading Teams.");
|
|
11636
11819
|
const teamIds = teams.map((team) => team.team_id).filter((teamId) => typeof teamId === "string" && teamId.length > 0);
|
|
@@ -11640,6 +11823,60 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11640
11823
|
}
|
|
11641
11824
|
return teams;
|
|
11642
11825
|
}
|
|
11826
|
+
async openTeamDirectChat(teamId, recipientHash) {
|
|
11827
|
+
if (!teamId || !/^[0-9a-f]{64}$/.test(recipientHash)) throw new Error("A Team ID and member hash are required.");
|
|
11828
|
+
const owner2 = this.captureTeamAccountOwner();
|
|
11829
|
+
const [ownHash, ownIdentity] = await Promise.all([this.ownDirectChatHash(), this.ensureDirectChatIdentity()]);
|
|
11830
|
+
if (recipientHash === ownHash) throw new Error("Choose another Team member to message.");
|
|
11831
|
+
const cache = await this.ensureSynced(false, [], { teamId });
|
|
11832
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while opening the direct chat.");
|
|
11833
|
+
const existing = cache.chats.find((row4) => row4.details.chat_visibility === "direct" && (!row4.details.team_id || row4.details.team_id === teamId) && (!row4.details.hashed_team_id || row4.details.hashed_team_id === computeSHA256(teamId)) && Array.isArray(row4.details.participant_user_hashes) && row4.details.participant_user_hashes.length === 2 && row4.details.participant_user_hashes.includes(ownHash) && row4.details.participant_user_hashes.includes(recipientHash));
|
|
11834
|
+
if (existing) {
|
|
11835
|
+
const wrapping2 = await this.getChatWrappingKey(teamId, this.getMasterKeyBytes());
|
|
11836
|
+
return this.decryptChatListItem(existing, wrapping2, cache, teamId);
|
|
11837
|
+
}
|
|
11838
|
+
const recipient = await this.http.get(
|
|
11839
|
+
`/v1/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(recipientHash)}/chat-encryption-key`,
|
|
11840
|
+
this.getCliRequestHeaders()
|
|
11841
|
+
);
|
|
11842
|
+
if (recipient.status === 404) throw new Error("Direct message pending: this Team member has not enrolled a chat encryption key yet.");
|
|
11843
|
+
if (!recipient.ok) throw new Error(`Team member chat encryption key failed with HTTP ${recipient.status}`);
|
|
11844
|
+
const recipientKey = await validatePublicChatEncryptionKeyId(recipient.data);
|
|
11845
|
+
const chatId = randomUUID9();
|
|
11846
|
+
const chatKey = new Uint8Array(randomBytes4(32));
|
|
11847
|
+
const ownEnvelope = await sealDirectChatKey(chatKey, { teamId, chatId, recipientHash: ownHash, keyId: ownIdentity.keyId }, ownIdentity.publicKey);
|
|
11848
|
+
const recipientEnvelope = await sealDirectChatKey(chatKey, { teamId, chatId, recipientHash, keyId: recipientKey.key_id }, recipientKey.public_key);
|
|
11849
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while opening the direct chat.");
|
|
11850
|
+
const response = await this.http.post(
|
|
11851
|
+
`/v1/teams/${encodeURIComponent(teamId)}/direct-chats`,
|
|
11852
|
+
{
|
|
11853
|
+
chat_id: chatId,
|
|
11854
|
+
recipient_user_hash: recipientHash,
|
|
11855
|
+
participant_key_envelopes: {
|
|
11856
|
+
[ownHash]: { key_id: ownIdentity.keyId, encrypted_chat_key: ownEnvelope },
|
|
11857
|
+
[recipientHash]: { key_id: recipientKey.key_id, encrypted_chat_key: recipientEnvelope }
|
|
11858
|
+
},
|
|
11859
|
+
encrypted_title: await encryptWithAesGcmCombined("Private Team message", chatKey)
|
|
11860
|
+
},
|
|
11861
|
+
this.getCliRequestHeaders()
|
|
11862
|
+
);
|
|
11863
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while opening the direct chat.");
|
|
11864
|
+
if (!response.ok || !response.data.chat) throw new Error(`Direct chat creation failed with HTTP ${response.status}`);
|
|
11865
|
+
const details = response.data.chat;
|
|
11866
|
+
if (details.chat_visibility !== "direct" || details.team_id !== teamId || details.id !== chatId) {
|
|
11867
|
+
throw new Error("Direct chat creation returned mismatched metadata.");
|
|
11868
|
+
}
|
|
11869
|
+
const latest = loadSyncCache(teamId) ?? cache;
|
|
11870
|
+
const row3 = { details, messages: [] };
|
|
11871
|
+
saveSyncCache({
|
|
11872
|
+
...latest,
|
|
11873
|
+
chats: [row3, ...latest.chats],
|
|
11874
|
+
loadedChatCount: latest.loadedChatCount + 1,
|
|
11875
|
+
totalChatCount: latest.totalChatCount + 1
|
|
11876
|
+
}, teamId);
|
|
11877
|
+
const wrapping = await this.getChatWrappingKey(teamId, this.getMasterKeyBytes());
|
|
11878
|
+
return this.decryptChatListItem(row3, wrapping, loadSyncCache(teamId), teamId);
|
|
11879
|
+
}
|
|
11643
11880
|
async approveTeamName(name) {
|
|
11644
11881
|
this.requireSession();
|
|
11645
11882
|
const normalized = name.trim().toLowerCase();
|
|
@@ -11690,6 +11927,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11690
11927
|
return response.data.team;
|
|
11691
11928
|
}
|
|
11692
11929
|
async getTeam(teamId) {
|
|
11930
|
+
await this.ensureDirectChatIdentity();
|
|
11693
11931
|
const owner2 = this.captureTeamAccountOwner();
|
|
11694
11932
|
const response = await this.http.get(`/v1/teams/${encodeURIComponent(teamId)}`, this.getCliRequestHeaders());
|
|
11695
11933
|
if (!response.ok || !response.data.team) throw new Error(`Team get failed with HTTP ${response.status}`);
|
|
@@ -11739,6 +11977,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
11739
11977
|
}
|
|
11740
11978
|
async listTeamMembers(teamId) {
|
|
11741
11979
|
this.requireSession();
|
|
11980
|
+
await this.ensureDirectChatIdentity();
|
|
11742
11981
|
const response = await this.http.get(`/v1/teams/${encodeURIComponent(teamId)}/members`, this.getCliRequestHeaders());
|
|
11743
11982
|
if (!response.ok) throw new Error(`Team members failed with HTTP ${response.status}`);
|
|
11744
11983
|
return Promise.all((response.data.members ?? []).map((member) => this.decryptTeamMember(teamId, member)));
|
|
@@ -12865,6 +13104,111 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
12865
13104
|
return response.data.user ?? {};
|
|
12866
13105
|
});
|
|
12867
13106
|
}
|
|
13107
|
+
async ownDirectChatHash() {
|
|
13108
|
+
const session = this.requireSession();
|
|
13109
|
+
const owner2 = this.captureTeamAccountOwner();
|
|
13110
|
+
const scope2 = `${session.apiUrl}:${session.sessionId}:${session.hashedEmail}:${session.masterKeyExportedB64}`;
|
|
13111
|
+
if (this.directChatUserHash?.scope !== scope2) {
|
|
13112
|
+
const cached = loadCachedDirectChatAccountKey(session, this.apiUrl);
|
|
13113
|
+
const promise = (cached ? Promise.resolve({ id: cached.accountId }) : this.whoAmI()).then((user2) => {
|
|
13114
|
+
const id = typeof user2.id === "string" ? user2.id : typeof user2.user_id === "string" ? user2.user_id : null;
|
|
13115
|
+
if (!id) throw new Error("Authenticated identity is required for direct chats.");
|
|
13116
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while loading direct chat identity.");
|
|
13117
|
+
return createHash13("sha256").update(id).digest("hex");
|
|
13118
|
+
}).catch((error) => {
|
|
13119
|
+
if (!(error instanceof TypeError)) throw error;
|
|
13120
|
+
const cached2 = loadCachedDirectChatAccountKey(session, this.apiUrl);
|
|
13121
|
+
if (!cached2) throw error;
|
|
13122
|
+
if (!owner2.isCurrent()) throw new Error("Account changed while loading direct chat identity.");
|
|
13123
|
+
return createHash13("sha256").update(cached2.accountId).digest("hex");
|
|
13124
|
+
}).catch((error) => {
|
|
13125
|
+
if (this.directChatUserHash?.promise === promise) this.directChatUserHash = void 0;
|
|
13126
|
+
throw error;
|
|
13127
|
+
});
|
|
13128
|
+
this.directChatUserHash = { scope: scope2, promise };
|
|
13129
|
+
}
|
|
13130
|
+
return this.directChatUserHash.promise;
|
|
13131
|
+
}
|
|
13132
|
+
async ensureDirectChatIdentity() {
|
|
13133
|
+
const session = this.requireSession();
|
|
13134
|
+
const scope2 = `${session.apiUrl}:${session.sessionId}:${session.hashedEmail}:${session.masterKeyExportedB64}`;
|
|
13135
|
+
if (this.directChatIdentity?.scope !== scope2) {
|
|
13136
|
+
const promise = this.loadDirectChatIdentity().catch((error) => {
|
|
13137
|
+
if (this.directChatIdentity?.promise === promise) this.directChatIdentity = void 0;
|
|
13138
|
+
throw error;
|
|
13139
|
+
});
|
|
13140
|
+
this.directChatIdentity = { scope: scope2, promise };
|
|
13141
|
+
}
|
|
13142
|
+
return this.directChatIdentity.promise;
|
|
13143
|
+
}
|
|
13144
|
+
async loadDirectChatIdentity() {
|
|
13145
|
+
const owner2 = this.captureTeamAccountOwner();
|
|
13146
|
+
const masterKey = this.getMasterKeyBytes();
|
|
13147
|
+
const recover = async (record11) => {
|
|
13148
|
+
const identity4 = await recoverAccountChatIdentity(record11, masterKey);
|
|
13149
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key recovery.");
|
|
13150
|
+
return identity4;
|
|
13151
|
+
};
|
|
13152
|
+
const local = loadCachedDirectChatAccountKey(this.requireSession(), this.apiUrl);
|
|
13153
|
+
if (local) return recover(local.record);
|
|
13154
|
+
const cacheVerifiedRecord = async (record11) => {
|
|
13155
|
+
try {
|
|
13156
|
+
const user2 = await this.whoAmI();
|
|
13157
|
+
if (!owner2.isCurrent()) return;
|
|
13158
|
+
const accountId = typeof user2.id === "string" ? user2.id : typeof user2.user_id === "string" ? user2.user_id : null;
|
|
13159
|
+
const trustedAccountId = loadTrustedAccountId();
|
|
13160
|
+
if (accountId && (trustedAccountId ? trustedAccountId === accountId : process.env.OPENMATES_ACCOUNT_GUARD !== "required")) {
|
|
13161
|
+
saveCachedDirectChatAccountKey(this.requireSession(), this.apiUrl, accountId, record11);
|
|
13162
|
+
}
|
|
13163
|
+
} catch {
|
|
13164
|
+
}
|
|
13165
|
+
};
|
|
13166
|
+
let existing;
|
|
13167
|
+
try {
|
|
13168
|
+
existing = await this.http.get(
|
|
13169
|
+
"/v1/account/chat-encryption-key",
|
|
13170
|
+
this.getCliRequestHeaders()
|
|
13171
|
+
);
|
|
13172
|
+
} catch (error) {
|
|
13173
|
+
if (!(error instanceof TypeError)) throw error;
|
|
13174
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key recovery.");
|
|
13175
|
+
const cached = loadCachedDirectChatAccountKey(this.requireSession(), this.apiUrl);
|
|
13176
|
+
if (!cached) throw error;
|
|
13177
|
+
return recover(cached.record);
|
|
13178
|
+
}
|
|
13179
|
+
if (!existing.ok && existing.status !== 404) throw new Error(`Account chat encryption key failed with HTTP ${existing.status}`);
|
|
13180
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key enrollment.");
|
|
13181
|
+
if (existing.data.key) {
|
|
13182
|
+
const identity4 = await recover(existing.data.key);
|
|
13183
|
+
await cacheVerifiedRecord(existing.data.key);
|
|
13184
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key recovery.");
|
|
13185
|
+
return identity4;
|
|
13186
|
+
}
|
|
13187
|
+
const generated = await createAccountChatIdentity(masterKey);
|
|
13188
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key enrollment.");
|
|
13189
|
+
const saved = await this.http.put(
|
|
13190
|
+
"/v1/account/chat-encryption-key",
|
|
13191
|
+
generated.record,
|
|
13192
|
+
this.getCliRequestHeaders()
|
|
13193
|
+
);
|
|
13194
|
+
if (saved.status === 409) {
|
|
13195
|
+
const current2 = await this.http.get(
|
|
13196
|
+
"/v1/account/chat-encryption-key",
|
|
13197
|
+
this.getCliRequestHeaders()
|
|
13198
|
+
);
|
|
13199
|
+
if (!owner2.isCurrent() || !current2.ok || !current2.data.key) throw new Error("Account chat encryption key enrollment conflicted. Retry.");
|
|
13200
|
+
const identity4 = await recover(current2.data.key);
|
|
13201
|
+
await cacheVerifiedRecord(current2.data.key);
|
|
13202
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key recovery.");
|
|
13203
|
+
return identity4;
|
|
13204
|
+
}
|
|
13205
|
+
if (!owner2.isCurrent() || !saved.ok) throw new Error(`Account chat encryption key enrollment failed with HTTP ${saved.status}`);
|
|
13206
|
+
const record10 = saved.data.key ?? generated.record;
|
|
13207
|
+
const identity3 = await recover(record10);
|
|
13208
|
+
await cacheVerifiedRecord(record10);
|
|
13209
|
+
if (!owner2.isCurrent()) throw new Error("Account changed during chat encryption key recovery.");
|
|
13210
|
+
return identity3;
|
|
13211
|
+
}
|
|
12868
13212
|
async getTopicPreferences() {
|
|
12869
13213
|
const user2 = await this.whoAmI();
|
|
12870
13214
|
return await this.decryptTopicPreferences(user2.encrypted_settings);
|
|
@@ -13231,6 +13575,20 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13231
13575
|
}
|
|
13232
13576
|
async resolveChatKey(cache, cached, wrappingKey, teamId = null) {
|
|
13233
13577
|
const chatId = String(cached.details.id ?? "");
|
|
13578
|
+
if (cached.details.chat_visibility === "direct") {
|
|
13579
|
+
if (!teamId || !chatId || cached.details.team_id && cached.details.team_id !== teamId || cached.details.hashed_team_id && cached.details.hashed_team_id !== computeSHA256(teamId)) {
|
|
13580
|
+
throw new Error("Direct chat Team binding mismatch.");
|
|
13581
|
+
}
|
|
13582
|
+
const ownHash = await this.ownDirectChatHash();
|
|
13583
|
+
const participants = cached.details.participant_user_hashes;
|
|
13584
|
+
if (!Array.isArray(participants) || participants.length !== 2 || !participants.includes(ownHash)) {
|
|
13585
|
+
throw new Error("Direct chat participant binding mismatch.");
|
|
13586
|
+
}
|
|
13587
|
+
const encrypted = cached.details.encrypted_chat_key;
|
|
13588
|
+
if (typeof encrypted !== "string") throw new Error("Direct chat envelope unavailable.");
|
|
13589
|
+
const identity3 = await this.ensureDirectChatIdentity();
|
|
13590
|
+
return openDirectChatKey(encrypted, { teamId, chatId, recipientHash: ownHash, keyId: identity3.keyId }, identity3.privateKey);
|
|
13591
|
+
}
|
|
13234
13592
|
const wrapperKey = chatId ? this.getContextWrapperEncryptedChatKey(cache, chatId, teamId) : null;
|
|
13235
13593
|
const encryptedChatKey = wrapperKey ?? (typeof cached.details.encrypted_chat_key === "string" ? cached.details.encrypted_chat_key : null);
|
|
13236
13594
|
return encryptedChatKey ? this.decryptChatKey(encryptedChatKey, wrappingKey) : null;
|
|
@@ -13262,6 +13620,8 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
13262
13620
|
const slug = chatKeyBytes && typeof d.encrypted_slug === "string" ? slugValue ?? "" : null;
|
|
13263
13621
|
return {
|
|
13264
13622
|
id,
|
|
13623
|
+
chatVisibility: d.chat_visibility === "direct" ? "direct" : teamId ? "team" : "personal",
|
|
13624
|
+
participantUserHashes: Array.isArray(d.participant_user_hashes) ? d.participant_user_hashes.filter((value) => typeof value === "string") : void 0,
|
|
13265
13625
|
shortId: id.slice(0, 8),
|
|
13266
13626
|
slug,
|
|
13267
13627
|
title,
|
|
@@ -14408,17 +14768,40 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
14408
14768
|
const selectedTeamId = this.getActiveTeamId();
|
|
14409
14769
|
const current2 = () => {
|
|
14410
14770
|
const active = this.session;
|
|
14411
|
-
if (!active || active.apiUrl !== owner2[0] || active.hashedEmail !== owner2[1] || active.sessionId !== owner2[2] || active.createdAt !== owner2[3] || active.masterKeyExportedB64 !== owner2[4] ||
|
|
14771
|
+
if (!active || active.apiUrl !== owner2[0] || active.hashedEmail !== owner2[1] || active.sessionId !== owner2[2] || active.createdAt !== owner2[3] || active.masterKeyExportedB64 !== owner2[4] || this.getActiveTeamId() !== selectedTeamId || this.resolveTeamContext(options) !== teamId)
|
|
14412
14772
|
throw new Error("Chat workspace changed");
|
|
14413
14773
|
};
|
|
14414
|
-
|
|
14774
|
+
let cache = options.preferCache ? loadSyncCache(teamId) : await this.ensureSynced(false, [], options);
|
|
14415
14775
|
current2();
|
|
14416
14776
|
if (!cache) throw new Error("Chat cache unavailable. Open the chat to refresh it.");
|
|
14777
|
+
const diskCacheAtStart = loadSyncCache(teamId);
|
|
14778
|
+
const diskChatIdsAtStart = diskCacheAtStart?.chats.map((chat) => String(chat.details.id));
|
|
14417
14779
|
const localTeamKey = options.preferCache && teamId ? loadLocalTeamKey(owner2[1], teamId) : null;
|
|
14418
14780
|
if (options.preferCache && teamId && !localTeamKey) throw new Error("Team key unavailable offline. Reconnect to refresh Team access.");
|
|
14419
14781
|
const wrappingKey = localTeamKey ? base64ToBytes(localTeamKey) : await this.getChatWrappingKey(teamId, masterKey);
|
|
14420
14782
|
current2();
|
|
14421
|
-
|
|
14783
|
+
let found;
|
|
14784
|
+
let fetchedMetadata = false;
|
|
14785
|
+
try {
|
|
14786
|
+
found = await this.resolveCachedChatForQuery(query, cache, wrappingKey, teamId);
|
|
14787
|
+
} catch (error) {
|
|
14788
|
+
if (options.preferCache || !(error instanceof Error) || !/^Chat '.*' not found\./.test(error.message) || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(query)) throw error;
|
|
14789
|
+
const response2 = await this.http.post(
|
|
14790
|
+
this.appendTeamQuery("/v1/chats/metadata/batch", { teamId }),
|
|
14791
|
+
{ chat_ids: [query] },
|
|
14792
|
+
this.getCliRequestHeaders()
|
|
14793
|
+
);
|
|
14794
|
+
current2();
|
|
14795
|
+
if (!response2.ok) throw new Error(`Chat metadata failed with HTTP ${response2.status}`);
|
|
14796
|
+
const details = response2.data.chats.find((chat) => String(chat.id ?? "") === query);
|
|
14797
|
+
if (!details) throw error;
|
|
14798
|
+
found = { details, messages: [] };
|
|
14799
|
+
fetchedMetadata = true;
|
|
14800
|
+
cache = { ...cache, chats: [...cache.chats, found], chatKeyWrappers: [
|
|
14801
|
+
...cache.chatKeyWrappers ?? [],
|
|
14802
|
+
...Array.isArray(details.chat_key_wrappers) ? details.chat_key_wrappers : []
|
|
14803
|
+
] };
|
|
14804
|
+
}
|
|
14422
14805
|
current2();
|
|
14423
14806
|
const chatItem = await this.decryptChatListItem(found, wrappingKey, cache, teamId);
|
|
14424
14807
|
current2();
|
|
@@ -14446,17 +14829,19 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
14446
14829
|
const rawMessages = Array.isArray(response.data.messages) ? response.data.messages : [];
|
|
14447
14830
|
const messages = await this.decryptRawChatMessages(rawMessages, chatItem, chatKeyBytes, cache.embeds);
|
|
14448
14831
|
current2();
|
|
14449
|
-
if (
|
|
14832
|
+
if ((options.direction ?? "latest") === "latest") {
|
|
14450
14833
|
const latestCache = loadSyncCache(teamId);
|
|
14451
14834
|
const row3 = latestCache?.chats.find((chat) => String(chat.details.id) === chatId);
|
|
14452
|
-
|
|
14835
|
+
const sameVersion = row3 && row3.details.messages_v === found.details.messages_v && row3.details.encrypted_chat_key === found.details.encrypted_chat_key && row3.messages.length === found.messages.length && row3.messages.every((raw, index) => raw === found.messages[index]);
|
|
14836
|
+
if (latestCache && (sameVersion || fetchedMetadata && !row3 && diskCacheAtStart && latestCache.syncedAt === diskCacheAtStart.syncedAt && latestCache.totalChatCount === diskCacheAtStart.totalChatCount && latestCache.loadedChatCount === diskCacheAtStart.loadedChatCount && latestCache.chats.length === diskChatIdsAtStart?.length && latestCache.chats.every((chat, index) => String(chat.details.id) === diskChatIdsAtStart[index]))) {
|
|
14453
14837
|
const canonicalId = (raw) => {
|
|
14454
14838
|
const value = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
14455
14839
|
return String(value.client_message_id ?? value.id ?? value.message_id ?? "");
|
|
14456
14840
|
};
|
|
14457
|
-
const
|
|
14841
|
+
const existing = row3?.messages ?? [];
|
|
14842
|
+
const existingById = new Map(existing.map((raw, index) => [canonicalId(raw), index]));
|
|
14458
14843
|
const overlap = rawMessages.find((raw) => !!canonicalId(raw) && existingById.has(canonicalId(raw)));
|
|
14459
|
-
const older = response.data.has_more_before !== true ? [] : overlap ?
|
|
14844
|
+
const older = response.data.has_more_before !== true ? [] : overlap ? existing.slice(0, existingById.get(canonicalId(overlap))) : existing;
|
|
14460
14845
|
const byId = /* @__PURE__ */ new Map();
|
|
14461
14846
|
for (const raw of [...older, ...rawMessages]) {
|
|
14462
14847
|
const value = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
@@ -14466,8 +14851,21 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
14466
14851
|
}
|
|
14467
14852
|
const merged = [...byId.values()].sort((a, b) => a.createdAt - b.createdAt).map((message) => message.raw);
|
|
14468
14853
|
current2();
|
|
14469
|
-
|
|
14470
|
-
|
|
14854
|
+
try {
|
|
14855
|
+
if (!row3) {
|
|
14856
|
+
const wrappers = Array.isArray(found.details.chat_key_wrappers) ? found.details.chat_key_wrappers : [];
|
|
14857
|
+
saveSyncCache({
|
|
14858
|
+
...latestCache,
|
|
14859
|
+
chats: [...latestCache.chats, { details: found.details, messages: merged }],
|
|
14860
|
+
loadedChatCount: latestCache.loadedChatCount + 1,
|
|
14861
|
+
totalChatCount: Math.max(latestCache.totalChatCount, latestCache.loadedChatCount + 1),
|
|
14862
|
+
chatKeyWrappers: [...latestCache.chatKeyWrappers ?? [], ...wrappers]
|
|
14863
|
+
}, teamId);
|
|
14864
|
+
} else if (merged.length !== row3.messages.length || merged.some((raw, index) => raw !== row3.messages[index])) {
|
|
14865
|
+
saveSyncCache({ ...latestCache, chats: latestCache.chats.map((chat) => chat === row3 ? { ...row3, messages: merged } : chat) }, teamId);
|
|
14866
|
+
}
|
|
14867
|
+
} catch {
|
|
14868
|
+
}
|
|
14471
14869
|
}
|
|
14472
14870
|
}
|
|
14473
14871
|
return {
|
|
@@ -15764,7 +16162,7 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
15764
16162
|
baselineMessagesV = typeof chat.details.messages_v === "number" ? chat.details.messages_v : 0;
|
|
15765
16163
|
const encKey = typeof chat.details.encrypted_chat_key === "string" ? chat.details.encrypted_chat_key : null;
|
|
15766
16164
|
if (encKey) {
|
|
15767
|
-
chatKeyBytes = await
|
|
16165
|
+
chatKeyBytes = await this.resolveChatKey(cache, chat, wrappingKey, teamId);
|
|
15768
16166
|
encryptedChatKey = encKey;
|
|
15769
16167
|
if (!chatKeyBytes) throw new Error("Could not decrypt the saved chat key.");
|
|
15770
16168
|
if (typeof chat.details.encrypted_title === "string") {
|
|
@@ -29019,6 +29417,7 @@ var OpenMates = class {
|
|
|
29019
29417
|
sdkName;
|
|
29020
29418
|
sdkSessionPromise;
|
|
29021
29419
|
masterKeyPromise;
|
|
29420
|
+
chatIdentityPromise;
|
|
29022
29421
|
constructor(options = {}) {
|
|
29023
29422
|
this.apiKey = options.apiKey ?? process.env.OPENMATES_API_KEY;
|
|
29024
29423
|
this.apiUrl = (options.apiUrl ?? DEFAULT_API_URL2).replace(/\/$/, "");
|
|
@@ -29123,6 +29522,34 @@ var OpenMates = class {
|
|
|
29123
29522
|
sdkSession() {
|
|
29124
29523
|
return this.getSdkSession();
|
|
29125
29524
|
}
|
|
29525
|
+
async chatIdentity() {
|
|
29526
|
+
if (!this.chatIdentityPromise) this.chatIdentityPromise = this.loadChatIdentity().catch((error) => {
|
|
29527
|
+
this.chatIdentityPromise = void 0;
|
|
29528
|
+
throw error;
|
|
29529
|
+
});
|
|
29530
|
+
return this.chatIdentityPromise;
|
|
29531
|
+
}
|
|
29532
|
+
async loadChatIdentity() {
|
|
29533
|
+
const masterKey = await this.masterKey();
|
|
29534
|
+
let response;
|
|
29535
|
+
try {
|
|
29536
|
+
response = await this.get("/v1/account/chat-encryption-key");
|
|
29537
|
+
} catch (error) {
|
|
29538
|
+
if (!(error instanceof OpenMatesApiError) || error.status !== 404) throw error;
|
|
29539
|
+
response = { key: null };
|
|
29540
|
+
}
|
|
29541
|
+
if (response.key) return recoverAccountChatIdentity(response.key, masterKey);
|
|
29542
|
+
const generated = await createAccountChatIdentity(masterKey);
|
|
29543
|
+
try {
|
|
29544
|
+
const saved = await this.put("/v1/account/chat-encryption-key", generated.record);
|
|
29545
|
+
return recoverAccountChatIdentity(saved.key ?? generated.record, masterKey);
|
|
29546
|
+
} catch (error) {
|
|
29547
|
+
if (!(error instanceof OpenMatesApiError) || error.status !== 409) throw error;
|
|
29548
|
+
const current2 = await this.get("/v1/account/chat-encryption-key");
|
|
29549
|
+
if (!current2.key) throw new OpenMatesConfigError("Account chat encryption key enrollment conflicted; retry.");
|
|
29550
|
+
return recoverAccountChatIdentity(current2.key, masterKey);
|
|
29551
|
+
}
|
|
29552
|
+
}
|
|
29126
29553
|
/** @internal Resource-key resolver shared by the Project namespace. */
|
|
29127
29554
|
async _getProjectGrantKey(projectId) {
|
|
29128
29555
|
const session = await this.getSdkSession();
|
|
@@ -29197,6 +29624,26 @@ var OpenMates = class {
|
|
|
29197
29624
|
return { ...payload, chat: decryptedChat, messages, embeds };
|
|
29198
29625
|
}
|
|
29199
29626
|
async resolveLoadedChatKey(chat, chatKeyWrappers) {
|
|
29627
|
+
if (chat.chat_visibility === "direct") {
|
|
29628
|
+
const session = await this.sdkSession();
|
|
29629
|
+
const userId = session.user?.id;
|
|
29630
|
+
if (!userId) throw new OpenMatesConfigError("Authenticated identity is required for a direct chat");
|
|
29631
|
+
const ownHash = createHash14("sha256").update(userId).digest("hex");
|
|
29632
|
+
const participants = chat.participant_user_hashes;
|
|
29633
|
+
if (!Array.isArray(participants) || participants.length !== 2 || !participants.includes(ownHash)) {
|
|
29634
|
+
throw new OpenMatesConfigError("Direct chat participant binding mismatch");
|
|
29635
|
+
}
|
|
29636
|
+
const identity3 = await this.chatIdentity();
|
|
29637
|
+
if (typeof chat.team_id !== "string" || typeof chat.encrypted_chat_key !== "string") {
|
|
29638
|
+
throw new OpenMatesConfigError("Direct chat key metadata is incomplete");
|
|
29639
|
+
}
|
|
29640
|
+
return openDirectChatKey(chat.encrypted_chat_key, {
|
|
29641
|
+
teamId: chat.team_id,
|
|
29642
|
+
chatId: chat.id,
|
|
29643
|
+
recipientHash: ownHash,
|
|
29644
|
+
keyId: identity3.keyId
|
|
29645
|
+
}, identity3.privateKey);
|
|
29646
|
+
}
|
|
29200
29647
|
const hashedChatId = createHash14("sha256").update(chat.id).digest("hex");
|
|
29201
29648
|
const wrapper = (chatKeyWrappers ?? []).find(
|
|
29202
29649
|
(entry) => entry.key_type === "master" && entry.hashed_chat_id === hashedChatId && typeof entry.encrypted_chat_key === "string"
|
|
@@ -32833,9 +33280,11 @@ var OpenMatesTeams = class {
|
|
|
32833
33280
|
}
|
|
32834
33281
|
async list() {
|
|
32835
33282
|
const result = await this.client.get("/v1/teams");
|
|
33283
|
+
if (result.teams?.length) await this.client.chatIdentity();
|
|
32836
33284
|
return result.teams ?? [];
|
|
32837
33285
|
}
|
|
32838
33286
|
async get(teamId) {
|
|
33287
|
+
await this.client.chatIdentity();
|
|
32839
33288
|
const result = await this.client.get(`/v1/teams/${encodeURIComponent(teamId)}`);
|
|
32840
33289
|
return result.team ?? result;
|
|
32841
33290
|
}
|
|
@@ -32893,6 +33342,62 @@ var OpenMatesTeams = class {
|
|
|
32893
33342
|
async members(teamId) {
|
|
32894
33343
|
return this.client.get(`/v1/teams/${encodeURIComponent(teamId)}/members`);
|
|
32895
33344
|
}
|
|
33345
|
+
async memberChatEncryptionKey(teamId, memberHash) {
|
|
33346
|
+
if (!/^[0-9a-f]{64}$/.test(memberHash)) throw new OpenMatesConfigError("Member hash must be a SHA-256 hex digest");
|
|
33347
|
+
try {
|
|
33348
|
+
const result = await this.client.get(
|
|
33349
|
+
`/v1/teams/${encodeURIComponent(teamId)}/members/${encodeURIComponent(memberHash)}/chat-encryption-key`
|
|
33350
|
+
);
|
|
33351
|
+
return validatePublicChatEncryptionKeyId(result);
|
|
33352
|
+
} catch (error) {
|
|
33353
|
+
if (error instanceof OpenMatesApiError && error.status === 404) {
|
|
33354
|
+
throw new OpenMatesConfigError("Direct message pending: this Team member has not enrolled a chat encryption key yet.");
|
|
33355
|
+
}
|
|
33356
|
+
throw error;
|
|
33357
|
+
}
|
|
33358
|
+
}
|
|
33359
|
+
async openDirectChat(teamId, recipientHash) {
|
|
33360
|
+
if (!teamId || !/^[0-9a-f]{64}$/.test(recipientHash)) throw new OpenMatesConfigError("A Team ID and member hash are required");
|
|
33361
|
+
const session = await this.client.sdkSession();
|
|
33362
|
+
const userId = session.user?.id;
|
|
33363
|
+
if (!userId) throw new OpenMatesConfigError("Authenticated identity is required for a direct chat");
|
|
33364
|
+
const ownHash = createHash14("sha256").update(userId).digest("hex");
|
|
33365
|
+
if (ownHash === recipientHash) throw new OpenMatesConfigError("Choose another Team member to message");
|
|
33366
|
+
const [ownIdentity, recipientKey] = await Promise.all([
|
|
33367
|
+
this.client.chatIdentity(),
|
|
33368
|
+
this.memberChatEncryptionKey(teamId, recipientHash)
|
|
33369
|
+
]);
|
|
33370
|
+
const chatId = randomUUID10();
|
|
33371
|
+
const chatKey = new Uint8Array(randomBytes6(32));
|
|
33372
|
+
const ownEnvelope = await sealDirectChatKey(
|
|
33373
|
+
chatKey,
|
|
33374
|
+
{ teamId, chatId, recipientHash: ownHash, keyId: ownIdentity.keyId },
|
|
33375
|
+
ownIdentity.publicKey
|
|
33376
|
+
);
|
|
33377
|
+
const recipientEnvelope = await sealDirectChatKey(
|
|
33378
|
+
chatKey,
|
|
33379
|
+
{ teamId, chatId, recipientHash, keyId: recipientKey.key_id },
|
|
33380
|
+
recipientKey.public_key
|
|
33381
|
+
);
|
|
33382
|
+
const result = await this.client.request(
|
|
33383
|
+
`/v1/teams/${encodeURIComponent(teamId)}/direct-chats`,
|
|
33384
|
+
{
|
|
33385
|
+
chat_id: chatId,
|
|
33386
|
+
recipient_user_hash: recipientHash,
|
|
33387
|
+
participant_key_envelopes: {
|
|
33388
|
+
[ownHash]: { key_id: ownIdentity.keyId, encrypted_chat_key: ownEnvelope },
|
|
33389
|
+
[recipientHash]: { key_id: recipientKey.key_id, encrypted_chat_key: recipientEnvelope }
|
|
33390
|
+
},
|
|
33391
|
+
encrypted_title: await encryptWithAesGcmCombined("Private Team message", chatKey)
|
|
33392
|
+
}
|
|
33393
|
+
);
|
|
33394
|
+
if (!result.chat) throw new OpenMatesConfigError("Direct chat creation returned no metadata");
|
|
33395
|
+
const participants = result.chat.participant_user_hashes;
|
|
33396
|
+
if (result.chat.chat_visibility !== "direct" || result.chat.team_id !== teamId || result.chat.id !== chatId || !Array.isArray(participants) || participants.length !== 2 || !participants.includes(ownHash) || !participants.includes(recipientHash)) {
|
|
33397
|
+
throw new OpenMatesConfigError("Direct chat creation returned mismatched metadata");
|
|
33398
|
+
}
|
|
33399
|
+
return this.client.decryptChatMetadata(result.chat);
|
|
33400
|
+
}
|
|
32896
33401
|
async decryptMemberProfile(teamKey, member) {
|
|
32897
33402
|
const encrypted = member.encrypted_member_profile;
|
|
32898
33403
|
if (typeof encrypted !== "string" || !encrypted) return { ...member, profile: null };
|
|
@@ -78429,6 +78934,14 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
78429
78934
|
}
|
|
78430
78935
|
},
|
|
78431
78936
|
notifications: {
|
|
78937
|
+
team_direct_chat: {
|
|
78938
|
+
title: {
|
|
78939
|
+
text: "Private Team message"
|
|
78940
|
+
},
|
|
78941
|
+
body: {
|
|
78942
|
+
text: "You have a new private conversation in your Team."
|
|
78943
|
+
}
|
|
78944
|
+
},
|
|
78432
78945
|
auto_logout: {
|
|
78433
78946
|
title: {
|
|
78434
78947
|
text: "Auto logout"
|
|
@@ -86624,6 +87137,18 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
86624
87137
|
members_description: {
|
|
86625
87138
|
text: "Manage your team members and their permissions."
|
|
86626
87139
|
},
|
|
87140
|
+
direct_message_action: {
|
|
87141
|
+
text: "Message"
|
|
87142
|
+
},
|
|
87143
|
+
direct_message_scope: {
|
|
87144
|
+
text: "Only you and this member can read this chat."
|
|
87145
|
+
},
|
|
87146
|
+
direct_message_pending: {
|
|
87147
|
+
text: "Secure messaging setup is pending. Ask this member to open OpenMates."
|
|
87148
|
+
},
|
|
87149
|
+
direct_message_failed: {
|
|
87150
|
+
text: "Could not open a private message. Try again."
|
|
87151
|
+
},
|
|
86627
87152
|
admins: {
|
|
86628
87153
|
text: "Admins"
|
|
86629
87154
|
},
|
|
@@ -99667,6 +100192,8 @@ var TUI_ACTIONS = [
|
|
|
99667
100192
|
{ label: "Show all workspace items", command: "/browse" },
|
|
99668
100193
|
{ label: "Use selected app skill", command: "/app-run" },
|
|
99669
100194
|
{ label: "Recent chats", command: "/chats" },
|
|
100195
|
+
{ label: "Team members", command: "/members" },
|
|
100196
|
+
{ label: "Message Team member", command: "/message" },
|
|
99670
100197
|
{ label: "Example chats", command: "/examples" },
|
|
99671
100198
|
{ label: "Projects", command: "/projects" },
|
|
99672
100199
|
{ label: "Tasks", command: "/tasks" },
|
|
@@ -102306,7 +102833,7 @@ function relativeTime(timestamp) {
|
|
|
102306
102833
|
return `${Math.floor(seconds / 86400)}d ago`;
|
|
102307
102834
|
}
|
|
102308
102835
|
function renderStatus(state, width) {
|
|
102309
|
-
return ["OpenMates", "", state.status ?? "Working..."].flatMap((line3) => wrap(line3, width));
|
|
102836
|
+
return ["OpenMates", "", ...(state.status ?? "Working...").split("\n")].flatMap((line3) => wrap(line3, width).map((text7) => line3 === "Message \xB7 Enter" ? pointerLine(text7, { kind: "key", name: "return" }) : line3 === "Next member \xB7 \u2193" ? pointerLine(text7, { kind: "key", name: "down" }) : text7));
|
|
102310
102837
|
}
|
|
102311
102838
|
function prepareMessageContent(content, width, state, questionBlocks = true) {
|
|
102312
102839
|
const segments2 = parseMessageSegments(content, { preserveCodeFences: true }).map((segment) => segment.type === "embed" ? segment : {
|
|
@@ -105034,6 +105561,19 @@ function createChatModelPreferences(client2) {
|
|
|
105034
105561
|
}
|
|
105035
105562
|
|
|
105036
105563
|
// src/tui.ts
|
|
105564
|
+
var teamMemberActions = /* @__PURE__ */ new WeakMap();
|
|
105565
|
+
function showSelectedTeamMember(state) {
|
|
105566
|
+
const selection = teamMemberActions.get(state);
|
|
105567
|
+
if (!selection?.members.length) {
|
|
105568
|
+
state.status = "No other active Team members are available.";
|
|
105569
|
+
return;
|
|
105570
|
+
}
|
|
105571
|
+
const member = selection.members[state.selectedIndex] ?? selection.members[0];
|
|
105572
|
+
state.status = `Team members ${state.selectedIndex + 1}/${selection.members.length}
|
|
105573
|
+
${member.label}
|
|
105574
|
+
Message \xB7 Enter
|
|
105575
|
+
Next member \xB7 \u2193`;
|
|
105576
|
+
}
|
|
105037
105577
|
function defaultModeForStreams(input, output) {
|
|
105038
105578
|
return input.isTTY === true && output.isTTY === true ? "tui" : "quickstart";
|
|
105039
105579
|
}
|
|
@@ -105316,6 +105856,21 @@ async function handleKey(params) {
|
|
|
105316
105856
|
modelShell,
|
|
105317
105857
|
mentionShell
|
|
105318
105858
|
};
|
|
105859
|
+
const memberActions = teamMemberActions.get(state);
|
|
105860
|
+
if (state.screen === "status" && memberActions && !memberActions.isCurrent()) teamMemberActions.delete(state);
|
|
105861
|
+
if (state.screen === "status" && memberActions && memberActions.isCurrent() && memberActions.teamId === client2.getActiveTeamId()) {
|
|
105862
|
+
if (memberActions.members.length > 0 && (key.name === "up" || key.name === "down")) {
|
|
105863
|
+
state.selectedIndex = (state.selectedIndex + (key.name === "down" ? 1 : -1) + memberActions.members.length) % memberActions.members.length;
|
|
105864
|
+
showSelectedTeamMember(state);
|
|
105865
|
+
render();
|
|
105866
|
+
return;
|
|
105867
|
+
}
|
|
105868
|
+
if (key.name === "return" && !state.input.trim()) {
|
|
105869
|
+
const member = memberActions.members[state.selectedIndex];
|
|
105870
|
+
if (member) await handleCommand({ command: `/message ${member.hash}`, state, client: client2, terminal, render, finish, modelShell });
|
|
105871
|
+
return;
|
|
105872
|
+
}
|
|
105873
|
+
}
|
|
105319
105874
|
if (key.name === "mouseclick") {
|
|
105320
105875
|
if (!key.mouse || key.ctrl || key.meta || key.shift) return;
|
|
105321
105876
|
const action2 = pointerTargetAt(state, key.mouse.column, key.mouse.row, terminal.width, terminal.height);
|
|
@@ -105491,6 +106046,50 @@ async function handleCommand(params) {
|
|
|
105491
106046
|
rememberDraft(state);
|
|
105492
106047
|
state.input = "";
|
|
105493
106048
|
state.inputCursor = null;
|
|
106049
|
+
if (name === "/members") {
|
|
106050
|
+
const teamId = client2.getActiveTeamId();
|
|
106051
|
+
if (!teamId) {
|
|
106052
|
+
state.status = "Select a Team to view members.";
|
|
106053
|
+
state.screen = "status";
|
|
106054
|
+
render();
|
|
106055
|
+
return;
|
|
106056
|
+
}
|
|
106057
|
+
const isCurrent = captureTuiWorkspaceOwner(client2);
|
|
106058
|
+
const members = await client2.listTeamMembers(teamId);
|
|
106059
|
+
if (!isCurrent() || client2.getActiveTeamId() !== teamId) return;
|
|
106060
|
+
const user2 = await client2.whoAmI();
|
|
106061
|
+
if (!isCurrent() || client2.getActiveTeamId() !== teamId) return;
|
|
106062
|
+
const ownId = typeof user2.id === "string" ? user2.id : typeof user2.user_id === "string" ? user2.user_id : "";
|
|
106063
|
+
teamMemberActions.set(state, { teamId, isCurrent, members: members.filter((member) => member.status === "active" && member.hashed_user_id && member.user_id !== ownId).map((member) => ({ hash: member.hashed_user_id, label: `${member.profile?.display_name || member.user_id} \xB7 ${member.role}` })) });
|
|
106064
|
+
state.selectedIndex = 0;
|
|
106065
|
+
state.screen = "status";
|
|
106066
|
+
showSelectedTeamMember(state);
|
|
106067
|
+
render();
|
|
106068
|
+
return;
|
|
106069
|
+
}
|
|
106070
|
+
if (name === "/message") {
|
|
106071
|
+
const teamId = client2.getActiveTeamId();
|
|
106072
|
+
if (!teamId) {
|
|
106073
|
+
state.status = "Select a Team to message a member.";
|
|
106074
|
+
state.screen = "status";
|
|
106075
|
+
render();
|
|
106076
|
+
return;
|
|
106077
|
+
}
|
|
106078
|
+
const memberHash = parts[0];
|
|
106079
|
+
if (!memberHash) {
|
|
106080
|
+
state.status = "Use /members to find a member, then /message <member-hash>.";
|
|
106081
|
+
state.screen = "status";
|
|
106082
|
+
render();
|
|
106083
|
+
return;
|
|
106084
|
+
}
|
|
106085
|
+
const chat = await client2.openTeamDirectChat(teamId, memberHash);
|
|
106086
|
+
if (client2.getActiveTeamId() !== teamId) return;
|
|
106087
|
+
state.recentChats = [chat, ...state.recentChats.filter((item) => item.id !== chat.id)];
|
|
106088
|
+
await openSavedChat({ state, client: client2, terminal, render, command: async () => {
|
|
106089
|
+
}, send: async () => {
|
|
106090
|
+
} }, chat.id);
|
|
106091
|
+
return;
|
|
106092
|
+
}
|
|
105494
106093
|
if (name === "/privacy") {
|
|
105495
106094
|
const action2 = parts[0] ?? "status";
|
|
105496
106095
|
if (action2 === "later") {
|
|
@@ -115342,24 +115941,33 @@ async function handleTeams(client2, subcommand, rest, flags) {
|
|
|
115342
115941
|
else printTeamRecord(team, client2.getActiveTeamId());
|
|
115343
115942
|
return;
|
|
115344
115943
|
}
|
|
115345
|
-
if (subcommand === "members") {
|
|
115944
|
+
if (subcommand === "members" || subcommand === "member") {
|
|
115346
115945
|
const action2 = rest[0];
|
|
115347
115946
|
const teamId = requiredStringFlag(rest[1], "<team-id>");
|
|
115947
|
+
if (action2 === "message") {
|
|
115948
|
+
const memberHash = requiredStringFlag(rest[2], "<member-hash>");
|
|
115949
|
+
const chat = await client2.openTeamDirectChat(teamId, memberHash);
|
|
115950
|
+
const message = rest.slice(3).join(" ").trim();
|
|
115951
|
+
const response = message ? await client2.sendMessage({ message, chatId: chat.id, teamId }) : void 0;
|
|
115952
|
+
if (flags.json === true) printJson2({ chat_id: chat.id, chat, ...response ? { response } : {} });
|
|
115953
|
+
else console.log(message ? `Message sent in direct chat ${chat.id}` : `Direct chat: ${chat.id}`);
|
|
115954
|
+
return;
|
|
115955
|
+
}
|
|
115348
115956
|
if (action2 === "list") {
|
|
115349
115957
|
const members = (await client2.listTeamMembers(teamId)).map(publicTeamMember);
|
|
115350
115958
|
if (flags.json === true) printJson2({ members });
|
|
115351
115959
|
else if (!members.length) console.log("No Team members.");
|
|
115352
|
-
else for (const member of members) console.log(`${member.display_name || member.user_id} ${member.role} ${member.status || ""} ${member.user_id}`.trim());
|
|
115960
|
+
else for (const member of members) console.log(`${member.display_name || member.user_id} ${member.role} ${member.status || ""} ${member.hashed_user_id || member.user_id}`.trim());
|
|
115353
115961
|
return;
|
|
115354
115962
|
}
|
|
115355
115963
|
if (action2 === "show") {
|
|
115356
115964
|
const userId = requiredStringFlag(rest[2], "<member-user-id>");
|
|
115357
115965
|
const member = publicTeamMember(await client2.getTeamMember(teamId, userId));
|
|
115358
115966
|
if (flags.json === true) printJson2({ member });
|
|
115359
|
-
else console.log(`${member.display_name || member.user_id} ${member.role} ${member.status || ""} ${member.user_id}`.trim());
|
|
115967
|
+
else console.log(`${member.display_name || member.user_id} ${member.role} ${member.status || ""} ${member.hashed_user_id || member.user_id}`.trim());
|
|
115360
115968
|
return;
|
|
115361
115969
|
}
|
|
115362
|
-
throw new Error("Use teams members list
|
|
115970
|
+
throw new Error("Use teams members list, show, or message <team-id> <member-hash> [text].");
|
|
115363
115971
|
}
|
|
115364
115972
|
if (subcommand === "profile") {
|
|
115365
115973
|
const action2 = rest[0];
|
|
@@ -124613,6 +125221,7 @@ function printTeamsHelp() {
|
|
|
124613
125221
|
openmates teams role <team-id> --user <user-id> --role admin|member|viewer [--json]
|
|
124614
125222
|
openmates teams members list <team-id> [--json]
|
|
124615
125223
|
openmates teams members show <team-id> <member-user-id> [--json]
|
|
125224
|
+
openmates teams member message <team-id> <member-hash> [text] [--json]
|
|
124616
125225
|
openmates teams profile show <team-id> [--json]
|
|
124617
125226
|
openmates teams profile edit <team-id> [--name <display-name>] [--avatar <value>] [--json]
|
|
124618
125227
|
openmates teams remove-member <team-id> --user <user-id> [--json]
|