replicas-cli 0.2.774 → 0.2.775

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 (2) hide show
  1. package/dist/index.cjs +164 -145
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -7558,96 +7558,6 @@ var import_chalk = __toESM(require("chalk"));
7558
7558
  var import_open = __toESM(require("open"));
7559
7559
  var import_prompts5 = __toESM(require("prompts"));
7560
7560
 
7561
- // src/lib/auth.ts
7562
- var import_supabase_js2 = require("@supabase/supabase-js");
7563
-
7564
- // src/lib/supabase.ts
7565
- var import_supabase_js = require("@supabase/supabase-js");
7566
- var supabaseUrl = process.env.REPLICAS_SUPABASE_URL || DEFAULT_SUPABASE_URL;
7567
- var supabaseAnonKey = process.env.REPLICAS_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY;
7568
- var supabase = (0, import_supabase_js.createClient)(supabaseUrl, supabaseAnonKey, {
7569
- auth: {
7570
- persistSession: true,
7571
- autoRefreshToken: true
7572
- }
7573
- });
7574
-
7575
- // src/lib/auth.ts
7576
- var AuthRefreshError = class extends Error {
7577
- constructor(message = 'Failed to refresh token. Please run "replicas login" again.') {
7578
- super(message);
7579
- this.name = "AuthRefreshError";
7580
- }
7581
- };
7582
- var NotAuthenticatedError = class extends Error {
7583
- constructor(message = 'Not authenticated. Please run "replicas login"') {
7584
- super(message);
7585
- this.name = "NotAuthenticatedError";
7586
- }
7587
- };
7588
- function isReplicasAuthError(error51) {
7589
- return error51 instanceof AuthRefreshError || error51 instanceof NotAuthenticatedError;
7590
- }
7591
- function isTokenExpired(expiresAt) {
7592
- const now = Math.floor(Date.now() / 1e3);
7593
- return expiresAt - now < 60;
7594
- }
7595
- async function refreshToken() {
7596
- const config3 = readConfig();
7597
- if (!config3) {
7598
- throw new NotAuthenticatedError();
7599
- }
7600
- let session;
7601
- try {
7602
- const { data, error: error51 } = await supabase.auth.refreshSession({
7603
- refresh_token: config3.refresh_token
7604
- });
7605
- if (error51) throw error51;
7606
- if (!data.session) throw new AuthRefreshError();
7607
- session = data.session;
7608
- } catch (error51) {
7609
- if (error51 instanceof AuthRefreshError) throw error51;
7610
- if ((0, import_supabase_js2.isAuthError)(error51) && !(0, import_supabase_js2.isAuthRetryableFetchError)(error51)) {
7611
- throw new AuthRefreshError();
7612
- }
7613
- throw error51;
7614
- }
7615
- writeConfig({
7616
- ...config3,
7617
- access_token: session.access_token,
7618
- refresh_token: session.refresh_token,
7619
- expires_at: session.expires_at || 0
7620
- });
7621
- }
7622
- async function getValidToken() {
7623
- const config3 = readConfig();
7624
- if (!config3?.access_token) {
7625
- throw new NotAuthenticatedError();
7626
- }
7627
- if (isTokenExpired(config3.expires_at)) {
7628
- await refreshToken();
7629
- const newConfig = readConfig();
7630
- if (!newConfig) {
7631
- throw new AuthRefreshError();
7632
- }
7633
- return newConfig.access_token;
7634
- }
7635
- return config3.access_token;
7636
- }
7637
- async function getCurrentUser() {
7638
- const token = await getValidToken();
7639
- const { data: { user }, error: error51 } = await supabase.auth.getUser(token);
7640
- if (error51) throw error51;
7641
- if (!user) throw new Error("User not found");
7642
- return {
7643
- id: user.id,
7644
- email: user.email || "Unknown"
7645
- };
7646
- }
7647
-
7648
- // src/lib/organization.ts
7649
- var import_prompts4 = __toESM(require("prompts"));
7650
-
7651
7561
  // ../shared/src/type-guards.ts
