replicas-cli 0.2.544 → 0.2.546

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 +259 -113
  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)) {
@@ -7631,6 +7645,7 @@ var AGENT = {
7631
7645
  CURSOR: "cursor",
7632
7646
  DEEPSEEK: "deepseek",
7633
7647
  FX: "fx",
7648
+ KIMI: "kimi",
7634
7649
  OPENCODE: "opencode",
7635
7650
  PI: "pi",
7636
7651
  RELAY: "relay"
@@ -7672,6 +7687,11 @@ var AGENT_PROVIDER_OPTIONS = [
7672
7687
  label: "fx",
7673
7688
  description: "Use fx for tasks"
7674
7689
  },
7690
+ {
7691
+ value: "kimi",
7692
+ label: "Kimi Code",
7693
+ description: "Use Kimi Code for tasks"
7694
+ },
7675
7695
  {
7676
7696
  value: "opencode",
7677
7697
  label: "Opencode",
@@ -7696,6 +7716,7 @@ var THINKING_LEVELS_BY_AGENT = {
7696
7716
  cursor: STANDARD_THINKING_LEVELS,
7697
7717
  deepseek: STANDARD_THINKING_LEVELS,
7698
7718
  fx: STANDARD_THINKING_LEVELS,
7719
+ kimi: STANDARD_THINKING_LEVELS,
7699
7720
  opencode: STANDARD_THINKING_LEVELS,
7700
7721
  pi: STANDARD_THINKING_LEVELS,
7701
7722
  relay: [...STANDARD_THINKING_LEVELS, "ultracode"]
@@ -7719,6 +7740,8 @@ function getProviderDisplayName(provider, variant = "short") {
7719
7740
  return variant === "long" ? "DeepSeek Harness" : "DeepSeek";
7720
7741
  case "fx":
7721
7742
  return "fx";
7743
+ case "kimi":
7744
+ return variant === "long" ? "Kimi Code" : "Kimi";
7722
7745
  case "opencode":
7723
7746
  return "Opencode";
7724
7747
  case "pi":
@@ -7787,6 +7810,9 @@ var AGENT_CREDENTIAL_METHODS_IN_ORDER = {
7787
7810
  [AGENT.FX]: [
7788
7811
  { method: CREDENTIAL_METHOD.AI_GATEWAY, credentialType: CREDENTIAL_TYPE.AI_GATEWAY_API_KEY }
7789
7812
  ],
7813
+ [AGENT.KIMI]: [
7814
+ { method: CREDENTIAL_METHOD.AI_GATEWAY, credentialType: CREDENTIAL_TYPE.AI_GATEWAY_API_KEY }
7815
+ ],
7790
7816
  [AGENT.OPENCODE]: [
7791
7817
  { method: CREDENTIAL_METHOD.OPENCODE_GO, credentialType: CREDENTIAL_TYPE.OPENCODE_GO_API_KEY },
7792
7818
  { method: CREDENTIAL_METHOD.ASTER, credentialType: CREDENTIAL_TYPE.ASTER_API_KEY },
@@ -22401,6 +22427,7 @@ var DEFAULT_OPENCODE_MODEL = "z-ai/glm-5.2";
22401
22427
  var DEEPSEEK_V4_PRO_MODEL = "deepseek/deepseek-v4-pro-0813";
22402
22428
  var DEEPSEEK_V4_FLASH_MODEL = "deepseek/deepseek-v4-flash-0731";
22403
22429
  var DEFAULT_FX_MODEL = "zai/glm-5.2";
22430
+ var DEFAULT_KIMI_MODEL = "moonshotai/kimi-k2.6";
22404
22431
  var OPENROUTER_MODELS = [
22405
22432
  DEFAULT_OPENCODE_MODEL,
22406
22433
  DEEPSEEK_V4_PRO_MODEL,
@@ -22487,6 +22514,7 @@ var AGENT_MODELS = {
22487
22514
  ],
22488
22515
  deepseek: prioritizeDeepseekModels(OPENROUTER_MODELS),
22489
22516
  fx: [DEFAULT_FX_MODEL],
22517
+ kimi: [DEFAULT_KIMI_MODEL],
22490
22518
  opencode: [...OPENROUTER_MODELS, ...ASTER_MODELS],
22491
22519
  pi: [...OPENROUTER_MODELS, ...ASTER_MODELS],
22492
22520
  relay: [CLAUDE_FABLE_5_MODEL, CLAUDE_OPUS_5_MODEL, CLAUDE_OPUS_4_8_MODEL, CLAUDE_SONNET_5_MODEL]
@@ -24378,7 +24406,7 @@ var headlessFilesystemAgentResultSchema = external_exports.object({
24378
24406
  });
24379
24407
 
24380
24408
  // ../shared/src/cli-version.ts
24381
- var CLI_VERSION = "0.2.544";
24409
+ var CLI_VERSION = "0.2.546";
24382
24410
 
24383
24411
  // ../shared/src/version.ts
24384
24412
  function compareVersions(v1, v2) {
@@ -25494,71 +25522,146 @@ function parseDeepseekEvents(events) {
25494
25522
  return messages;
25495
25523
  }
25496
25524
 
25497
- // ../shared/src/display-message/parsers/fx-parser.ts
25498
- function getFxTextChunk(update, kind) {
25525
+ // ../shared/src/display-message/parsers/acp-parser.ts
25526
+ var FX_CONTEXT_DIAGNOSTIC = /^\[context\] skill catalog omitted .*?(?:\r?\n|$)/;
25527
+ function getAcpTextChunk(update, kind) {
25499
25528
  if (!isRecord(update) || update.sessionUpdate !== kind || !isRecord(update.content) || update.content.type !== "text") return null;
25500
- return typeof update.content.text === "string" && update.content.text ? update.content.text : null;
25529
+ return typeof update.content.text === "string" && update.content.text.length > 0 ? update.content.text : null;
25501
25530
  }
25502
- function parseFxEvents(events) {
25531
+ function isProviderEvent(event, provider, suffix) {
25532
+ return event.type === `acp-${suffix}` && (event.payload.provider === void 0 || event.payload.provider === provider) || event.type === `${provider}-${suffix}`;
25533
+ }
25534
+ function parseAcpEvents(events, provider) {
25503
25535
  const messages = [];
25536
+ const activeChunks = {};
25537
+ const resolvedInputs = /* @__PURE__ */ new Map();
25538
+ let chunkSequence = 0;
25504
25539
  for (const event of events) {
25505
- const content = getUserMessage(event);
25506
- if (content !== null) {
25507
- if (content.trim()) {
25540
+ const userContent = getUserMessage(event);
25541
+ if (userContent !== null) {
25542
+ activeChunks.agent_message_chunk = void 0;
25543
+ activeChunks.agent_thought_chunk = void 0;
25544
+ if (userContent.trim()) {
25508
25545
  messages.push({
25509
- id: getUserMessageId(event) ?? `fx-user-${event.timestamp}`,
25546
+ id: getUserMessageId(event) ?? `${provider}-user-${event.timestamp}`,
25510
25547
  type: "user",
25511
- content,
25548
+ content: userContent,
25512
25549
  timestamp: event.timestamp
25513
25550
  });
25514
25551
  }
25515
25552
  continue;
25516
25553
  }
25517
- if (event.type === "fx-error") {
25554
+ if (isProviderEvent(event, provider, "error")) {
25518
25555
  messages.push({
25519
- id: `fx-error-${event.timestamp}`,
25556
+ id: `${provider}-error-${event.timestamp}`,
25520
25557
  type: "error",
25521
- message: typeof event.payload.message === "string" ? event.payload.message : "fx failed",
25558
+ message: typeof event.payload.message === "string" ? event.payload.message : `${provider} failed`,
25522
25559
  timestamp: event.timestamp
25523
25560
  });
25524
25561
  continue;
25525
25562
  }
25526
- if (event.type === "fx-turn-complete") {
25563
+ if (isProviderEvent(event, provider, "turn-complete")) {
25527
25564
  for (const message of messages) {
25528
25565
  if (message.type === "reasoning" && message.status === "in_progress") message.status = "completed";
25529
25566
  }
25567
+ activeChunks.agent_message_chunk = void 0;
25568
+ activeChunks.agent_thought_chunk = void 0;
25569
+ continue;
25570
+ }
25571
+ if (event.type === "replicas-tool-input-request" && typeof event.payload.toolUseId === "string" && typeof event.payload.requestId === "string") {
25572
+ const message = messages.find((candidate) => candidate.type === "tool_call" && candidate.id === `${provider}-tool-${event.payload.toolUseId}`);
25573
+ if (message && (Array.isArray(event.payload.options) || Array.isArray(event.payload.questions))) {
25574
+ const resolved = resolvedInputs.get(event.payload.toolUseId);
25575
+ message.inputRequest = {
25576
+ requestId: event.payload.requestId,
25577
+ status: resolved?.selectionId === "aborted" ? "aborted" : resolved ? "resolved" : "pending",
25578
+ ...Array.isArray(event.payload.options) ? { options: event.payload.options } : {},
25579
+ ...Array.isArray(event.payload.questions) ? { questions: event.payload.questions } : {},
25580
+ ...resolved ? { selectionId: resolved.selectionId } : {},
25581
+ ...resolved?.selectionSummary ? { selectionSummary: resolved.selectionSummary } : {}
25582
+ };
25583
+ }
25584
+ continue;
25585
+ }
25586
+ if (event.type === "replicas-tool-input-resolved" && typeof event.payload.toolUseId === "string" && typeof event.payload.selectionId === "string") {
25587
+ const resolved = {
25588
+ selectionId: event.payload.selectionId,
25589
+ ...typeof event.payload.selectionSummary === "string" ? { selectionSummary: event.payload.selectionSummary } : {}
25590
+ };
25591
+ resolvedInputs.set(event.payload.toolUseId, resolved);
25592
+ const message = messages.find((candidate) => candidate.type === "tool_call" && candidate.id === `${provider}-tool-${event.payload.toolUseId}`);
25593
+ if (message?.inputRequest) {
25594
+ message.inputRequest = {
25595
+ ...message.inputRequest,
25596
+ status: resolved.selectionId === "aborted" ? "aborted" : "resolved",
25597
+ ...resolved
25598
+ };
25599
+ }
25530
25600
  continue;
25531
25601
  }
25532
- if (event.type !== "fx-session-update" || !isRecord(event.payload.update)) continue;
25602
+ if (!isProviderEvent(event, provider, "session-update") || !isRecord(event.payload.update)) continue;
25533
25603
  const update = event.payload.update;
25534
25604
  const kind = update.sessionUpdate;
25535
25605
  if (kind === "agent_message_chunk" || kind === "agent_thought_chunk") {
25536
- const content2 = getFxTextChunk(update, kind);
25537
- if (!content2) continue;
25538
- const id = typeof update.messageId === "string" ? `fx-${update.messageId}` : `fx-${kind}-${event.timestamp}`;
25606
+ const content = getAcpTextChunk(update, kind);
25607
+ if (content === null) continue;
25608
+ const explicitId = typeof update.messageId === "string" && update.messageId.length > 0 ? `${provider}-${kind}-${update.messageId}` : null;
25609
+ const id = explicitId ?? activeChunks[kind] ?? `${provider}-${kind}-${chunkSequence++}`;
25610
+ activeChunks[kind] = id;
25611
+ const otherKind = kind === "agent_message_chunk" ? "agent_thought_chunk" : "agent_message_chunk";
25612
+ if (!explicitId) activeChunks[otherKind] = void 0;
25539
25613
  const existing = messages.find((message) => message.id === id);
25540
- const text = `${existing && "content" in existing && typeof existing.content === "string" ? existing.content : ""}${content2}`;
25614
+ const text = `${existing && "content" in existing && typeof existing.content === "string" ? existing.content : ""}${content}`.replace(provider === "fx" ? FX_CONTEXT_DIAGNOSTIC : /$^/, "");
25615
+ if (!text) continue;
25541
25616
  upsertDisplayMessage(messages, kind === "agent_thought_chunk" ? { id, type: "reasoning", content: text, status: "in_progress", timestamp: existing?.timestamp ?? event.timestamp } : { id, type: "agent", content: text, timestamp: existing?.timestamp ?? event.timestamp });
25542
25617
  continue;
25543
25618
  }
25619
+ activeChunks.agent_message_chunk = void 0;
25620
+ activeChunks.agent_thought_chunk = void 0;
25544
25621
  if ((kind === "tool_call" || kind === "tool_call_update") && typeof update.toolCallId === "string") {
25545
- const id = `fx-tool-${update.toolCallId}`;
25622
+ const id = `${provider}-tool-${update.toolCallId}`;
25546
25623
  const existing = messages.find((message) => message.id === id && message.type === "tool_call");
25547
25624
  const status = update.status === "completed" || update.status === "failed" ? update.status === "failed" ? "failed" : "completed" : "in_progress";
25625
+ const contentOutput = Array.isArray(update.content) && update.content.length > 0 ? stringifyDisplayValue(update.content) : void 0;
25548
25626
  upsertDisplayMessage(messages, createCallDisplayMessage({
25549
25627
  id,
25550
- server: "fx",
25551
- tool: typeof update.name === "string" ? update.name : typeof update.title === "string" ? update.title : existing?.tool ?? "tool",
25628
+ server: provider,
25629
+ tool: typeof update.name === "string" && update.name ? update.name : typeof update.title === "string" && update.title ? update.title : existing?.tool ?? (typeof update.kind === "string" ? update.kind : "tool"),
25552
25630
  input: update.rawInput === void 0 ? existing?.input : isRecord(update.rawInput) ? update.rawInput : stringifyDisplayValue(update.rawInput),
25553
- output: update.rawOutput === void 0 ? existing?.output : stringifyDisplayValue(update.rawOutput),
25631
+ output: update.rawOutput === void 0 ? contentOutput ?? existing?.output : stringifyDisplayValue(update.rawOutput),
25554
25632
  status,
25555
- timestamp: event.timestamp
25633
+ timestamp: existing?.timestamp ?? event.timestamp
25556
25634
  }));
25635
+ continue;
25636
+ }
25637
+ const plan = kind === "plan" ? update : isRecord(update.plan) ? update.plan : void 0;
25638
+ if ((kind === "plan" || kind === "plan_update") && plan && Array.isArray(plan.entries)) {
25639
+ const items = plan.entries.flatMap((entry) => {
25640
+ if (!isRecord(entry) || typeof entry.content !== "string") return [];
25641
+ const itemStatus = entry.status === "completed" || entry.status === "in_progress" ? entry.status : "pending";
25642
+ return [{
25643
+ text: entry.content,
25644
+ completed: entry.status === "completed",
25645
+ itemStatus
25646
+ }];
25647
+ });
25648
+ upsertDisplayMessage(messages, {
25649
+ id: `${provider}-plan-${typeof plan.planId === "string" ? plan.planId : "current"}`,
25650
+ type: "todo_list",
25651
+ items,
25652
+ status: items.every((item) => item.completed) ? "completed" : "in_progress",
25653
+ timestamp: event.timestamp
25654
+ });
25557
25655
  }
25558
25656
  }
25559
25657
  return messages;
25560
25658
  }
25561
25659
 
25660
+ // ../shared/src/display-message/parsers/fx-parser.ts
25661
+ function parseFxEvents(events) {
25662
+ return parseAcpEvents(events, "fx");
25663
+ }
25664
+
25562
25665
  // ../shared/src/display-message/parsers/opencode-parser.ts
25563
25666
  function timestampFromMs(value, fallback) {
25564
25667
  return typeof value === "number" && Number.isFinite(value) ? new Date(value).toISOString() : fallback;
@@ -26726,6 +26829,9 @@ function parseAgentEvents(events, agentType) {
26726
26829
  if (agentType === "fx") {
26727
26830
  return parseFxEvents(events);
26728
26831
  }
26832
+ if (agentType === "kimi") {
26833
+ return parseAcpEvents(events, "kimi");
26834
+ }
26729
26835
  if (agentType === "opencode") {
26730
26836
  return parseOpencodeEvents(events);
26731
26837
  }
@@ -26924,45 +27030,7 @@ async function readSseStream(body, onEvent, shouldContinue = () => true) {
26924
27030
  // src/lib/monolith-url.ts
26925
27031
  var MONOLITH_URL = process.env.REPLICAS_MONOLITH_URL || process.env.MONOLITH_URL || "https://api.tryreplicas.com";
26926
27032
 
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
- }
27033
+ // src/lib/authenticated-api.ts
26966
27034
  var ApiError = class extends Error {
26967
27035
  constructor(message, status) {
26968
27036
  super(message);
@@ -26975,39 +27043,24 @@ async function throwResponseError(response) {
26975
27043
  throw new ApiError(error51.error || `Request failed with status ${response.status}`, response.status);
26976
27044
  }
26977
27045
  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 = {
27046
+ const requestHeaders = new Headers({ "Content-Type": "application/json", ...headers });
27047
+ new Headers(options?.headers).forEach((value, key) => requestHeaders.set(key, value));
27048
+ const response = await fetchWithRedirects(`${MONOLITH_URL}${url2}`, {
26984
27049
  ...options,
26985
27050
  headers: requestHeaders,
26986
27051
  body: options?.body !== void 0 ? JSON.stringify(options.body) : void 0
26987
- };
26988
- const response = await fetchWithRedirects(absoluteUrl, requestInit);
27052
+ });
26989
27053
  if (!response.ok) {
26990
27054
  await throwResponseError(response);
26991
27055
  }
26992
27056
  return response.json();
26993
27057
  }
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;
27058
+ async function authenticatedFetch(url2, options) {
27059
+ const token = await getValidToken();
27060
+ if (!token) {
27061
+ throw new Error("No access token available");
27009
27062
  }
27010
- await readSseStream(response.body, options.onEvent);
27063
+ return apiFetch(url2, { Authorization: `Bearer ${token}` }, options);
27011
27064
  }
27012
27065
 
27013
27066
  // src/lib/organization.ts
@@ -27018,7 +27071,11 @@ async function fetchOrganizations() {
27018
27071
  return response.organizations;
27019
27072
  }
27020
27073
  async function fetchActiveRole() {
27021
- return orgAuthenticatedFetch("/v1/organization/membership").then((response) => response.role).catch(() => null);
27074
+ const organizationId = getConfirmedOrganizationId();
27075
+ if (!organizationId) return null;
27076
+ return authenticatedFetch("/v1/organization/membership", {
27077
+ headers: { "Replicas-Org-Id": organizationId }
27078
+ }).then((response) => response.role).catch(() => null);
27022
27079
  }
27023
27080
  async function setActiveOrganization(organizationId) {
27024
27081
  const organizations = await fetchOrganizations();
@@ -27031,7 +27088,14 @@ async function setActiveOrganization(organizationId) {
27031
27088
  async function findActiveOrganization() {
27032
27089
  const organizations = await fetchOrganizations();
27033
27090
  const currentId = getOrganizationId();
27034
- return { organizations, organization: organizations.find((org2) => org2.id === currentId) ?? null };
27091
+ const storedOrganization = organizations.find((org2) => org2.id === currentId) ?? null;
27092
+ if (currentId && !storedOrganization) {
27093
+ clearOrganizationId();
27094
+ }
27095
+ return {
27096
+ organizations,
27097
+ organization: isOrganizationSelectionConfirmed() ? storedOrganization : null
27098
+ };
27035
27099
  }
27036
27100
  async function promptForOrganization(organizations, currentId) {
27037
27101
  const { organizationId } = await (0, import_prompts3.default)({
@@ -27046,30 +27110,30 @@ async function promptForOrganization(organizations, currentId) {
27046
27110
  });
27047
27111
  return organizations.find((org2) => org2.id === organizationId) ?? null;
27048
27112
  }
27049
- async function ensureOrganization(options = {}) {
27113
+ async function ensureOrganizationSelection(options = {}) {
27050
27114
  const { organizations, organization } = await findActiveOrganization();
27051
27115
  if (organizations.length === 0) {
27052
27116
  throw new Error("You are not a member of any organization. Please contact support.");
27053
27117
  }
27054
27118
  if (organization) {
27055
- return { organization, total: organizations.length, role: await fetchActiveRole() };
27119
+ return { organization, total: organizations.length };
27056
27120
  }
27057
- const canAsk = options.interactive === true && organizations.length > 1 && !!process.stdin.isTTY;
27121
+ const canAsk = options.interactive === true && !!process.stdin.isTTY;
27058
27122
  if (canAsk) {
27059
27123
  const chosen = await promptForOrganization(organizations, null);
27060
27124
  if (!chosen) {
27061
- clearOrganizationId();
27062
- return { organization: null, total: organizations.length, role: null };
27125
+ return { organization: null, total: organizations.length };
27063
27126
  }
27064
27127
  setOrganizationId(chosen.id);
27065
- return { organization: chosen, total: organizations.length, role: await fetchActiveRole() };
27128
+ return { organization: chosen, total: organizations.length };
27066
27129
  }
27067
- const [newestOrganization] = organizations;
27068
- setOrganizationId(newestOrganization.id);
27130
+ return { organization: null, total: organizations.length };
27131
+ }
27132
+ async function ensureOrganization(options = {}) {
27133
+ const selection = await ensureOrganizationSelection(options);
27069
27134
  return {
27070
- organization: newestOrganization,
27071
- total: organizations.length,
27072
- role: await fetchActiveRole()
27135
+ ...selection,
27136
+ role: selection.organization ? await fetchActiveRole() : null
27073
27137
  };
27074
27138
  }
27075
27139
  async function resolveActiveOrganization() {
@@ -27124,7 +27188,7 @@ async function resolveAuthFlowTarget(identity) {
27124
27188
  }
27125
27189
  const [replicasAccount, active] = await Promise.all([
27126
27190
  getCurrentUser().then((user) => user.email).catch(() => null),
27127
- resolveActiveOrganization()
27191
+ ensureOrganization({ interactive: true })
27128
27192
  ]);
27129
27193
  return {
27130
27194
  ...identity,
@@ -27320,6 +27384,7 @@ async function loginCommand() {
27320
27384
  refresh_token: refreshToken2,
27321
27385
  expires_at: parseInt(expiresAt, 10),
27322
27386
  organization_id: existingConfig?.organization_id,
27387
+ organization_selection_confirmed: existingConfig?.organization_selection_confirmed,
27323
27388
  ide_command: existingConfig?.ide_command
27324
27389
  };
27325
27390
  try {
@@ -27408,7 +27473,7 @@ function logoutCommand() {
27408
27473
  console.log(import_chalk3.default.yellow("You are not logged in."));
27409
27474
  return;
27410
27475
  }
27411
- deleteConfig();
27476
+ clearAuthentication();
27412
27477
  console.log(import_chalk3.default.green("\u2713 Successfully logged out!"));
27413
27478
  }
27414
27479
 
@@ -27476,6 +27541,54 @@ async function connectSSH(token, host, proxyCommand) {
27476
27541
  var import_chalk5 = __toESM(require("chalk"));
27477
27542
  var import_prompts5 = __toESM(require("prompts"));
27478
27543
 
27544
+ // src/lib/api.ts
27545
+ async function buildOrgAuthHeaders() {
27546
+ const extraHeaders = {};
27547
+ if (isAgentMode()) {
27548
+ const agent = readAgentConfig();
27549
+ if (!agent) {
27550
+ throw new Error("Agent mode config is incomplete");
27551
+ }
27552
+ extraHeaders["X-Workspace-Id"] = agent.workspace_id;
27553
+ return { bearer: agent.engine_secret, extraHeaders };
27554
+ }
27555
+ const bearer = await getValidToken();
27556
+ const selectedId = getConfirmedOrganizationId();
27557
+ const organizationId = selectedId ?? (await ensureOrganizationSelection({ interactive: true })).organization?.id;
27558
+ if (!organizationId) {
27559
+ throw new Error(
27560
+ 'No organization selected. Please run "replicas org switch" to select an organization.'
27561
+ );
27562
+ }
27563
+ extraHeaders["Replicas-Org-Id"] = organizationId;
27564
+ return { bearer, extraHeaders };
27565
+ }
27566
+ async function orgAuthenticatedFetch(url2, options) {
27567
+ const { bearer, extraHeaders } = await buildOrgAuthHeaders();
27568
+ return apiFetch(url2, {
27569
+ "Authorization": `Bearer ${bearer}`,
27570
+ ...extraHeaders
27571
+ }, options);
27572
+ }
27573
+ async function orgAuthenticatedSseStream(url2, options) {
27574
+ const { bearer, extraHeaders } = await buildOrgAuthHeaders();
27575
+ const response = await fetch(`${MONOLITH_URL}${url2}`, {
27576
+ method: "POST",
27577
+ headers: {
27578
+ "Authorization": `Bearer ${bearer}`,
27579
+ "Content-Type": "application/json",
27580
+ "Accept": "text/event-stream",
27581
+ ...extraHeaders
27582
+ },
27583
+ body: JSON.stringify(options.body)
27584
+ });
27585
+ if (!response.ok || !response.body) {
27586
+ await throwResponseError(response);
27587
+ return;
27588
+ }
27589
+ await readSseStream(response.body, options.onEvent);
27590
+ }
27591
+
27479
27592
  // src/lib/git.ts
27480
27593
  var import_child_process2 = require("child_process");
27481
27594
  var import_path2 = __toESM(require("path"));
@@ -27770,7 +27883,7 @@ async function orgCommand() {
27770
27883
  process.exit(1);
27771
27884
  }
27772
27885
  try {
27773
- const currentOrgId = getOrganizationId();
27886
+ const currentOrgId = getConfirmedOrganizationId();
27774
27887
  const organizations = await fetchOrganizations();
27775
27888
  if (!currentOrgId) {
27776
27889
  console.log(import_chalk8.default.yellow("\n No organization selected."));
@@ -27779,6 +27892,7 @@ async function orgCommand() {
27779
27892
  }
27780
27893
  const currentOrg = organizations.find((org2) => org2.id === currentOrgId);
27781
27894
  if (!currentOrg) {
27895
+ clearOrganizationId();
27782
27896
  console.log(import_chalk8.default.yellow("\n The selected organization is no longer available to you."));
27783
27897
  console.log(import_chalk8.default.gray(' Run "replicas org switch" to select another.\n'));
27784
27898
  return;
@@ -27813,7 +27927,7 @@ async function orgSwitchCommand() {
27813
27927
  console.log(import_chalk8.default.gray(" Please contact support.\n"));
27814
27928
  return;
27815
27929
  }
27816
- const currentOrgId = getOrganizationId();
27930
+ const currentOrgId = getConfirmedOrganizationId();
27817
27931
  let selectedId = organizations[0].id;
27818
27932
  if (organizations.length > 1) {
27819
27933
  const chosen = await promptForOrganization(organizations, currentOrgId);
@@ -28062,7 +28176,7 @@ async function runCodexOAuthFlow(totalSteps) {
28062
28176
  // src/lib/credential-upload.ts
28063
28177
  var import_chalk10 = __toESM(require("chalk"));
28064
28178
  var UPLOAD_STEPS = 1;
28065
- async function withAuthRecovery(operation, assertStillApproved) {
28179
+ async function withAuthRecovery(operation, afterReauthentication) {
28066
28180
  try {
28067
28181
  return { result: await operation(), reauthenticated: false };
28068
28182
  } catch (error51) {
@@ -28073,8 +28187,8 @@ async function withAuthRecovery(operation, assertStillApproved) {
28073
28187
  console.log(import_chalk10.default.yellow(`
28074
28188
  ${message}`));
28075
28189
  await loginCommand();
28076
- await assertStillApproved();
28077
- console.log(import_chalk10.default.gray(" Retrying credential upload..."));
28190
+ await afterReauthentication?.();
28191
+ console.log(import_chalk10.default.gray(" Retrying..."));
28078
28192
  return { result: await operation(), reauthenticated: true };
28079
28193
  }
28080
28194
  }
@@ -28101,7 +28215,7 @@ async function runCredentialAuthFlow(options, flow) {
28101
28215
  if (options.org === true && options.user === true) {
28102
28216
  throw new Error("Pass either --org or --user, not both.");
28103
28217
  }
28104
- const target = await resolveAuthFlowTarget(identity);
28218
+ const { result: target } = await withAuthRecovery(() => resolveAuthFlowTarget(identity));
28105
28219
  if (!isUserScoped && !target.organization) {
28106
28220
  throw new Error(
28107
28221
  "Could not determine which organization to save to. Re-run the command, or use --user to save to your personal account."
@@ -30841,6 +30955,38 @@ function parseBooleanOption(value) {
30841
30955
  }
30842
30956
  var program = new import_commander.Command();
30843
30957
  program.name("replicas").description("CLI for managing Replicas workspaces").version(CLI_VERSION);
30958
+ var organizationOptionalCommands = /* @__PURE__ */ new Set([
30959
+ "computer",
30960
+ "claude-auth",
30961
+ "codex-auth",
30962
+ "config",
30963
+ "init",
30964
+ "login",
30965
+ "logout",
30966
+ "media",
30967
+ "mothership",
30968
+ "org",
30969
+ "service",
30970
+ "whoami"
30971
+ ]);
30972
+ program.hook("preAction", async (_command, actionCommand) => {
30973
+ let rootCommand = actionCommand;
30974
+ while (rootCommand.parent && rootCommand.parent !== program) {
30975
+ rootCommand = rootCommand.parent;
30976
+ }
30977
+ if (isAgentMode() || !isAuthenticated() || organizationOptionalCommands.has(rootCommand.name())) return;
30978
+ try {
30979
+ const { organization } = await ensureOrganizationSelection({ interactive: true });
30980
+ if (!organization) {
30981
+ throw new Error('No organization selected. Run "replicas org switch" to select one.');
30982
+ }
30983
+ } catch (error51) {
30984
+ console.error(import_chalk22.default.red(`
30985
+ \u2717 ${error51 instanceof Error ? error51.message : "Unknown error"}
30986
+ `));
30987
+ process.exit(1);
30988
+ }
30989
+ });
30844
30990
  function registerSlackCommands(parent) {
30845
30991
  const slack = parent.command("slack").description("Manage Slack thread routing");
30846
30992
  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.544",
3
+ "version": "0.2.546",
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": {