replicas-cli 0.2.543 → 0.2.545
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.cjs +144 -90
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ Display current authenticated user.
|
|
|
36
36
|
Display current organization.
|
|
37
37
|
|
|
38
38
|
### `replicas org switch`
|
|
39
|
-
|
|
39
|
+
Choose the organization used by organization-scoped commands. The selection persists across sessions.
|
|
40
40
|
|
|
41
41
|
### `replicas connect <workspace-name>`
|
|
42
42
|
Connect to a workspace via SSH.
|
package/dist/index.cjs
CHANGED
|
@@ -7408,10 +7408,15 @@ function writeConfig(config3) {
|
|
|
7408
7408
|
throw error51;
|
|
7409
7409
|
}
|
|
7410
7410
|
}
|
|
7411
|
-
function
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7411
|
+
function clearAuthentication() {
|
|
7412
|
+
const config3 = readConfig();
|
|
7413
|
+
if (!config3) return;
|
|
7414
|
+
writeConfig({
|
|
7415
|
+
...config3,
|
|
7416
|
+
access_token: "",
|
|
7417
|
+
refresh_token: "",
|
|
7418
|
+
expires_at: 0
|
|
7419
|
+
});
|
|
7415
7420
|
}
|
|
7416
7421
|
function isAuthenticated() {
|
|
7417
7422
|
const config3 = readConfig();
|
|
@@ -7431,6 +7436,7 @@ function setOrganizationId(organizationId) {
|
|
|
7431
7436
|
throw new Error("No config file found. Please login first.");
|
|
7432
7437
|
}
|
|
7433
7438
|
config3.organization_id = organizationId;
|
|
7439
|
+
config3.organization_selection_confirmed = true;
|
|
7434
7440
|
writeConfig(config3);
|
|
7435
7441
|
}
|
|
7436
7442
|
function clearOrganizationId() {
|
|
@@ -7439,12 +7445,20 @@ function clearOrganizationId() {
|
|
|
7439
7445
|
return;
|
|
7440
7446
|
}
|
|
7441
7447
|
delete config3.organization_id;
|
|
7448
|
+
delete config3.organization_selection_confirmed;
|
|
7442
7449
|
writeConfig(config3);
|
|
7443
7450
|
}
|
|
7444
7451
|
function getOrganizationId() {
|
|
7445
7452
|
const config3 = readConfig();
|
|
7446
7453
|
return config3?.organization_id || null;
|
|
7447
7454
|
}
|
|
7455
|
+
function getConfirmedOrganizationId() {
|
|
7456
|
+
const config3 = readConfig();
|
|
7457
|
+
return config3?.organization_selection_confirmed === true ? config3.organization_id || null : null;
|
|
7458
|
+
}
|
|
7459
|
+
function isOrganizationSelectionConfirmed() {
|
|
7460
|
+
return readConfig()?.organization_selection_confirmed === true;
|
|
7461
|
+
}
|
|
7448
7462
|
function setIdeCommand(ideCommand) {
|
|
7449
7463
|
const config3 = readConfig();
|
|
7450
7464
|
if (!config3) {
|
|
@@ -7553,7 +7567,7 @@ async function refreshToken() {
|
|
|
7553
7567
|
}
|
|
7554
7568
|
async function getValidToken() {
|
|
7555
7569
|
const config3 = readConfig();
|
|
7556
|
-
if (!config3) {
|
|
7570
|
+
if (!config3?.access_token) {
|
|
7557
7571
|
throw new NotAuthenticatedError();
|
|
7558
7572
|
}
|
|
7559
7573
|
if (isTokenExpired(config3.expires_at)) {
|
|
@@ -24378,7 +24392,7 @@ var headlessFilesystemAgentResultSchema = external_exports.object({
|
|
|
24378
24392
|
});
|
|
24379
24393
|
|
|
24380
24394
|
// ../shared/src/cli-version.ts
|
|
24381
|
-
var CLI_VERSION = "0.2.
|
|
24395
|
+
var CLI_VERSION = "0.2.545";
|
|
24382
24396
|
|
|
24383
24397
|
// ../shared/src/version.ts
|
|
24384
24398
|
function compareVersions(v1, v2) {
|
|
@@ -26924,45 +26938,7 @@ async function readSseStream(body, onEvent, shouldContinue = () => true) {
|
|
|
26924
26938
|
// src/lib/monolith-url.ts
|
|
26925
26939
|
var MONOLITH_URL = process.env.REPLICAS_MONOLITH_URL || process.env.MONOLITH_URL || "https://api.tryreplicas.com";
|
|
26926
26940
|
|
|
26927
|
-
// src/lib/api.ts
|
|
26928
|
-
async function buildOrgAuthHeaders() {
|
|
26929
|
-
const extraHeaders = {};
|
|
26930
|
-
if (isAgentMode()) {
|
|
26931
|
-
const agent = readAgentConfig();
|
|
26932
|
-
if (!agent) {
|
|
26933
|
-
throw new Error("Agent mode config is incomplete");
|
|
26934
|
-
}
|
|
26935
|
-
extraHeaders["X-Workspace-Id"] = agent.workspace_id;
|
|
26936
|
-
return { bearer: agent.engine_secret, extraHeaders };
|
|
26937
|
-
}
|
|
26938
|
-
const bearer = await getValidToken();
|
|
26939
|
-
const organizationId = getOrganizationId();
|
|
26940
|
-
if (!organizationId) {
|
|
26941
|
-
throw new Error(
|
|
26942
|
-
'No organization selected. Please run "replicas org switch" to select an organization.'
|
|
26943
|
-
);
|
|
26944
|
-
}
|
|
26945
|
-
extraHeaders["Replicas-Org-Id"] = organizationId;
|
|
26946
|
-
return { bearer, extraHeaders };
|
|
26947
|
-
}
|
|
26948
|
-
async function authenticatedFetch(url2, options) {
|
|
26949
|
-
const token = await getValidToken();
|
|
26950
|
-
if (!token) {
|
|
26951
|
-
throw new Error("No access token available");
|
|
26952
|
-
}
|
|
26953
|
-
return apiFetch(url2, {
|
|
26954
|
-
"Authorization": `Bearer ${token}`,
|
|
26955
|
-
...options?.headers || {}
|
|
26956
|
-
}, options);
|
|
26957
|
-
}
|
|
26958
|
-
async function orgAuthenticatedFetch(url2, options) {
|
|
26959
|
-
const { bearer, extraHeaders } = await buildOrgAuthHeaders();
|
|
26960
|
-
return apiFetch(url2, {
|
|
26961
|
-
"Authorization": `Bearer ${bearer}`,
|
|
26962
|
-
...extraHeaders,
|
|
26963
|
-
...options?.headers || {}
|
|
26964
|
-
}, options);
|
|
26965
|
-
}
|
|
26941
|
+
// src/lib/authenticated-api.ts
|
|
26966
26942
|
var ApiError = class extends Error {
|
|
26967
26943
|
constructor(message, status) {
|
|
26968
26944
|
super(message);
|
|
@@ -26975,39 +26951,24 @@ async function throwResponseError(response) {
|
|
|
26975
26951
|
throw new ApiError(error51.error || `Request failed with status ${response.status}`, response.status);
|
|
26976
26952
|
}
|
|
26977
26953
|
async function apiFetch(url2, headers, options) {
|
|
26978
|
-
const requestHeaders = {
|
|
26979
|
-
|
|
26980
|
-
|
|
26981
|
-
};
|
|
26982
|
-
const absoluteUrl = `${MONOLITH_URL}${url2}`;
|
|
26983
|
-
const requestInit = {
|
|
26954
|
+
const requestHeaders = new Headers({ "Content-Type": "application/json", ...headers });
|
|
26955
|
+
new Headers(options?.headers).forEach((value, key) => requestHeaders.set(key, value));
|
|
26956
|
+
const response = await fetchWithRedirects(`${MONOLITH_URL}${url2}`, {
|
|
26984
26957
|
...options,
|
|
26985
26958
|
headers: requestHeaders,
|
|
26986
26959
|
body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
|
|
26987
|
-
};
|
|
26988
|
-
const response = await fetchWithRedirects(absoluteUrl, requestInit);
|
|
26960
|
+
});
|
|
26989
26961
|
if (!response.ok) {
|
|
26990
26962
|
await throwResponseError(response);
|
|
26991
26963
|
}
|
|
26992
26964
|
return response.json();
|
|
26993
26965
|
}
|
|
26994
|
-
async function
|
|
26995
|
-
const
|
|
26996
|
-
|
|
26997
|
-
|
|
26998
|
-
headers: {
|
|
26999
|
-
"Authorization": `Bearer ${bearer}`,
|
|
27000
|
-
"Content-Type": "application/json",
|
|
27001
|
-
"Accept": "text/event-stream",
|
|
27002
|
-
...extraHeaders
|
|
27003
|
-
},
|
|
27004
|
-
body: JSON.stringify(options.body)
|
|
27005
|
-
});
|
|
27006
|
-
if (!response.ok || !response.body) {
|
|
27007
|
-
await throwResponseError(response);
|
|
27008
|
-
return;
|
|
26966
|
+
async function authenticatedFetch(url2, options) {
|
|
26967
|
+
const token = await getValidToken();
|
|
26968
|
+
if (!token) {
|
|
26969
|
+
throw new Error("No access token available");
|
|
27009
26970
|
}
|
|
27010
|
-
|
|
26971
|
+
return apiFetch(url2, { Authorization: `Bearer ${token}` }, options);
|
|
27011
26972
|
}
|
|
27012
26973
|
|
|
27013
26974
|
// src/lib/organization.ts
|
|
@@ -27018,7 +26979,11 @@ async function fetchOrganizations() {
|
|
|
27018
26979
|
return response.organizations;
|
|
27019
26980
|
}
|
|
27020
26981
|
async function fetchActiveRole() {
|
|
27021
|
-
|
|
26982
|
+
const organizationId = getConfirmedOrganizationId();
|
|
26983
|
+
if (!organizationId) return null;
|
|
26984
|
+
return authenticatedFetch("/v1/organization/membership", {
|
|
26985
|
+
headers: { "Replicas-Org-Id": organizationId }
|
|
26986
|
+
}).then((response) => response.role).catch(() => null);
|
|
27022
26987
|
}
|
|
27023
26988
|
async function setActiveOrganization(organizationId) {
|
|
27024
26989
|
const organizations = await fetchOrganizations();
|
|
@@ -27031,7 +26996,14 @@ async function setActiveOrganization(organizationId) {
|
|
|
27031
26996
|
async function findActiveOrganization() {
|
|
27032
26997
|
const organizations = await fetchOrganizations();
|
|
27033
26998
|
const currentId = getOrganizationId();
|
|
27034
|
-
|
|
26999
|
+
const storedOrganization = organizations.find((org2) => org2.id === currentId) ?? null;
|
|
27000
|
+
if (currentId && !storedOrganization) {
|
|
27001
|
+
clearOrganizationId();
|
|
27002
|
+
}
|
|
27003
|
+
return {
|
|
27004
|
+
organizations,
|
|
27005
|
+
organization: isOrganizationSelectionConfirmed() ? storedOrganization : null
|
|
27006
|
+
};
|
|
27035
27007
|
}
|
|
27036
27008
|
async function promptForOrganization(organizations, currentId) {
|
|
27037
27009
|
const { organizationId } = await (0, import_prompts3.default)({
|
|
@@ -27046,30 +27018,30 @@ async function promptForOrganization(organizations, currentId) {
|
|
|
27046
27018
|
});
|
|
27047
27019
|
return organizations.find((org2) => org2.id === organizationId) ?? null;
|
|
27048
27020
|
}
|
|
27049
|
-
async function
|
|
27021
|
+
async function ensureOrganizationSelection(options = {}) {
|
|
27050
27022
|
const { organizations, organization } = await findActiveOrganization();
|
|
27051
27023
|
if (organizations.length === 0) {
|
|
27052
27024
|
throw new Error("You are not a member of any organization. Please contact support.");
|
|
27053
27025
|
}
|
|
27054
27026
|
if (organization) {
|
|
27055
|
-
return { organization, total: organizations.length
|
|
27027
|
+
return { organization, total: organizations.length };
|
|
27056
27028
|
}
|
|
27057
|
-
const canAsk = options.interactive === true &&
|
|
27029
|
+
const canAsk = options.interactive === true && !!process.stdin.isTTY;
|
|
27058
27030
|
if (canAsk) {
|
|
27059
27031
|
const chosen = await promptForOrganization(organizations, null);
|
|
27060
27032
|
if (!chosen) {
|
|
27061
|
-
|
|
27062
|
-
return { organization: null, total: organizations.length, role: null };
|
|
27033
|
+
return { organization: null, total: organizations.length };
|
|
27063
27034
|
}
|
|
27064
27035
|
setOrganizationId(chosen.id);
|
|
27065
|
-
return { organization: chosen, total: organizations.length
|
|
27036
|
+
return { organization: chosen, total: organizations.length };
|
|
27066
27037
|
}
|
|
27067
|
-
|
|
27068
|
-
|
|
27038
|
+
return { organization: null, total: organizations.length };
|
|
27039
|
+
}
|
|
27040
|
+
async function ensureOrganization(options = {}) {
|
|
27041
|
+
const selection = await ensureOrganizationSelection(options);
|
|
27069
27042
|
return {
|
|
27070
|
-
|
|
27071
|
-
|
|
27072
|
-
role: await fetchActiveRole()
|
|
27043
|
+
...selection,
|
|
27044
|
+
role: selection.organization ? await fetchActiveRole() : null
|
|
27073
27045
|
};
|
|
27074
27046
|
}
|
|
27075
27047
|
async function resolveActiveOrganization() {
|
|
@@ -27124,7 +27096,7 @@ async function resolveAuthFlowTarget(identity) {
|
|
|
27124
27096
|
}
|
|
27125
27097
|
const [replicasAccount, active] = await Promise.all([
|
|
27126
27098
|
getCurrentUser().then((user) => user.email).catch(() => null),
|
|
27127
|
-
|
|
27099
|
+
ensureOrganization({ interactive: true })
|
|
27128
27100
|
]);
|
|
27129
27101
|
return {
|
|
27130
27102
|
...identity,
|
|
@@ -27320,6 +27292,7 @@ async function loginCommand() {
|
|
|
27320
27292
|
refresh_token: refreshToken2,
|
|
27321
27293
|
expires_at: parseInt(expiresAt, 10),
|
|
27322
27294
|
organization_id: existingConfig?.organization_id,
|
|
27295
|
+
organization_selection_confirmed: existingConfig?.organization_selection_confirmed,
|
|
27323
27296
|
ide_command: existingConfig?.ide_command
|
|
27324
27297
|
};
|
|
27325
27298
|
try {
|
|
@@ -27408,7 +27381,7 @@ function logoutCommand() {
|
|
|
27408
27381
|
console.log(import_chalk3.default.yellow("You are not logged in."));
|
|
27409
27382
|
return;
|
|
27410
27383
|
}
|
|
27411
|
-
|
|
27384
|
+
clearAuthentication();
|
|
27412
27385
|
console.log(import_chalk3.default.green("\u2713 Successfully logged out!"));
|
|
27413
27386
|
}
|
|
27414
27387
|
|
|
@@ -27476,6 +27449,54 @@ async function connectSSH(token, host, proxyCommand) {
|
|
|
27476
27449
|
var import_chalk5 = __toESM(require("chalk"));
|
|
27477
27450
|
var import_prompts5 = __toESM(require("prompts"));
|
|
27478
27451
|
|
|
27452
|
+
// src/lib/api.ts
|
|
27453
|
+
async function buildOrgAuthHeaders() {
|
|
27454
|
+
const extraHeaders = {};
|
|
27455
|
+
if (isAgentMode()) {
|
|
27456
|
+
const agent = readAgentConfig();
|
|
27457
|
+
if (!agent) {
|
|
27458
|
+
throw new Error("Agent mode config is incomplete");
|
|
27459
|
+
}
|
|
27460
|
+
extraHeaders["X-Workspace-Id"] = agent.workspace_id;
|
|
27461
|
+
return { bearer: agent.engine_secret, extraHeaders };
|
|
27462
|
+
}
|
|
27463
|
+
const bearer = await getValidToken();
|
|
27464
|
+
const selectedId = getConfirmedOrganizationId();
|
|
27465
|
+
const organizationId = selectedId ?? (await ensureOrganizationSelection({ interactive: true })).organization?.id;
|
|
27466
|
+
if (!organizationId) {
|
|
27467
|
+
throw new Error(
|
|
27468
|
+
'No organization selected. Please run "replicas org switch" to select an organization.'
|
|
27469
|
+
);
|
|
27470
|
+
}
|
|
27471
|
+
extraHeaders["Replicas-Org-Id"] = organizationId;
|
|
27472
|
+
return { bearer, extraHeaders };
|
|
27473
|
+
}
|
|
27474
|
+
async function orgAuthenticatedFetch(url2, options) {
|
|
27475
|
+
const { bearer, extraHeaders } = await buildOrgAuthHeaders();
|
|
27476
|
+
return apiFetch(url2, {
|
|
27477
|
+
"Authorization": `Bearer ${bearer}`,
|
|
27478
|
+
...extraHeaders
|
|
27479
|
+
}, options);
|
|
27480
|
+
}
|
|
27481
|
+
async function orgAuthenticatedSseStream(url2, options) {
|
|
27482
|
+
const { bearer, extraHeaders } = await buildOrgAuthHeaders();
|
|
27483
|
+
const response = await fetch(`${MONOLITH_URL}${url2}`, {
|
|
27484
|
+
method: "POST",
|
|
27485
|
+
headers: {
|
|
27486
|
+
"Authorization": `Bearer ${bearer}`,
|
|
27487
|
+
"Content-Type": "application/json",
|
|
27488
|
+
"Accept": "text/event-stream",
|
|
27489
|
+
...extraHeaders
|
|
27490
|
+
},
|
|
27491
|
+
body: JSON.stringify(options.body)
|
|
27492
|
+
});
|
|
27493
|
+
if (!response.ok || !response.body) {
|
|
27494
|
+
await throwResponseError(response);
|
|
27495
|
+
return;
|
|
27496
|
+
}
|
|
27497
|
+
await readSseStream(response.body, options.onEvent);
|
|
27498
|
+
}
|
|
27499
|
+
|
|
27479
27500
|
// src/lib/git.ts
|
|
27480
27501
|
var import_child_process2 = require("child_process");
|
|
27481
27502
|
var import_path2 = __toESM(require("path"));
|
|
@@ -27770,7 +27791,7 @@ async function orgCommand() {
|
|
|
27770
27791
|
process.exit(1);
|
|
27771
27792
|
}
|
|
27772
27793
|
try {
|
|
27773
|
-
const currentOrgId =
|
|
27794
|
+
const currentOrgId = getConfirmedOrganizationId();
|
|
27774
27795
|
const organizations = await fetchOrganizations();
|
|
27775
27796
|
if (!currentOrgId) {
|
|
27776
27797
|
console.log(import_chalk8.default.yellow("\n No organization selected."));
|
|
@@ -27779,6 +27800,7 @@ async function orgCommand() {
|
|
|
27779
27800
|
}
|
|
27780
27801
|
const currentOrg = organizations.find((org2) => org2.id === currentOrgId);
|
|
27781
27802
|
if (!currentOrg) {
|
|
27803
|
+
clearOrganizationId();
|
|
27782
27804
|
console.log(import_chalk8.default.yellow("\n The selected organization is no longer available to you."));
|
|
27783
27805
|
console.log(import_chalk8.default.gray(' Run "replicas org switch" to select another.\n'));
|
|
27784
27806
|
return;
|
|
@@ -27813,7 +27835,7 @@ async function orgSwitchCommand() {
|
|
|
27813
27835
|
console.log(import_chalk8.default.gray(" Please contact support.\n"));
|
|
27814
27836
|
return;
|
|
27815
27837
|
}
|
|
27816
|
-
const currentOrgId =
|
|
27838
|
+
const currentOrgId = getConfirmedOrganizationId();
|
|
27817
27839
|
let selectedId = organizations[0].id;
|
|
27818
27840
|
if (organizations.length > 1) {
|
|
27819
27841
|
const chosen = await promptForOrganization(organizations, currentOrgId);
|
|
@@ -28062,7 +28084,7 @@ async function runCodexOAuthFlow(totalSteps) {
|
|
|
28062
28084
|
// src/lib/credential-upload.ts
|
|
28063
28085
|
var import_chalk10 = __toESM(require("chalk"));
|
|
28064
28086
|
var UPLOAD_STEPS = 1;
|
|
28065
|
-
async function withAuthRecovery(operation,
|
|
28087
|
+
async function withAuthRecovery(operation, afterReauthentication) {
|
|
28066
28088
|
try {
|
|
28067
28089
|
return { result: await operation(), reauthenticated: false };
|
|
28068
28090
|
} catch (error51) {
|
|
@@ -28073,8 +28095,8 @@ async function withAuthRecovery(operation, assertStillApproved) {
|
|
|
28073
28095
|
console.log(import_chalk10.default.yellow(`
|
|
28074
28096
|
${message}`));
|
|
28075
28097
|
await loginCommand();
|
|
28076
|
-
await
|
|
28077
|
-
console.log(import_chalk10.default.gray(" Retrying
|
|
28098
|
+
await afterReauthentication?.();
|
|
28099
|
+
console.log(import_chalk10.default.gray(" Retrying..."));
|
|
28078
28100
|
return { result: await operation(), reauthenticated: true };
|
|
28079
28101
|
}
|
|
28080
28102
|
}
|
|
@@ -28101,7 +28123,7 @@ async function runCredentialAuthFlow(options, flow) {
|
|
|
28101
28123
|
if (options.org === true && options.user === true) {
|
|
28102
28124
|
throw new Error("Pass either --org or --user, not both.");
|
|
28103
28125
|
}
|
|
28104
|
-
const target = await resolveAuthFlowTarget(identity);
|
|
28126
|
+
const { result: target } = await withAuthRecovery(() => resolveAuthFlowTarget(identity));
|
|
28105
28127
|
if (!isUserScoped && !target.organization) {
|
|
28106
28128
|
throw new Error(
|
|
28107
28129
|
"Could not determine which organization to save to. Re-run the command, or use --user to save to your personal account."
|
|
@@ -30841,6 +30863,38 @@ function parseBooleanOption(value) {
|
|
|
30841
30863
|
}
|
|
30842
30864
|
var program = new import_commander.Command();
|
|
30843
30865
|
program.name("replicas").description("CLI for managing Replicas workspaces").version(CLI_VERSION);
|
|
30866
|
+
var organizationOptionalCommands = /* @__PURE__ */ new Set([
|
|
30867
|
+
"computer",
|
|
30868
|
+
"claude-auth",
|
|
30869
|
+
"codex-auth",
|
|
30870
|
+
"config",
|
|
30871
|
+
"init",
|
|
30872
|
+
"login",
|
|
30873
|
+
"logout",
|
|
30874
|
+
"media",
|
|
30875
|
+
"mothership",
|
|
30876
|
+
"org",
|
|
30877
|
+
"service",
|
|
30878
|
+
"whoami"
|
|
30879
|
+
]);
|
|
30880
|
+
program.hook("preAction", async (_command, actionCommand) => {
|
|
30881
|
+
let rootCommand = actionCommand;
|
|
30882
|
+
while (rootCommand.parent && rootCommand.parent !== program) {
|
|
30883
|
+
rootCommand = rootCommand.parent;
|
|
30884
|
+
}
|
|
30885
|
+
if (isAgentMode() || !isAuthenticated() || organizationOptionalCommands.has(rootCommand.name())) return;
|
|
30886
|
+
try {
|
|
30887
|
+
const { organization } = await ensureOrganizationSelection({ interactive: true });
|
|
30888
|
+
if (!organization) {
|
|
30889
|
+
throw new Error('No organization selected. Run "replicas org switch" to select one.');
|
|
30890
|
+
}
|
|
30891
|
+
} catch (error51) {
|
|
30892
|
+
console.error(import_chalk22.default.red(`
|
|
30893
|
+
\u2717 ${error51 instanceof Error ? error51.message : "Unknown error"}
|
|
30894
|
+
`));
|
|
30895
|
+
process.exit(1);
|
|
30896
|
+
}
|
|
30897
|
+
});
|
|
30844
30898
|
function registerSlackCommands(parent) {
|
|
30845
30899
|
const slack = parent.command("slack").description("Manage Slack thread routing");
|
|
30846
30900
|
const slackThread = slack.command("thread").description("Attach or switch the Slack thread that routes to a workspace");
|