7652
7562
  function isRecord(value) {
7653
7563
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -27963,7 +27873,7 @@ var headlessAgentRequestSchema = external_exports.object({
27963
27873
  });
27964
27874
 
27965
27875
  // ../shared/src/cli-version.ts
27966
- var CLI_VERSION = "0.2.774";
27876
+ var CLI_VERSION = "0.2.775";
27967
27877
 
27968
27878
  // ../shared/src/version.ts
27969
27879
  function compareVersions(v1, v2) {
@@ -28697,6 +28607,96 @@ function identityTokenUrl() {
28697
28607
  return `http://127.0.0.1:${IDENTITY_PROXY_PORT}${IDENTITY_TOKEN_PATH}`;
28698
28608
  }
28699
28609
 
28610
+ // src/lib/auth.ts
28611
+ var import_supabase_js2 = require("@supabase/supabase-js");
28612
+
28613
+ // src/lib/supabase.ts
28614
+ var import_supabase_js = require("@supabase/supabase-js");
28615
+ var supabaseUrl = process.env.REPLICAS_SUPABASE_URL || DEFAULT_SUPABASE_URL;
28616
+ var supabaseAnonKey = process.env.REPLICAS_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY;
28617
+ var supabase = (0, import_supabase_js.createClient)(supabaseUrl, supabaseAnonKey, {
28618
+ auth: {
28619
+ persistSession: true,
28620
+ autoRefreshToken: true
28621
+ }
28622
+ });
28623
+
28624
+ // src/lib/auth.ts
28625
+ var AuthRefreshError = class extends Error {
28626
+ constructor(message = 'Failed to refresh token. Please run "replicas login" again.') {
28627
+ super(message);
28628
+ this.name = "AuthRefreshError";
28629
+ }
28630
+ };
28631
+ var NotAuthenticatedError = class extends Error {
28632
+ constructor(message = 'Not authenticated. Please run "replicas login"') {
28633
+ super(message);
28634
+ this.name = "NotAuthenticatedError";
28635
+ }
28636
+ };
28637
+ function isReplicasAuthError(error51) {
28638
+ return error51 instanceof AuthRefreshError || error51 instanceof NotAuthenticatedError;
28639
+ }
28640
+ function isTokenExpired(expiresAt) {
28641
+ const now = Math.floor(Date.now() / 1e3);
28642
+ return expiresAt - now < 60;
28643
+ }
28644
+ async function refreshToken() {
28645
+ const config3 = readConfig();
28646
+ if (!config3) {
28647
+ throw new NotAuthenticatedError();
28648
+ }
28649
+ let session;
28650
+ try {
28651
+ const { data, error: error51 } = await supabase.auth.refreshSession({
28652
+ refresh_token: config3.refresh_token
28653
+ });
28654
+ if (error51) throw error51;
28655
+ if (!data.session) throw new AuthRefreshError();
28656
+ session = data.session;
28657
+ } catch (error51) {
28658
+ if (error51 instanceof AuthRefreshError) throw error51;
28659
+ if ((0, import_supabase_js2.isAuthError)(error51) && !(0, import_supabase_js2.isAuthRetryableFetchError)(error51)) {
28660
+ throw new AuthRefreshError();
28661
+ }
28662
+ throw error51;
28663
+ }
28664
+ writeConfig({
28665
+ ...config3,
28666
+ access_token: session.access_token,
28667
+ refresh_token: session.refresh_token,
28668
+ expires_at: session.expires_at || 0
28669
+ });
28670
+ }
28671
+ async function getValidToken() {
28672
+ const config3 = readConfig();
28673
+ if (!config3?.access_token) {
28674
+ throw new NotAuthenticatedError();
28675
+ }
28676
+ if (isTokenExpired(config3.expires_at)) {
28677
+ await refreshToken();
28678
+ const newConfig = readConfig();
28679
+ if (!newConfig) {
28680
+ throw new AuthRefreshError();
28681
+ }
28682
+ return newConfig.access_token;
28683
+ }
28684
+ return config3.access_token;
28685
+ }
28686
+ async function getCurrentUser() {
28687
+ const token = await getValidToken();
28688
+ const { data: { user }, error: error51 } = await supabase.auth.getUser(token);
28689
+ if (error51) throw error51;
28690
+ if (!user) throw new Error("User not found");
28691
+ return {
28692
+ id: user.id,
28693
+ email: user.email || "Unknown"
28694
+ };
28695
+ }
28696
+
28697
+ // src/lib/organization.ts
28698
+ var import_prompts4 = __toESM(require("prompts"));
28699
+
28700
28700
  // src/lib/monolith-url.ts
28701
28701
  var MONOLITH_URL = process.env.REPLICAS_MONOLITH_URL || process.env.MONOLITH_URL || "https://api.replicas.dev";
28702
28702
 
@@ -28822,6 +28822,54 @@ async function resolveActiveOrganization() {
28822
28822
  }
28823
28823
  }
28824
28824
 
28825
+ // src/lib/api.ts
28826
+ async function buildOrgAuthHeaders() {
28827
+ const extraHeaders = {};
28828
+ if (isAgentMode()) {
28829
+ const agent = readAgentConfig();
28830
+ if (!agent) {
28831
+ throw new Error("Agent mode config is incomplete");
28832
+ }
28833
+ extraHeaders["X-Workspace-Id"] = agent.workspace_id;
28834
+ return { bearer: agent.engine_secret, extraHeaders };
28835
+ }
28836
+ const bearer = await getValidToken();
28837
+ const selectedId = getConfirmedOrganizationId();
28838
+ const organizationId = selectedId ?? (await ensureOrganizationSelection({ interactive: true })).organization?.id;
28839
+ if (!organizationId) {
28840
+ throw new Error(
28841
+ 'No organization selected. Please run "replicas org switch" to select an organization.'
28842
+ );
28843
+ }
28844
+ extraHeaders["Replicas-Org-Id"] = organizationId;
28845
+ return { bearer, extraHeaders };
28846
+ }
28847
+ async function orgAuthenticatedFetch(url2, options) {
28848
+ const { bearer, extraHeaders } = await buildOrgAuthHeaders();
28849
+ return apiFetch(url2, {
28850
+ "Authorization": `Bearer ${bearer}`,
28851
+ ...extraHeaders
28852
+ }, options);
28853
+ }
28854
+ async function orgAuthenticatedSseStream(url2, options) {
28855
+ const { bearer, extraHeaders } = await buildOrgAuthHeaders();
28856
+ const response = await fetch(`${MONOLITH_URL}${url2}`, {
28857
+ method: "POST",
28858
+ headers: {
28859
+ "Authorization": `Bearer ${bearer}`,
28860
+ "Content-Type": "application/json",
28861
+ "Accept": "text/event-stream",
28862
+ ...extraHeaders
28863
+ },
28864
+ body: JSON.stringify(options.body)
28865
+ });
28866
+ if (!response.ok || !response.body) {
28867
+ await throwResponseError(response);
28868
+ return;
28869
+ }
28870
+ await readSseStream(response.body, options.onEvent);
28871
+ }
28872
+
28825
28873
  // src/lib/oauth-flow-ui.ts
28826
28874
  var LABEL_WIDTH = 16;
28827
28875
  var brand = import_chalk.default.hex("#3eeba3");
@@ -28829,7 +28877,8 @@ function field(label, value) {
28829
28877
  console.log(` ${import_chalk.default.gray(label.padEnd(LABEL_WIDTH))}${value}`);
28830
28878
  }
28831
28879
  function appliesTo(target) {
28832
- return target.isUserScoped ? "Personal Settings" : "Organization Settings";
28880
+ if (!target.isUserScoped) return "Organization Settings";
28881
+ return target.sharesWithOrganization ? "Personal Settings + Organization Settings" : "Personal Settings";
28833
28882
  }
28834
28883
  function hyperlink(url2) {
28835
28884
  return `\x1B]8;;${url2}\x07${url2}\x1B]8;;\x07`;
@@ -28857,18 +28906,24 @@ async function resolveAuthFlowTarget(identity) {
28857
28906
  ...identity,
28858
28907
  replicasAccount: null,
28859
28908
  organization: null,
28860
- role: null
28909
+ role: null,
28910
+ sharesWithOrganization: false
28861
28911
  };
28862
28912
  }
28863
28913
  const [replicasAccount, active] = await Promise.all([
28864
28914
  getCurrentUser().then((user) => user.email).catch(() => null),
28865
28915
  ensureOrganization({ interactive: true })
28866
28916
  ]);
28917
+ const sharesWithOrganization = identity.isUserScoped && active.organization !== null && active.role === "admin" && await Promise.all([
28918
+ orgAuthenticatedFetch("/v1/organization/onboarding"),
28919
+ orgAuthenticatedFetch(identity.orgEndpoint)
28920
+ ]).then(([{ onboarding }, orgCredential]) => !onboarding.completed && !orgCredential.hasCredentials).catch(() => false);
28867
28921
  return {
28868
28922
  ...identity,
28869
28923
  replicasAccount,
28870
28924
  organization: active.organization,
28871
- role: active.role
28925
+ role: active.role,
28926
+ sharesWithOrganization
28872
28927
  };
28873
28928
  }
