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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +144 -90
  3. 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
- Switch between organizations.
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 deleteConfig() {
7412
- if (import_fs.default.existsSync(CONFIG_FILE)) {
7413
- import_fs.default.unlinkSync(CONFIG_FILE);
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.543";
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
- "Content-Type": "application/json",
26980
- ...headers
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 orgAuthenticatedSseStream(url2, options) {
26995
- const { bearer, extraHeaders } = await buildOrgAuthHeaders();
26996
- const response = await fetch(`${MONOLITH_URL}${url2}`, {
26997
- method: "POST",
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
- await readSseStream(response.body, options.onEvent);
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
- return orgAuthenticatedFetch("/v1/organization/membership").then((response) => response.role).catch(() => null);
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
- return { organizations, organization: organizations.find((org2) => org2.id === currentId) ?? null };
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 ensureOrganization(options = {}) {
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, role: await fetchActiveRole() };
27027
+ return { organization, total: organizations.length };
27056
27028
  }
27057
- const canAsk = options.interactive === true && organizations.length > 1 && !!process.stdin.isTTY;
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
- clearOrganizationId();
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, role: await fetchActiveRole() };
27036
+ return { organization: chosen, total: organizations.length };
27066
27037
  }
27067
- const [newestOrganization] = organizations;
27068
- setOrganizationId(newestOrganization.id);
27038
+ return { organization: null, total: organizations.length };
27039
+ }
27040
+ async function ensureOrganization(options = {}) {
27041
+ const selection = await ensureOrganizationSelection(options);
27069
27042
  return {
27070
- organization: newestOrganization,
27071
- total: organizations.length,
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
- resolveActiveOrganization()
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
- deleteConfig();
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 = getOrganizationId();
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 = getOrganizationId();
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, assertStillApproved) {
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 assertStillApproved();
28077
- console.log(import_chalk10.default.gray(" Retrying credential upload..."));
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.2.543",
3
+ "version": "0.2.545",
4
4
  "description": "CLI for managing Replicas workspaces - SSH into cloud dev environments with automatic port forwarding",
5
5
  "main": "dist/index.cjs",
6
6
  "bin": {