28874
28929
  async function verifyAuthFlowTarget(target, assumeYes) {
@@ -28879,6 +28934,15 @@ async function verifyAuthFlowTarget(target, assumeYes) {
28879
28934
  if (!target.isUserScoped) {
28880
28935
  console.log(import_chalk.default.gray("\n Use --user to apply this to your personal account instead."));
28881
28936
  }
28937
+ if (target.sharesWithOrganization) {
28938
+ console.log(
28939
+ import_chalk.default.gray(
28940
+ `
28941
+ ${target.organization?.name ?? "Your organization"} has no ${target.credentialLabel} default yet,
28942
+ so this also becomes the organization default.`
28943
+ )
28944
+ );
28945
+ }
28882
28946
  console.log();
28883
28947
  if (assumeYes) {
28884
28948
  console.log(import_chalk.default.gray(" Confirmed by --yes."));
@@ -29362,54 +29426,6 @@ async function connectSSH(token, host, proxyCommand, localForward) {
29362
29426
  var import_chalk6 = __toESM(require("chalk"));
29363
29427
  var import_prompts6 = __toESM(require("prompts"));
29364
29428
 
29365
- // src/lib/api.ts
29366
- async function buildOrgAuthHeaders() {
29367
- const extraHeaders = {};
29368
- if (isAgentMode()) {
29369
- const agent = readAgentConfig();
29370
- if (!agent) {
29371
- throw new Error("Agent mode config is incomplete");
29372
- }
29373
- extraHeaders["X-Workspace-Id"] = agent.workspace_id;
29374
- return { bearer: agent.engine_secret, extraHeaders };
29375
- }
29376
- const bearer = await getValidToken();
29377
- const selectedId = getConfirmedOrganizationId();
29378
- const organizationId = selectedId ?? (await ensureOrganizationSelection({ interactive: true })).organization?.id;
29379
- if (!organizationId) {
29380
- throw new Error(
29381
- 'No organization selected. Please run "replicas org switch" to select an organization.'
29382
- );
29383
- }
29384
- extraHeaders["Replicas-Org-Id"] = organizationId;
29385
- return { bearer, extraHeaders };
29386
- }
29387
- async function orgAuthenticatedFetch(url2, options) {
29388
- const { bearer, extraHeaders } = await buildOrgAuthHeaders();
29389
- return apiFetch(url2, {
29390
- "Authorization": `Bearer ${bearer}`,
29391
- ...extraHeaders
29392
- }, options);
29393
- }
29394
- async function orgAuthenticatedSseStream(url2, options) {
29395
- const { bearer, extraHeaders } = await buildOrgAuthHeaders();
29396
- const response = await fetch(`${MONOLITH_URL}${url2}`, {
29397
- method: "POST",
29398
- headers: {
29399
- "Authorization": `Bearer ${bearer}`,
29400
- "Content-Type": "application/json",
29401
- "Accept": "text/event-stream",
29402
- ...extraHeaders
29403
- },
29404
- body: JSON.stringify(options.body)
29405
- });
29406
- if (!response.ok || !response.body) {
29407
- await throwResponseError(response);
29408
- return;
29409
- }
29410
- await readSseStream(response.body, options.onEvent);
29411
- }
29412
-
29413
29429
  // src/lib/git.ts
29414
29430
  var import_child_process2 = require("child_process");
29415
29431
  var import_path2 = __toESM(require("path"));
@@ -30061,7 +30077,7 @@ async function runCredentialAuthFlow(options, flow) {
30061
30077
  const { providerLabel, credentialLabel, connectorLabel } = flow;
30062
30078
  const totalSteps = flow.oauthSteps + UPLOAD_STEPS;
30063
30079
  const isUserScoped = options.org !== true;
30064
- const identity = { isUserScoped, credentialLabel, connectorLabel };
30080
+ const identity = { isUserScoped, orgEndpoint: flow.orgEndpoint, credentialLabel, connectorLabel };
30065
30081
  try {
30066
30082
  if (options.org === true && options.user === true) {
30067
30083
  throw new Error("Pass either --org or --user, not both.");
@@ -30085,7 +30101,7 @@ async function runCredentialAuthFlow(options, flow) {
30085
30101
  const { result: response, reauthenticated } = await withAuthRecovery(
30086
30102
  () => orgAuthenticatedFetch(isUserScoped ? flow.userEndpoint : flow.orgEndpoint, {
30087
30103
  method: "POST",
30088
- body: flow.toRequestBody(tokens)
30104
+ body: { ...flow.toRequestBody(tokens), share_with_org: target.sharesWithOrganization }
30089
30105
  }),
30090
30106
  async () => {
30091
30107
  const current = await resolveAuthFlowTarget(identity);
@@ -30103,7 +30119,10 @@ async function runCredentialAuthFlow(options, flow) {
30103
30119
  if (!response.success) {
30104
30120
  throw new Error(response.error || `Failed to upload ${providerLabel} credentials to Replicas`);
30105
30121
  }
30106
- const saved = reauthenticated || !target.organization ? await resolveAuthFlowTarget(identity) : target;
30122
+ const saved = {
30123
+ ...reauthenticated || !target.organization ? await resolveAuthFlowTarget(identity) : target,
30124
+ sharesWithOrganization: response.sharedWithOrganization === true
30125
+ };
30107
30126
  renderAuthFlowSuccess(providerLabel, response, saved);
30108
30127
  const completionUrl = new URL("/cli-login/success", process.env.REPLICAS_WEB_URL || DEFAULT_WEB_APP_URL);
30109
30128
  completionUrl.searchParams.set("provider", flow.provider);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.2.774",
3
+ "version": "0.2.775",
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": {