replicas-cli 0.2.30 → 0.2.32

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.js +1181 -1173
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -813,1273 +813,1281 @@ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
813
813
  var import_chalk6 = __toESM(require("chalk"));
814
814
  var import_child_process4 = require("child_process");
815
815
 
816
- // src/lib/ssh-config.ts
817
- var import_fs3 = __toESM(require("fs"));
818
- var import_path4 = __toESM(require("path"));
819
- var import_os2 = __toESM(require("os"));
820
- var SSH_CONFIG_PATH = import_path4.default.join(import_os2.default.homedir(), ".ssh", "config");
821
- var REPLICAS_MARKER_START = "# === REPLICAS CLI MANAGED ENTRY START ===";
822
- var REPLICAS_MARKER_END = "# === REPLICAS CLI MANAGED ENTRY END ===";
823
- function ensureSSHDir() {
824
- const sshDir = import_path4.default.join(import_os2.default.homedir(), ".ssh");
825
- if (!import_fs3.default.existsSync(sshDir)) {
826
- import_fs3.default.mkdirSync(sshDir, { recursive: true, mode: 448 });
827
- }
828
- }
829
- function readSSHConfig() {
830
- ensureSSHDir();
831
- if (!import_fs3.default.existsSync(SSH_CONFIG_PATH)) {
832
- return "";
816
+ // ../shared/src/sandbox.ts
817
+ var SANDBOX_LIFECYCLE = {
818
+ AUTO_STOP_MINUTES: 60,
819
+ AUTO_ARCHIVE_MINUTES: 60 * 24 * 7,
820
+ AUTO_DELETE_MINUTES: -1,
821
+ SSH_TOKEN_EXPIRATION_MINUTES: 3 * 60
822
+ };
823
+ var SANDBOX_PATHS = {
824
+ HOME_DIR: "/home/ubuntu",
825
+ WORKSPACES_DIR: "/home/ubuntu/workspaces",
826
+ REPLICAS_FILES_DIR: "/home/ubuntu/.replicas/files",
827
+ REPLICAS_FILES_DISPLAY_DIR: "~/.replicas/files"
828
+ };
829
+
830
+ // ../shared/src/routes/workspaces.ts
831
+ var WORKSPACE_FILE_UPLOAD_MAX_SIZE_BYTES = 20 * 1024 * 1024;
832
+ var WORKSPACE_FILE_CONTENT_MAX_SIZE_BYTES = 1 * 1024 * 1024;
833
+
834
+ // ../shared/src/display-message/parsers/codex-parser.ts
835
+ function safeJsonParse(str, fallback) {
836
+ try {
837
+ return JSON.parse(str);
838
+ } catch {
839
+ return fallback;
833
840
  }
834
- return import_fs3.default.readFileSync(SSH_CONFIG_PATH, "utf-8");
835
841
  }
836
- function writeSSHConfig(content) {
837
- ensureSSHDir();
838
- import_fs3.default.writeFileSync(SSH_CONFIG_PATH, content, { mode: 384 });
842
+ function getStatusFromExitCode(exitCode) {
843
+ return exitCode === 0 ? "completed" : "failed";
839
844
  }
840
- function generateConfigBlock(entry) {
841
- const lines = [
842
- REPLICAS_MARKER_START,
843
- `Host ${entry.host}`,
844
- ` HostName ${entry.hostname}`,
845
- ` User ${entry.user}`,
846
- ` StrictHostKeyChecking no`,
847
- ` UserKnownHostsFile /dev/null`
848
- ];
849
- if (entry.portForwards && entry.portForwards.size > 0) {
850
- for (const [localPort, remotePort] of entry.portForwards) {
851
- lines.push(` LocalForward ${localPort} localhost:${remotePort}`);
852
- }
853
- }
854
- lines.push(REPLICAS_MARKER_END);
855
- return lines.join("\n");
845
+ function parseShellOutput(raw) {
846
+ const exitCodeMatch = raw.match(/^Exit code: (\d+)/);
847
+ const exitCode = exitCodeMatch ? parseInt(exitCodeMatch[1], 10) : 0;
848
+ const outputIndex = raw.indexOf("Output:\n");
849
+ const output = outputIndex >= 0 ? raw.slice(outputIndex + "Output:\n".length) : raw;
850
+ return { exitCode, output };
856
851
  }
857
- function addOrUpdateSSHConfigEntry(entry) {
858
- let config2 = readSSHConfig();
859
- const lines = config2.split("\n");
860
- const result = [];
861
- let inTargetBlock = false;
862
- for (const line of lines) {
863
- if (line.trim() === REPLICAS_MARKER_START) {
864
- const nextLines = lines.slice(lines.indexOf(line) + 1, lines.indexOf(line) + 3);
865
- const hostLine = nextLines.find((l) => l.trim().startsWith("Host "));
866
- if (hostLine && hostLine.includes(entry.host)) {
867
- inTargetBlock = true;
868
- continue;
852
+ function parsePatch(input) {
853
+ const operations = [];
854
+ const lines = input.split("\n");
855
+ let currentOp = null;
856
+ let diffLines = [];
857
+ for (let i = 0; i < lines.length; i++) {
858
+ const line = lines[i];
859
+ if (line.startsWith("*** Add File:")) {
860
+ if (currentOp) {
861
+ if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
862
+ operations.push(currentOp);
863
+ diffLines = [];
869
864
  }
870
- result.push(line);
871
- continue;
872
- }
873
- if (line.trim() === REPLICAS_MARKER_END) {
874
- if (inTargetBlock) {
875
- inTargetBlock = false;
876
- continue;
865
+ currentOp = {
866
+ action: "add",
867
+ path: line.replace("*** Add File:", "").trim()
868
+ };
869
+ } else if (line.startsWith("*** Update File:")) {
870
+ if (currentOp) {
871
+ if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
872
+ operations.push(currentOp);
873
+ diffLines = [];
877
874
  }
878
- result.push(line);
879
- continue;
880
- }
881
- if (inTargetBlock) {
875
+ currentOp = {
876
+ action: "update",
877
+ path: line.replace("*** Update File:", "").trim()
878
+ };
879
+ } else if (line.startsWith("*** Delete File:")) {
880
+ if (currentOp) {
881
+ if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
882
+ operations.push(currentOp);
883
+ diffLines = [];
884
+ }
885
+ currentOp = {
886
+ action: "delete",
887
+ path: line.replace("*** Delete File:", "").trim()
888
+ };
889
+ } else if (line.startsWith("*** Move to:")) {
890
+ if (currentOp) {
891
+ currentOp.moveTo = line.replace("*** Move to:", "").trim();
892
+ }
893
+ } else if (line.startsWith("*** Begin Patch") || line.startsWith("*** End Patch")) {
882
894
  continue;
895
+ } else if (currentOp && (line.startsWith("+") || line.startsWith("-") || line.startsWith("@@") || line.trim() === "")) {
896
+ diffLines.push(line);
883
897
  }
884
- result.push(line);
885
- }
886
- config2 = result.join("\n");
887
- const newEntry = generateConfigBlock(entry);
888
- let finalConfig = config2.trim();
889
- if (finalConfig.length > 0) {
890
- finalConfig += "\n\n";
891
- }
892
- finalConfig += newEntry + "\n";
893
- writeSSHConfig(finalConfig);
894
- }
895
- function getWorkspaceHostAlias(workspaceName) {
896
- return `replicas-${workspaceName.toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
897
- }
898
-
899
- // src/commands/code.ts
900
- async function codeCommand(workspaceName, options) {
901
- if (!isAuthenticated()) {
902
- console.log(import_chalk6.default.red('Not logged in. Please run "replicas login" first.'));
903
- process.exit(1);
904
898
  }
905
- try {
906
- const { workspace, sshToken, sshHost, portMappings, repoName } = await prepareWorkspaceConnection(
907
- workspaceName,
908
- options
909
- );
910
- const hostAlias = getWorkspaceHostAlias(workspace.name);
911
- console.log(import_chalk6.default.blue("\nConfiguring SSH connection..."));
912
- addOrUpdateSSHConfigEntry({
913
- host: hostAlias,
914
- hostname: sshHost,
915
- user: sshToken,
916
- portForwards: portMappings
917
- });
918
- console.log(import_chalk6.default.green(`\u2713 SSH config entry created: ${hostAlias}`));
919
- const remotePath = repoName ? `/home/ubuntu/workspaces/${repoName}` : "/home/ubuntu";
920
- const ideCommand = getIdeCommand();
921
- const fullCommand = `${ideCommand} --remote ssh-remote+${hostAlias} ${remotePath}`;
922
- console.log(import_chalk6.default.blue(`
923
- Opening ${ideCommand} for workspace ${workspace.name}...`));
924
- console.log(import_chalk6.default.gray(`Command: ${fullCommand}`));
925
- if (portMappings.size > 0) {
926
- console.log(import_chalk6.default.cyan("\nPort forwarding configured:"));
927
- for (const [remotePort, localPort] of portMappings) {
928
- console.log(import_chalk6.default.cyan(` \u2022 localhost:${localPort} \u2192 workspace:${remotePort}`));
929
- }
930
- }
931
- console.log(import_chalk6.default.yellow(`
932
- Note: SSH tokens expire after 3 hours. Run this command again to refresh.`));
933
- const ide = (0, import_child_process4.spawn)(ideCommand, [
934
- "--remote",
935
- `ssh-remote+${hostAlias}`,
936
- remotePath
937
- ], {
938
- stdio: "inherit",
939
- detached: false
940
- });
941
- ide.on("error", (error) => {
942
- console.error(import_chalk6.default.red(`
943
- Failed to launch ${ideCommand}: ${error.message}`));
944
- console.log(import_chalk6.default.yellow(`
945
- Make sure ${ideCommand} is installed and available in your PATH.`));
946
- console.log(import_chalk6.default.gray(`You can configure a different IDE with: replicas config set ide <command>`));
947
- process.exit(1);
948
- });
949
- ide.on("close", (code) => {
950
- if (code === 0) {
951
- console.log(import_chalk6.default.green(`
952
- \u2713 ${ideCommand} closed successfully.
953
- `));
954
- }
955
- });
956
- } catch (error) {
957
- console.error(import_chalk6.default.red(`
958
- Error: ${error instanceof Error ? error.message : "Unknown error"}`));
959
- process.exit(1);
899
+ if (currentOp) {
900
+ if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
901
+ operations.push(currentOp);
960
902
  }
903
+ return operations;
961
904
  }
962
-
963
- // src/commands/org.ts
964
- var import_chalk7 = __toESM(require("chalk"));
965
- var import_prompts2 = __toESM(require("prompts"));
966
- async function orgCommand() {
967
- if (!isAuthenticated()) {
968
- console.log(import_chalk7.default.red('Not logged in. Please run "replicas login" first.'));
969
- process.exit(1);
970
- }
971
- try {
972
- const currentOrgId = getOrganizationId();
973
- const organizations = await fetchOrganizations();
974
- if (!currentOrgId) {
975
- console.log(import_chalk7.default.yellow("No organization selected."));
976
- console.log(import_chalk7.default.gray('Run "replicas org switch" to select an organization.\n'));
977
- return;
905
+ function parseCodexEvents(events) {
906
+ const messages = [];
907
+ const pendingCommands = /* @__PURE__ */ new Map();
908
+ const pendingPatches = /* @__PURE__ */ new Map();
909
+ events.forEach((event, eventIndex) => {
910
+ if (event.type === "event_msg" && event.payload?.type === "user_message") {
911
+ messages.push({
912
+ id: `user-${event.timestamp}-${eventIndex}`,
913
+ type: "user",
914
+ content: event.payload.message || "",
915
+ timestamp: event.timestamp
916
+ });
978
917
  }
979
- const currentOrg = organizations.find((org2) => org2.id === currentOrgId);
980
- if (!currentOrg) {
981
- console.log(import_chalk7.default.yellow(`Current organization ID (${currentOrgId}) not found.`));
982
- console.log(import_chalk7.default.gray('Run "replicas org switch" to select a new organization.\n'));
983
- return;
918
+ if (event.type === "event_msg" && event.payload?.type === "agent_reasoning") {
919
+ messages.push({
920
+ id: `reasoning-${event.timestamp}-${eventIndex}-${messages.length}`,
921
+ type: "reasoning",
922
+ content: event.payload.text || "",
923
+ status: "completed",
924
+ timestamp: event.timestamp
925
+ });
984
926
  }
985
- console.log(import_chalk7.default.green("\nCurrent organization:"));
986
- console.log(import_chalk7.default.gray(` Name: ${currentOrg.name}`));
987
- console.log(import_chalk7.default.gray(` ID: ${currentOrg.id}
988
- `));
989
- } catch (error) {
990
- console.error(import_chalk7.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
991
- process.exit(1);
992
- }
993
- }
994
- async function orgSwitchCommand() {
995
- if (!isAuthenticated()) {
996
- console.log(import_chalk7.default.red('Not logged in. Please run "replicas login" first.'));
997
- process.exit(1);
998
- }
999
- try {
1000
- const organizations = await fetchOrganizations();
1001
- if (organizations.length === 0) {
1002
- console.log(import_chalk7.default.yellow("You are not a member of any organization."));
1003
- console.log(import_chalk7.default.gray("Please contact support.\n"));
1004
- return;
1005
- }
1006
- if (organizations.length === 1) {
1007
- await setActiveOrganization(organizations[0].id);
1008
- console.log(import_chalk7.default.green(`
1009
- \u2713 Organization set to: ${organizations[0].name}
1010
- `));
1011
- return;
1012
- }
1013
- const currentOrgId = getOrganizationId();
1014
- const response = await (0, import_prompts2.default)({
1015
- type: "select",
1016
- name: "organizationId",
1017
- message: "Select an organization:",
1018
- choices: organizations.map((org2) => ({
1019
- title: `${org2.name}${org2.id === currentOrgId ? " (current)" : ""}`,
1020
- value: org2.id,
1021
- description: org2.id
1022
- })),
1023
- initial: organizations.findIndex((org2) => org2.id === currentOrgId)
1024
- });
1025
- if (!response.organizationId) {
1026
- console.log(import_chalk7.default.yellow("\nCancelled."));
1027
- return;
927
+ if (event.type === "response_item") {
928
+ const payloadType = event.payload?.type;
929
+ if (payloadType === "message" && event.payload?.role === "assistant") {
930
+ const content = event.payload.content || [];
931
+ const textContent = content.filter((c) => c.type === "output_text").map((c) => c.text || "").join("\n");
932
+ if (textContent) {
933
+ messages.push({
934
+ id: `agent-${event.timestamp}-${eventIndex}`,
935
+ type: "agent",
936
+ content: textContent,
937
+ timestamp: event.timestamp
938
+ });
939
+ }
940
+ }
941
+ if (payloadType === "function_call" && (event.payload?.name === "shell" || event.payload?.name === "shell_command")) {
942
+ const callId = event.payload.call_id;
943
+ const args = safeJsonParse(event.payload.arguments || "{}", {});
944
+ const command = Array.isArray(args.command) ? args.command.join(" ") : args.command || "";
945
+ const msg = {
946
+ id: `command-${callId || "no-call-id"}-${eventIndex}`,
947
+ type: "command",
948
+ command,
949
+ output: "",
950
+ status: "in_progress",
951
+ timestamp: event.timestamp
952
+ };
953
+ messages.push(msg);
954
+ if (callId) {
955
+ pendingCommands.set(callId, msg);
956
+ }
957
+ }
958
+ if (payloadType === "function_call_output") {
959
+ const callId = event.payload.call_id;
960
+ const rawOutput = event.payload.output || "";
961
+ const commandMsg = callId ? pendingCommands.get(callId) : void 0;
962
+ if (commandMsg) {
963
+ const { exitCode, output } = parseShellOutput(rawOutput);
964
+ commandMsg.output = output;
965
+ commandMsg.exitCode = exitCode;
966
+ commandMsg.status = getStatusFromExitCode(exitCode);
967
+ pendingCommands.delete(callId);
968
+ }
969
+ }
970
+ if (payloadType === "custom_tool_call") {
971
+ const callId = event.payload.call_id;
972
+ const name = event.payload.name;
973
+ const input = event.payload.input || "";
974
+ const status = event.payload.status || "in_progress";
975
+ if (name === "apply_patch") {
976
+ const operations = parsePatch(input);
977
+ pendingPatches.set(callId, { input, status, timestamp: event.timestamp, operations });
978
+ } else {
979
+ messages.push({
980
+ id: `toolcall-${event.timestamp}-${eventIndex}`,
981
+ type: "tool_call",
982
+ server: "custom",
983
+ tool: name,
984
+ status,
985
+ timestamp: event.timestamp
986
+ });
987
+ }
988
+ }
989
+ if (payloadType === "custom_tool_call_output") {
990
+ const callId = event.payload.call_id;
991
+ const output = safeJsonParse(
992
+ event.payload.output || "{}",
993
+ {}
994
+ );
995
+ const pendingPatch = pendingPatches.get(callId);
996
+ if (pendingPatch) {
997
+ messages.push({
998
+ id: `patch-${pendingPatch.timestamp}-${eventIndex}`,
999
+ type: "patch",
1000
+ operations: pendingPatch.operations,
1001
+ output: output.output || "",
1002
+ exitCode: output.metadata?.exit_code,
1003
+ status: getStatusFromExitCode(output.metadata?.exit_code),
1004
+ timestamp: pendingPatch.timestamp
1005
+ });
1006
+ pendingPatches.delete(callId);
1007
+ } else {
1008
+ const toolCallMsg = messages.findLast((m) => m.type === "tool_call");
1009
+ if (toolCallMsg) {
1010
+ toolCallMsg.status = getStatusFromExitCode(output.metadata?.exit_code);
1011
+ }
1012
+ }
1013
+ }
1014
+ if (payloadType === "function_call" && event.payload?.name === "update_plan") {
1015
+ const args = safeJsonParse(
1016
+ event.payload.arguments || "{}",
1017
+ {}
1018
+ );
1019
+ if (args.plan && Array.isArray(args.plan)) {
1020
+ const todoItems = args.plan.map((item) => ({
1021
+ text: item.step,
1022
+ completed: item.status === "completed"
1023
+ }));
1024
+ messages.push({
1025
+ id: `todo-${event.timestamp}-${eventIndex}`,
1026
+ type: "todo_list",
1027
+ items: todoItems,
1028
+ status: "completed",
1029
+ timestamp: event.timestamp
1030
+ });
1031
+ }
1032
+ }
1028
1033
  }
1029
- await setActiveOrganization(response.organizationId);
1030
- const selectedOrg = organizations.find((org2) => org2.id === response.organizationId);
1031
- console.log(import_chalk7.default.green(`
1032
- \u2713 Switched to organization: ${selectedOrg?.name}
1033
- `));
1034
- } catch (error) {
1035
- console.error(import_chalk7.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1036
- process.exit(1);
1037
- }
1034
+ });
1035
+ return messages;
1038
1036
  }
1039
1037
 
1040
- // src/commands/config.ts
1041
- var import_chalk8 = __toESM(require("chalk"));
1042
- async function configGetCommand(key) {
1043
- if (!isAuthenticated()) {
1044
- console.log(import_chalk8.default.red('Not logged in. Please run "replicas login" first.'));
1045
- process.exit(1);
1046
- }
1047
- try {
1048
- if (key === "ide") {
1049
- const ideCommand = getIdeCommand();
1050
- console.log(import_chalk8.default.green(`
1051
- IDE command: ${ideCommand}
1052
- `));
1053
- } else {
1054
- console.log(import_chalk8.default.red(`Unknown config key: ${key}`));
1055
- console.log(import_chalk8.default.gray("Available keys: ide"));
1056
- process.exit(1);
1057
- }
1058
- } catch (error) {
1059
- console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1060
- process.exit(1);
1061
- }
1062
- }
1063
- async function configSetCommand(key, value) {
1064
- if (!isAuthenticated()) {
1065
- console.log(import_chalk8.default.red('Not logged in. Please run "replicas login" first.'));
1066
- process.exit(1);
1067
- }
1038
+ // ../shared/src/display-message/parsers/claude-parser.ts
1039
+ function safeJsonParse2(str, fallback) {
1068
1040
  try {
1069
- if (key === "ide") {
1070
- setIdeCommand(value);
1071
- console.log(import_chalk8.default.green(`
1072
- \u2713 IDE command set to: ${value}
1073
- `));
1074
- } else {
1075
- console.log(import_chalk8.default.red(`Unknown config key: ${key}`));
1076
- console.log(import_chalk8.default.gray("Available keys: ide"));
1077
- process.exit(1);
1078
- }
1079
- } catch (error) {
1080
- console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1081
- process.exit(1);
1041
+ return JSON.parse(str);
1042
+ } catch {
1043
+ return fallback;
1082
1044
  }
1083
1045
  }
1084
- async function configListCommand() {
1085
- if (!isAuthenticated()) {
1086
- console.log(import_chalk8.default.red('Not logged in. Please run "replicas login" first.'));
1087
- process.exit(1);
1088
- }
1089
- try {
1090
- const config2 = readConfig();
1091
- if (!config2) {
1092
- console.log(import_chalk8.default.red("No config found. Please login first."));
1093
- process.exit(1);
1094
- }
1095
- console.log(import_chalk8.default.green("\nCurrent configuration:"));
1096
- console.log(import_chalk8.default.gray(` Organization ID: ${config2.organization_id || "(not set)"}`));
1097
- console.log(import_chalk8.default.gray(` IDE command: ${config2.ide_command || "code (default)"}`));
1098
- console.log();
1099
- } catch (error) {
1100
- console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1101
- process.exit(1);
1046
+ function extractToolResultContent(content) {
1047
+ if (!content) return "";
1048
+ if (typeof content === "string") return content;
1049
+ if (Array.isArray(content)) {
1050
+ return content.filter((item) => item.type === "text").map((item) => item.text || "").join("\n");
1102
1051
  }
1052
+ return "";
1103
1053
  }
1104
-
1105
- // src/commands/tools.ts
1106
- var import_chalk9 = __toESM(require("chalk"));
1107
- var import_figlet = __toESM(require("figlet"));
1108
- async function toolsCommand() {
1109
- const banner = import_figlet.default.textSync("Replicas Machine Image", {
1110
- horizontalLayout: "default",
1111
- verticalLayout: "default",
1112
- whitespaceBreak: true
1113
- });
1114
- console.log(import_chalk9.default.cyan(banner));
1115
- console.log();
1116
- console.log(import_chalk9.default.bold("RMI Version: v0.1.3"));
1117
- console.log();
1118
- console.log(
1119
- import_chalk9.default.gray(
1120
- "The following tooling and dependencies are provided by default on the Replicas Machine Image.\n"
1121
- )
1054
+ function parseClaudeEvents(events, parentToolUseId) {
1055
+ const messages = [];
1056
+ const filterValue = parentToolUseId !== void 0 ? parentToolUseId : null;
1057
+ const filteredEvents = events.filter(
1058
+ (e) => e.payload.parent_tool_use_id === filterValue
1122
1059
  );
1123
- const sections = [
1124
- {
1125
- title: "Python",
1126
- tools: ["Python 3.12.3", "pip 24", "Poetry 2.2.1", "uv 0.9.5"]
1127
- },
1128
- {
1129
- title: "Node.js",
1130
- tools: [
1131
- "Node.js 22.21.0",
1132
- "npm 10.9.4",
1133
- "nvm 0.40.3",
1134
- "Yarn 1.22.22",
1135
- "pnpm 10.18.3",
1136
- "ESLint 9.38.0",
1137
- "Prettier 3.6.2"
1138
- ]
1139
- },
1140
- {
1141
- title: "C/C++",
1142
- tools: ["gcc 13.3.0", "g++ 13.3.0", "make 4.3"]
1143
- },
1144
- {
1145
- title: "Perl",
1146
- tools: ["Perl 5.38.2"]
1147
- },
1148
- {
1149
- title: "AI Coding Assistants",
1150
- tools: ["Claude Code (@anthropic-ai/claude-code)", "OpenAI Codex (@openai/codex)"]
1151
- },
1152
- {
1153
- title: "Misc",
1154
- tools: [
1155
- "git 2.43.0",
1156
- "vim 9.1.697",
1157
- "nano 7.2",
1158
- "curl",
1159
- "wget",
1160
- "GitHub CLI (gh)",
1161
- "Docker Engine"
1162
- ]
1060
+ const toolCallMap = /* @__PURE__ */ new Map();
1061
+ filteredEvents.forEach((event) => {
1062
+ if (event.type === "claude-user") {
1063
+ const content = event.payload.message?.content || [];
1064
+ const toolResult = content.find((c) => c.type === "tool_result");
1065
+ if (toolResult && toolResult.tool_use_id) {
1066
+ return;
1067
+ }
1068
+ const textContent = content.filter((c) => c.type === "text").map((c) => c.text || "").join("\n");
1069
+ const images = content.filter((c) => c.type === "image" && c.source).map((c) => {
1070
+ const source = c.source;
1071
+ return {
1072
+ type: "image",
1073
+ mediaType: source.media_type || "image/png",
1074
+ data: source.data || ""
1075
+ };
1076
+ }).filter((img) => img.data);
1077
+ if (textContent || images.length > 0) {
1078
+ messages.push({
1079
+ id: `user-${event.timestamp}`,
1080
+ type: "user",
1081
+ content: textContent || (images.length > 0 ? `[${images.length} image${images.length > 1 ? "s" : ""} attached]` : ""),
1082
+ images: images.length > 0 ? images : void 0,
1083
+ timestamp: event.timestamp
1084
+ });
1085
+ }
1163
1086
  }
1164
- ];
1165
- sections.forEach((section) => {
1166
- console.log(import_chalk9.default.blue(section.title));
1167
- section.tools.forEach((tool) => {
1168
- console.log(` - ${tool}`);
1169
- });
1170
- console.log();
1171
- });
1172
- }
1173
-
1174
- // src/commands/codex-auth.ts
1175
- var import_chalk10 = __toESM(require("chalk"));
1176
-
1177
- // src/lib/codex-oauth.ts
1178
- var import_http2 = __toESM(require("http"));
1179
- var import_url2 = require("url");
1180
- var import_open2 = __toESM(require("open"));
1181
-
1182
- // src/lib/pkce.ts
1183
- var import_crypto = require("crypto");
1184
- function generatePkceCodes() {
1185
- const verifierBytes = (0, import_crypto.randomBytes)(32);
1186
- const codeVerifier = verifierBytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1187
- const hash = (0, import_crypto.createHash)("sha256");
1188
- hash.update(codeVerifier);
1189
- const codeChallenge = hash.digest("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1190
- return {
1191
- codeVerifier,
1192
- codeChallenge
1193
- };
1194
- }
1195
- function generateState2() {
1196
- return (0, import_crypto.randomBytes)(16).toString("hex");
1197
- }
1198
-
1199
- // src/lib/codex-oauth.ts
1200
- var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
1201
- var AUTHORIZATION_ENDPOINT = "https://auth.openai.com/oauth/authorize";
1202
- var TOKEN_ENDPOINT = "https://auth.openai.com/oauth/token";
1203
- var CALLBACK_PORT = 1455;
1204
- var CALLBACK_PATH = "/auth/callback";
1205
- var WEB_APP_URL2 = process.env.REPLICAS_WEB_URL || "https://replicas.dev";
1206
- function buildAuthorizationUrl(codeChallenge, state) {
1207
- const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
1208
- const params = new URLSearchParams({
1209
- response_type: "code",
1210
- client_id: CODEX_OAUTH_CLIENT_ID,
1211
- redirect_uri: redirectUri,
1212
- scope: "openid profile email offline_access",
1213
- code_challenge: codeChallenge,
1214
- code_challenge_method: "S256",
1215
- id_token_add_organizations: "true",
1216
- codex_cli_simplified_flow: "true",
1217
- state,
1218
- originator: "codex_cli_rs"
1219
- });
1220
- return `${AUTHORIZATION_ENDPOINT}?${params.toString()}`;
1221
- }
1222
- async function exchangeCodeForTokens(code, codeVerifier) {
1223
- const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
1224
- const params = new URLSearchParams({
1225
- grant_type: "authorization_code",
1226
- code,
1227
- redirect_uri: redirectUri,
1228
- client_id: CODEX_OAUTH_CLIENT_ID,
1229
- code_verifier: codeVerifier
1230
- });
1231
- const response = await fetch(TOKEN_ENDPOINT, {
1232
- method: "POST",
1233
- headers: {
1234
- "Content-Type": "application/x-www-form-urlencoded"
1235
- },
1236
- body: params.toString()
1237
- });
1238
- if (!response.ok) {
1239
- const errorText = await response.text();
1240
- throw new Error(`Failed to exchange code for tokens: ${response.status} ${errorText}`);
1241
- }
1242
- const data = await response.json();
1243
- return {
1244
- idToken: data.id_token,
1245
- accessToken: data.access_token,
1246
- refreshToken: data.refresh_token
1247
- };
1248
- }
1249
- function startCallbackServer(expectedState, codeVerifier) {
1250
- return new Promise((resolve, reject) => {
1251
- const server = import_http2.default.createServer(async (req, res) => {
1252
- try {
1253
- if (!req.url) {
1254
- res.writeHead(400);
1255
- res.end("Bad Request");
1256
- return;
1087
+ if (event.type === "claude-assistant") {
1088
+ const contentBlocks = event.payload.message?.content || [];
1089
+ contentBlocks.forEach((block) => {
1090
+ if (block.type === "text" && block.text) {
1091
+ messages.push({
1092
+ id: `agent-${event.timestamp}-${messages.length}`,
1093
+ type: "agent",
1094
+ content: block.text,
1095
+ timestamp: event.timestamp
1096
+ });
1257
1097
  }
1258
- const url = new import_url2.URL(req.url, `http://127.0.0.1:${CALLBACK_PORT}`);
1259
- if (url.pathname === CALLBACK_PATH) {
1260
- const code = url.searchParams.get("code");
1261
- const state = url.searchParams.get("state");
1262
- const error = url.searchParams.get("error");
1263
- const errorDescription = url.searchParams.get("error_description");
1264
- if (error) {
1265
- const errorMessage = encodeURIComponent(`${error}: ${errorDescription || "Unknown error"}`);
1266
- res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1267
- res.end();
1268
- server.close();
1269
- reject(new Error(`OAuth error: ${error} - ${errorDescription}`));
1270
- return;
1271
- }
1272
- if (state !== expectedState) {
1273
- const errorMessage = encodeURIComponent("State parameter mismatch. Possible CSRF attack.");
1274
- res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1275
- res.end();
1276
- server.close();
1277
- reject(new Error("State mismatch - possible CSRF attack"));
1278
- return;
1279
- }
1280
- if (!code) {
1281
- const errorMessage = encodeURIComponent("No authorization code received.");
1282
- res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1283
- res.end();
1284
- server.close();
1285
- reject(new Error("No authorization code received"));
1286
- return;
1287
- }
1288
- try {
1289
- const tokens = await exchangeCodeForTokens(code, codeVerifier);
1290
- res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/success` });
1291
- res.end();
1292
- server.close();
1293
- resolve(tokens);
1294
- } catch (tokenError) {
1295
- const errorMessage = encodeURIComponent(tokenError instanceof Error ? tokenError.message : "Unknown error");
1296
- res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1297
- res.end();
1298
- server.close();
1299
- reject(tokenError);
1098
+ if (block.type === "thinking" && block.text) {
1099
+ messages.push({
1100
+ id: `reasoning-${event.timestamp}-${messages.length}`,
1101
+ type: "reasoning",
1102
+ content: block.text,
1103
+ status: "completed",
1104
+ timestamp: event.timestamp
1105
+ });
1106
+ }
1107
+ if (block.type === "tool_use" && block.id) {
1108
+ const toolName = block.name || "unknown";
1109
+ const toolInput = block.input || {};
1110
+ const toolUseId = block.id;
1111
+ toolCallMap.set(toolUseId, {
1112
+ messageIndex: messages.length,
1113
+ toolName,
1114
+ input: toolInput
1115
+ });
1116
+ if (toolName === "Task") {
1117
+ const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
1118
+ const nestedEvents = events.filter((e) => e.payload.parent_tool_use_id === toolUseId).map((e) => ({
1119
+ timestamp: e.timestamp,
1120
+ type: e.type,
1121
+ payload: e.payload
1122
+ }));
1123
+ messages.push({
1124
+ id: `subagent-${event.timestamp}-${messages.length}`,
1125
+ type: "subagent",
1126
+ toolUseId,
1127
+ description: inputObj.description || "Subagent Task",
1128
+ prompt: inputObj.prompt || "",
1129
+ subagentType: inputObj.subagent_type || "general",
1130
+ model: inputObj.model,
1131
+ status: "in_progress",
1132
+ nestedEvents,
1133
+ timestamp: event.timestamp
1134
+ });
1135
+ } else if (toolName === "Bash" || toolName === "bash" || toolName === "shell") {
1136
+ const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
1137
+ const command = inputObj.command || "";
1138
+ messages.push({
1139
+ id: `command-${event.timestamp}-${messages.length}`,
1140
+ type: "command",
1141
+ command,
1142
+ output: "",
1143
+ status: "in_progress",
1144
+ timestamp: event.timestamp
1145
+ });
1146
+ } else if (toolName === "Write" || toolName === "Edit") {
1147
+ const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
1148
+ const filePath = inputObj.file_path || "";
1149
+ const action = toolName === "Write" ? "add" : "update";
1150
+ messages.push({
1151
+ id: `file-${event.timestamp}-${messages.length}`,
1152
+ type: "file_change",
1153
+ changes: [{
1154
+ path: filePath,
1155
+ kind: action
1156
+ }],
1157
+ status: "in_progress",
1158
+ timestamp: event.timestamp
1159
+ });
1160
+ } else if (toolName === "TodoWrite") {
1161
+ const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
1162
+ const todos = inputObj.todos || [];
1163
+ const todoItems = todos.map((todo) => ({
1164
+ text: todo.content,
1165
+ completed: todo.status === "completed"
1166
+ }));
1167
+ if (todoItems.length > 0) {
1168
+ messages.push({
1169
+ id: `todo-${event.timestamp}-${messages.length}`,
1170
+ type: "todo_list",
1171
+ items: todoItems,
1172
+ status: "completed",
1173
+ timestamp: event.timestamp
1174
+ });
1175
+ }
1176
+ } else if (toolName === "WebSearch") {
1177
+ const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
1178
+ const query = inputObj.query || "";
1179
+ messages.push({
1180
+ id: `search-${event.timestamp}-${messages.length}`,
1181
+ type: "web_search",
1182
+ query,
1183
+ status: "in_progress",
1184
+ timestamp: event.timestamp
1185
+ });
1186
+ } else {
1187
+ messages.push({
1188
+ id: `toolcall-${event.timestamp}-${messages.length}`,
1189
+ type: "tool_call",
1190
+ server: "claude",
1191
+ tool: toolName,
1192
+ input: toolInput,
1193
+ status: "in_progress",
1194
+ timestamp: event.timestamp
1195
+ });
1300
1196
  }
1301
- } else {
1302
- res.writeHead(404);
1303
- res.end("Not Found");
1304
1197
  }
1305
- } catch (serverError) {
1306
- res.writeHead(500);
1307
- res.end("Internal Server Error");
1308
- server.close();
1309
- reject(serverError);
1198
+ });
1199
+ }
1200
+ if (event.type === "claude-result") {
1201
+ const payload = event.payload;
1202
+ if (payload.is_error || payload.subtype !== "success") {
1203
+ const errorList = payload.errors || [];
1204
+ const errorMessage = errorList.length > 0 ? errorList.join("\n") : "Claude session encountered an unexpected error.";
1205
+ messages.push({
1206
+ id: `error-${event.timestamp}`,
1207
+ type: "error",
1208
+ message: errorMessage,
1209
+ timestamp: event.timestamp
1210
+ });
1310
1211
  }
1311
- });
1312
- server.on("error", (err) => {
1313
- if (err.code === "EADDRINUSE") {
1314
- reject(new Error(`Port ${CALLBACK_PORT} is already in use. Please close any other OAuth flows and try again.`));
1315
- } else {
1316
- reject(err);
1212
+ }
1213
+ });
1214
+ filteredEvents.forEach((event) => {
1215
+ if (event.type === "claude-user") {
1216
+ const content = event.payload.message?.content || [];
1217
+ const toolResult = content.find((c) => c.type === "tool_result");
1218
+ if (toolResult && toolResult.tool_use_id) {
1219
+ const toolInfo = toolCallMap.get(toolResult.tool_use_id);
1220
+ if (!toolInfo) return;
1221
+ const resultContent = extractToolResultContent(toolResult.content);
1222
+ const isError = toolResult.is_error || false;
1223
+ const status = isError ? "failed" : "completed";
1224
+ const message = messages[toolInfo.messageIndex];
1225
+ if (!message) return;
1226
+ if (message.type === "command") {
1227
+ message.output = resultContent;
1228
+ message.status = status;
1229
+ const exitCodeMatch = resultContent.match(/exit code:?\s*(\d+)/i);
1230
+ if (exitCodeMatch) {
1231
+ message.exitCode = parseInt(exitCodeMatch[1], 10);
1232
+ } else {
1233
+ message.exitCode = isError ? 1 : 0;
1234
+ }
1235
+ } else if (message.type === "file_change") {
1236
+ message.status = status;
1237
+ } else if (message.type === "web_search") {
1238
+ message.status = status;
1239
+ } else if (message.type === "tool_call") {
1240
+ message.output = resultContent;
1241
+ message.status = status;
1242
+ } else if (message.type === "subagent") {
1243
+ message.output = resultContent;
1244
+ message.status = status;
1245
+ }
1317
1246
  }
1318
- });
1319
- server.listen(CALLBACK_PORT, "127.0.0.1");
1247
+ }
1320
1248
  });
1249
+ return messages;
1321
1250
  }
1322
- async function runCodexOAuthFlow() {
1323
- const pkce = generatePkceCodes();
1324
- const state = generateState2();
1325
- const authUrl = buildAuthorizationUrl(pkce.codeChallenge, state);
1326
- const tokensPromise = startCallbackServer(state, pkce.codeVerifier);
1327
- await new Promise((resolve) => setTimeout(resolve, 500));
1328
- console.log("If the browser does not open automatically, visit:");
1329
- console.log(authUrl);
1330
- console.log();
1331
- try {
1332
- await (0, import_open2.default)(authUrl);
1333
- } catch (openError) {
1334
- console.warn("Failed to open browser automatically. Please open the URL manually.");
1251
+
1252
+ // ../shared/src/display-message/parsers/index.ts
1253
+ function parseAgentEvents(events, agentType) {
1254
+ if (agentType === "codex") {
1255
+ return parseCodexEvents(events);
1256
+ } else if (agentType === "claude") {
1257
+ return parseClaudeEvents(events);
1335
1258
  }
1336
- console.log("Waiting for authentication...");
1337
- const tokens = await tokensPromise;
1338
- console.log("\u2713 Successfully obtained Codex credentials");
1339
- return tokens;
1259
+ return [];
1340
1260
  }
1341
1261
 
1342
- // src/commands/codex-auth.ts
1343
- async function codexAuthCommand(options = {}) {
1344
- const isUserScoped = options.user === true;
1345
- const scopeLabel = isUserScoped ? "personal" : "organization";
1346
- console.log(import_chalk10.default.cyan(`Authenticating with Codex (${scopeLabel})...
1347
- `));
1348
- try {
1349
- const tokens = await runCodexOAuthFlow();
1350
- console.log(import_chalk10.default.gray("\nUploading credentials to Replicas..."));
1351
- const endpoint = isUserScoped ? "/v1/codex/user/credentials" : "/v1/codex/credentials";
1352
- const response = await orgAuthenticatedFetch(
1353
- endpoint,
1354
- {
1355
- method: "POST",
1356
- body: {
1357
- access_token: tokens.accessToken,
1358
- refresh_token: tokens.refreshToken,
1359
- id_token: tokens.idToken
1360
- }
1361
- }
1362
- );
1363
- if (response.success) {
1364
- console.log(import_chalk10.default.green("\n\u2713 Codex authentication complete!"));
1365
- if (response.email) {
1366
- console.log(import_chalk10.default.gray(` Account: ${response.email}`));
1262
+ // src/lib/ssh-config.ts
1263
+ var import_fs3 = __toESM(require("fs"));
1264
+ var import_path4 = __toESM(require("path"));
1265
+ var import_os2 = __toESM(require("os"));
1266
+ var SSH_CONFIG_PATH = import_path4.default.join(import_os2.default.homedir(), ".ssh", "config");
1267
+ var REPLICAS_MARKER_START = "# === REPLICAS CLI MANAGED ENTRY START ===";
1268
+ var REPLICAS_MARKER_END = "# === REPLICAS CLI MANAGED ENTRY END ===";
1269
+ function ensureSSHDir() {
1270
+ const sshDir = import_path4.default.join(import_os2.default.homedir(), ".ssh");
1271
+ if (!import_fs3.default.existsSync(sshDir)) {
1272
+ import_fs3.default.mkdirSync(sshDir, { recursive: true, mode: 448 });
1273
+ }
1274
+ }
1275
+ function readSSHConfig() {
1276
+ ensureSSHDir();
1277
+ if (!import_fs3.default.existsSync(SSH_CONFIG_PATH)) {
1278
+ return "";
1279
+ }
1280
+ return import_fs3.default.readFileSync(SSH_CONFIG_PATH, "utf-8");
1281
+ }
1282
+ function writeSSHConfig(content) {
1283
+ ensureSSHDir();
1284
+ import_fs3.default.writeFileSync(SSH_CONFIG_PATH, content, { mode: 384 });
1285
+ }
1286
+ function generateConfigBlock(entry) {
1287
+ const lines = [
1288
+ REPLICAS_MARKER_START,
1289
+ `Host ${entry.host}`,
1290
+ ` HostName ${entry.hostname}`,
1291
+ ` User ${entry.user}`,
1292
+ ` StrictHostKeyChecking no`,
1293
+ ` UserKnownHostsFile /dev/null`
1294
+ ];
1295
+ if (entry.portForwards && entry.portForwards.size > 0) {
1296
+ for (const [localPort, remotePort] of entry.portForwards) {
1297
+ lines.push(` LocalForward ${localPort} localhost:${remotePort}`);
1298
+ }
1299
+ }
1300
+ lines.push(REPLICAS_MARKER_END);
1301
+ return lines.join("\n");
1302
+ }
1303
+ function addOrUpdateSSHConfigEntry(entry) {
1304
+ let config2 = readSSHConfig();
1305
+ const lines = config2.split("\n");
1306
+ const result = [];
1307
+ let inTargetBlock = false;
1308
+ for (const line of lines) {
1309
+ if (line.trim() === REPLICAS_MARKER_START) {
1310
+ const nextLines = lines.slice(lines.indexOf(line) + 1, lines.indexOf(line) + 3);
1311
+ const hostLine = nextLines.find((l) => l.trim().startsWith("Host "));
1312
+ if (hostLine && hostLine.includes(entry.host)) {
1313
+ inTargetBlock = true;
1314
+ continue;
1367
1315
  }
1368
- if (response.planType) {
1369
- console.log(import_chalk10.default.gray(` Plan: ${response.planType}`));
1316
+ result.push(line);
1317
+ continue;
1318
+ }
1319
+ if (line.trim() === REPLICAS_MARKER_END) {
1320
+ if (inTargetBlock) {
1321
+ inTargetBlock = false;
1322
+ continue;
1370
1323
  }
1371
- if (isUserScoped) {
1372
- console.log(import_chalk10.default.gray("\nYour personal Codex credentials have been saved.\n"));
1373
- } else {
1374
- console.log(import_chalk10.default.gray("\nYou can now use Codex in your workspaces.\n"));
1324
+ result.push(line);
1325
+ continue;
1326
+ }
1327
+ if (inTargetBlock) {
1328
+ continue;
1329
+ }
1330
+ result.push(line);
1331
+ }
1332
+ config2 = result.join("\n");
1333
+ const newEntry = generateConfigBlock(entry);
1334
+ let finalConfig = config2.trim();
1335
+ if (finalConfig.length > 0) {
1336
+ finalConfig += "\n\n";
1337
+ }
1338
+ finalConfig += newEntry + "\n";
1339
+ writeSSHConfig(finalConfig);
1340
+ }
1341
+ function getWorkspaceHostAlias(workspaceName) {
1342
+ return `replicas-${workspaceName.toLowerCase().replace(/[^a-z0-9-]/g, "-")}`;
1343
+ }
1344
+
1345
+ // src/commands/code.ts
1346
+ async function codeCommand(workspaceName, options) {
1347
+ if (!isAuthenticated()) {
1348
+ console.log(import_chalk6.default.red('Not logged in. Please run "replicas login" first.'));
1349
+ process.exit(1);
1350
+ }
1351
+ try {
1352
+ const { workspace, sshToken, sshHost, portMappings, repoName } = await prepareWorkspaceConnection(
1353
+ workspaceName,
1354
+ options
1355
+ );
1356
+ const hostAlias = getWorkspaceHostAlias(workspace.name);
1357
+ console.log(import_chalk6.default.blue("\nConfiguring SSH connection..."));
1358
+ addOrUpdateSSHConfigEntry({
1359
+ host: hostAlias,
1360
+ hostname: sshHost,
1361
+ user: sshToken,
1362
+ portForwards: portMappings
1363
+ });
1364
+ console.log(import_chalk6.default.green(`\u2713 SSH config entry created: ${hostAlias}`));
1365
+ const remotePath = repoName ? `${SANDBOX_PATHS.WORKSPACES_DIR}/${repoName}` : SANDBOX_PATHS.HOME_DIR;
1366
+ const ideCommand = getIdeCommand();
1367
+ const fullCommand = `${ideCommand} --remote ssh-remote+${hostAlias} ${remotePath}`;
1368
+ console.log(import_chalk6.default.blue(`
1369
+ Opening ${ideCommand} for workspace ${workspace.name}...`));
1370
+ console.log(import_chalk6.default.gray(`Command: ${fullCommand}`));
1371
+ if (portMappings.size > 0) {
1372
+ console.log(import_chalk6.default.cyan("\nPort forwarding configured:"));
1373
+ for (const [remotePort, localPort] of portMappings) {
1374
+ console.log(import_chalk6.default.cyan(` \u2022 localhost:${localPort} \u2192 workspace:${remotePort}`));
1375
1375
  }
1376
- } else {
1377
- throw new Error(response.error || "Failed to upload Codex credentials to Replicas");
1378
1376
  }
1377
+ console.log(import_chalk6.default.yellow(`
1378
+ Note: SSH tokens expire after 3 hours. Run this command again to refresh.`));
1379
+ const ide = (0, import_child_process4.spawn)(ideCommand, [
1380
+ "--remote",
1381
+ `ssh-remote+${hostAlias}`,
1382
+ remotePath
1383
+ ], {
1384
+ stdio: "inherit",
1385
+ detached: false
1386
+ });
1387
+ ide.on("error", (error) => {
1388
+ console.error(import_chalk6.default.red(`
1389
+ Failed to launch ${ideCommand}: ${error.message}`));
1390
+ console.log(import_chalk6.default.yellow(`
1391
+ Make sure ${ideCommand} is installed and available in your PATH.`));
1392
+ console.log(import_chalk6.default.gray(`You can configure a different IDE with: replicas config set ide <command>`));
1393
+ process.exit(1);
1394
+ });
1395
+ ide.on("close", (code) => {
1396
+ if (code === 0) {
1397
+ console.log(import_chalk6.default.green(`
1398
+ \u2713 ${ideCommand} closed successfully.
1399
+ `));
1400
+ }
1401
+ });
1379
1402
  } catch (error) {
1380
- console.log(import_chalk10.default.red("\n\u2717 Error:"), error instanceof Error ? error.message : error);
1403
+ console.error(import_chalk6.default.red(`
1404
+ Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1381
1405
  process.exit(1);
1382
1406
  }
1383
1407
  }
1384
1408
 
1385
- // src/commands/claude-auth.ts
1386
- var import_chalk12 = __toESM(require("chalk"));
1387
-
1388
- // src/lib/claude-oauth.ts
1389
- var import_open3 = __toESM(require("open"));
1390
- var import_readline = __toESM(require("readline"));
1391
- var import_chalk11 = __toESM(require("chalk"));
1392
- var DEFAULT_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
1393
- var CLAUDE_CLIENT_ID = process.env.CLAUDE_OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID;
1394
- var AUTHORIZATION_ENDPOINT2 = "https://claude.ai/oauth/authorize";
1395
- var TOKEN_ENDPOINT2 = "https://console.anthropic.com/v1/oauth/token";
1396
- var REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
1397
- var SCOPES = "org:create_api_key user:profile user:inference";
1398
- function buildAuthorizationUrl2(codeChallenge, state) {
1399
- const params = new URLSearchParams({
1400
- code: "true",
1401
- client_id: CLAUDE_CLIENT_ID,
1402
- response_type: "code",
1403
- redirect_uri: REDIRECT_URI,
1404
- scope: SCOPES,
1405
- code_challenge: codeChallenge,
1406
- code_challenge_method: "S256",
1407
- state
1408
- });
1409
- return `${AUTHORIZATION_ENDPOINT2}?${params.toString()}`;
1410
- }
1411
- async function promptForAuthorizationCode(instruction) {
1412
- const rl = import_readline.default.createInterface({
1413
- input: process.stdin,
1414
- output: process.stdout
1415
- });
1416
- return new Promise((resolve) => {
1417
- rl.question(instruction, (answer) => {
1418
- rl.close();
1419
- resolve(answer.trim());
1420
- });
1421
- });
1422
- }
1423
- function parseUserInput(input) {
1424
- if (!input.includes("#") && !input.includes("code=")) {
1425
- return { code: input };
1409
+ // src/commands/org.ts
1410
+ var import_chalk7 = __toESM(require("chalk"));
1411
+ var import_prompts2 = __toESM(require("prompts"));
1412
+ async function orgCommand() {
1413
+ if (!isAuthenticated()) {
1414
+ console.log(import_chalk7.default.red('Not logged in. Please run "replicas login" first.'));
1415
+ process.exit(1);
1426
1416
  }
1427
- if (input.startsWith("http://") || input.startsWith("https://")) {
1428
- try {
1429
- const url = new URL(input);
1430
- const fragment = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash;
1431
- const params = new URLSearchParams(fragment);
1432
- const code2 = params.get("code") ?? void 0;
1433
- const state2 = params.get("state") ?? void 0;
1434
- if (code2) {
1435
- return { code: code2, state: state2 };
1436
- }
1437
- } catch {
1417
+ try {
1418
+ const currentOrgId = getOrganizationId();
1419
+ const organizations = await fetchOrganizations();
1420
+ if (!currentOrgId) {
1421
+ console.log(import_chalk7.default.yellow("No organization selected."));
1422
+ console.log(import_chalk7.default.gray('Run "replicas org switch" to select an organization.\n'));
1423
+ return;
1438
1424
  }
1425
+ const currentOrg = organizations.find((org2) => org2.id === currentOrgId);
1426
+ if (!currentOrg) {
1427
+ console.log(import_chalk7.default.yellow(`Current organization ID (${currentOrgId}) not found.`));
1428
+ console.log(import_chalk7.default.gray('Run "replicas org switch" to select a new organization.\n'));
1429
+ return;
1430
+ }
1431
+ console.log(import_chalk7.default.green("\nCurrent organization:"));
1432
+ console.log(import_chalk7.default.gray(` Name: ${currentOrg.name}`));
1433
+ console.log(import_chalk7.default.gray(` ID: ${currentOrg.id}
1434
+ `));
1435
+ } catch (error) {
1436
+ console.error(import_chalk7.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1437
+ process.exit(1);
1439
1438
  }
1440
- const [code, state] = input.split("#");
1441
- return { code, state };
1442
1439
  }
1443
- async function exchangeCodeForTokens2(code, state, expectedState, codeVerifier) {
1444
- if (state && state !== expectedState) {
1445
- throw new Error("State mismatch detected. Please restart the authentication flow.");
1446
- }
1447
- const payload = {
1448
- code,
1449
- state: state ?? expectedState,
1450
- grant_type: "authorization_code",
1451
- client_id: CLAUDE_CLIENT_ID,
1452
- redirect_uri: REDIRECT_URI,
1453
- code_verifier: codeVerifier
1454
- };
1455
- const response = await fetch(TOKEN_ENDPOINT2, {
1456
- method: "POST",
1457
- headers: {
1458
- "Content-Type": "application/json"
1459
- },
1460
- body: JSON.stringify(payload)
1461
- });
1462
- if (!response.ok) {
1463
- const text = await response.text();
1464
- throw new Error(`Failed to exchange authorization code: ${response.status} ${text}`);
1440
+ async function orgSwitchCommand() {
1441
+ if (!isAuthenticated()) {
1442
+ console.log(import_chalk7.default.red('Not logged in. Please run "replicas login" first.'));
1443
+ process.exit(1);
1465
1444
  }
1466
- const data = await response.json();
1467
- const now = Date.now();
1468
- const expiresAt = new Date(now + data.expires_in * 1e3).toISOString();
1469
- const refreshTokenExpiresAt = data.refresh_token_expires_in ? new Date(now + data.refresh_token_expires_in * 1e3).toISOString() : void 0;
1470
- return {
1471
- accessToken: data.access_token,
1472
- refreshToken: data.refresh_token,
1473
- tokenType: data.token_type,
1474
- scope: data.scope,
1475
- expiresAt,
1476
- refreshTokenExpiresAt
1477
- };
1478
- }
1479
- async function runClaudeOAuthFlow() {
1480
- const pkce = generatePkceCodes();
1481
- const state = generateState2();
1482
- const authUrl = buildAuthorizationUrl2(pkce.codeChallenge, state);
1483
- console.log(import_chalk11.default.gray("We will open your browser so you can authenticate with Claude Code."));
1484
- console.log(import_chalk11.default.gray("If the browser does not open automatically, use the URL below:\n"));
1485
- console.log(authUrl);
1486
- console.log();
1487
1445
  try {
1488
- await (0, import_open3.default)(authUrl);
1489
- } catch {
1490
- console.warn(import_chalk11.default.yellow("Failed to open browser automatically. Please copy and open the URL manually."));
1491
- }
1492
- console.log(import_chalk11.default.cyan("After completing authentication, copy the code shown on the success page."));
1493
- console.log(import_chalk11.default.cyan("You can paste either the full URL, or a value formatted as CODE#STATE.\n"));
1494
- const userInput = await promptForAuthorizationCode("Paste the authorization code (or URL) here: ");
1495
- if (!userInput) {
1496
- throw new Error("No authorization code provided.");
1497
- }
1498
- const { code, state: returnedState } = parseUserInput(userInput);
1499
- if (!code) {
1500
- throw new Error("Unable to parse authorization code. Please try again.");
1446
+ const organizations = await fetchOrganizations();
1447
+ if (organizations.length === 0) {
1448
+ console.log(import_chalk7.default.yellow("You are not a member of any organization."));
1449
+ console.log(import_chalk7.default.gray("Please contact support.\n"));
1450
+ return;
1451
+ }
1452
+ if (organizations.length === 1) {
1453
+ await setActiveOrganization(organizations[0].id);
1454
+ console.log(import_chalk7.default.green(`
1455
+ \u2713 Organization set to: ${organizations[0].name}
1456
+ `));
1457
+ return;
1458
+ }
1459
+ const currentOrgId = getOrganizationId();
1460
+ const response = await (0, import_prompts2.default)({
1461
+ type: "select",
1462
+ name: "organizationId",
1463
+ message: "Select an organization:",
1464
+ choices: organizations.map((org2) => ({
1465
+ title: `${org2.name}${org2.id === currentOrgId ? " (current)" : ""}`,
1466
+ value: org2.id,
1467
+ description: org2.id
1468
+ })),
1469
+ initial: organizations.findIndex((org2) => org2.id === currentOrgId)
1470
+ });
1471
+ if (!response.organizationId) {
1472
+ console.log(import_chalk7.default.yellow("\nCancelled."));
1473
+ return;
1474
+ }
1475
+ await setActiveOrganization(response.organizationId);
1476
+ const selectedOrg = organizations.find((org2) => org2.id === response.organizationId);
1477
+ console.log(import_chalk7.default.green(`
1478
+ \u2713 Switched to organization: ${selectedOrg?.name}
1479
+ `));
1480
+ } catch (error) {
1481
+ console.error(import_chalk7.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1482
+ process.exit(1);
1501
1483
  }
1502
- console.log(import_chalk11.default.gray("\nExchanging authorization code for tokens..."));
1503
- const tokens = await exchangeCodeForTokens2(code, returnedState, state, pkce.codeVerifier);
1504
- console.log(import_chalk11.default.green("\u2713 Successfully obtained Claude credentials"));
1505
- return tokens;
1506
1484
  }
1507
1485
 
1508
- // src/commands/claude-auth.ts
1509
- async function claudeAuthCommand(options = {}) {
1510
- const isUserScoped = options.user === true;
1511
- const scopeLabel = isUserScoped ? "personal" : "organization";
1512
- console.log(import_chalk12.default.cyan(`Authenticating with Claude Code (${scopeLabel})...
1513
- `));
1486
+ // src/commands/config.ts
1487
+ var import_chalk8 = __toESM(require("chalk"));
1488
+ async function configGetCommand(key) {
1489
+ if (!isAuthenticated()) {
1490
+ console.log(import_chalk8.default.red('Not logged in. Please run "replicas login" first.'));
1491
+ process.exit(1);
1492
+ }
1514
1493
  try {
1515
- const tokens = await runClaudeOAuthFlow();
1516
- console.log(import_chalk12.default.gray("\nUploading credentials to Replicas..."));
1517
- const endpoint = isUserScoped ? "/v1/claude/user/credentials" : "/v1/claude/credentials";
1518
- const response = await orgAuthenticatedFetch(
1519
- endpoint,
1520
- {
1521
- method: "POST",
1522
- body: {
1523
- access_token: tokens.accessToken,
1524
- refresh_token: tokens.refreshToken,
1525
- token_type: tokens.tokenType,
1526
- scope: tokens.scope,
1527
- expires_at: tokens.expiresAt,
1528
- refresh_token_expires_at: tokens.refreshTokenExpiresAt
1529
- }
1530
- }
1531
- );
1532
- if (!response.success) {
1533
- throw new Error(response.error || "Failed to upload Claude credentials to Replicas");
1534
- }
1535
- console.log(import_chalk12.default.green("\n\u2713 Claude authentication complete!"));
1536
- if (response.email) {
1537
- console.log(import_chalk12.default.gray(` Account: ${response.email}`));
1538
- }
1539
- if (response.planType) {
1540
- console.log(import_chalk12.default.gray(` Plan: ${response.planType}`));
1494
+ if (key === "ide") {
1495
+ const ideCommand = getIdeCommand();
1496
+ console.log(import_chalk8.default.green(`
1497
+ IDE command: ${ideCommand}
1498
+ `));
1499
+ } else {
1500
+ console.log(import_chalk8.default.red(`Unknown config key: ${key}`));
1501
+ console.log(import_chalk8.default.gray("Available keys: ide"));
1502
+ process.exit(1);
1541
1503
  }
1542
- if (isUserScoped) {
1543
- console.log(import_chalk12.default.gray("\nYour personal Claude credentials have been saved.\n"));
1504
+ } catch (error) {
1505
+ console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1506
+ process.exit(1);
1507
+ }
1508
+ }
1509
+ async function configSetCommand(key, value) {
1510
+ if (!isAuthenticated()) {
1511
+ console.log(import_chalk8.default.red('Not logged in. Please run "replicas login" first.'));
1512
+ process.exit(1);
1513
+ }
1514
+ try {
1515
+ if (key === "ide") {
1516
+ setIdeCommand(value);
1517
+ console.log(import_chalk8.default.green(`
1518
+ \u2713 IDE command set to: ${value}
1519
+ `));
1544
1520
  } else {
1545
- console.log(import_chalk12.default.gray("\nYou can now use Claude Code in your workspaces.\n"));
1521
+ console.log(import_chalk8.default.red(`Unknown config key: ${key}`));
1522
+ console.log(import_chalk8.default.gray("Available keys: ide"));
1523
+ process.exit(1);
1546
1524
  }
1547
1525
  } catch (error) {
1548
- console.log(import_chalk12.default.red("\n\u2717 Error:"), error instanceof Error ? error.message : error);
1526
+ console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1549
1527
  process.exit(1);
1550
1528
  }
1551
1529
  }
1552
-
1553
- // src/commands/init.ts
1554
- var import_chalk13 = __toESM(require("chalk"));
1555
- var import_fs4 = __toESM(require("fs"));
1556
- var import_path5 = __toESM(require("path"));
1557
- var REPLICAS_CONFIG_FILENAME = "replicas.json";
1558
- function getDefaultConfig() {
1559
- return {
1560
- copy: [],
1561
- ports: [],
1562
- systemPrompt: "",
1563
- startHook: {
1564
- timeout: 3e5,
1565
- commands: []
1566
- }
1567
- };
1568
- }
1569
- function initCommand(options) {
1570
- const configPath = import_path5.default.join(process.cwd(), REPLICAS_CONFIG_FILENAME);
1571
- if (import_fs4.default.existsSync(configPath) && !options.force) {
1572
- console.log(
1573
- import_chalk13.default.yellow(
1574
- `${REPLICAS_CONFIG_FILENAME} already exists in this directory.`
1575
- )
1576
- );
1577
- console.log(import_chalk13.default.gray("Use --force to overwrite the existing file."));
1578
- return;
1530
+ async function configListCommand() {
1531
+ if (!isAuthenticated()) {
1532
+ console.log(import_chalk8.default.red('Not logged in. Please run "replicas login" first.'));
1533
+ process.exit(1);
1579
1534
  }
1580
- const defaultConfig = getDefaultConfig();
1581
- const configContent = JSON.stringify(defaultConfig, null, 2);
1582
1535
  try {
1583
- import_fs4.default.writeFileSync(configPath, configContent + "\n", "utf-8");
1584
- console.log(import_chalk13.default.green(`\u2713 Created ${REPLICAS_CONFIG_FILENAME}`));
1585
- console.log("");
1586
- console.log(import_chalk13.default.gray("Configuration options:"));
1587
- console.log(
1588
- import_chalk13.default.gray(' copy - Files to sync to the workspace (e.g., [".env"])')
1589
- );
1590
- console.log(
1591
- import_chalk13.default.gray(" ports - Ports to forward from the workspace (e.g., [3000, 8080])")
1592
- );
1593
- console.log(
1594
- import_chalk13.default.gray(" systemPrompt - Custom instructions for AI coding assistants")
1595
- );
1596
- console.log(
1597
- import_chalk13.default.gray(" startHook - Commands to run on workspace startup")
1598
- );
1536
+ const config2 = readConfig();
1537
+ if (!config2) {
1538
+ console.log(import_chalk8.default.red("No config found. Please login first."));
1539
+ process.exit(1);
1540
+ }
1541
+ console.log(import_chalk8.default.green("\nCurrent configuration:"));
1542
+ console.log(import_chalk8.default.gray(` Organization ID: ${config2.organization_id || "(not set)"}`));
1543
+ console.log(import_chalk8.default.gray(` IDE command: ${config2.ide_command || "code (default)"}`));
1544
+ console.log();
1599
1545
  } catch (error) {
1600
- throw new Error(
1601
- `Failed to create ${REPLICAS_CONFIG_FILENAME}: ${error instanceof Error ? error.message : "Unknown error"}`
1602
- );
1546
+ console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
1547
+ process.exit(1);
1603
1548
  }
1604
1549
  }
1605
1550
 
1606
- // src/lib/version-check.ts
1607
- var import_chalk14 = __toESM(require("chalk"));
1608
- var MONOLITH_URL2 = process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
1609
- var VERSION_CHECK_TIMEOUT = 2e3;
1610
- function compareVersions(v1, v2) {
1611
- const parts1 = v1.split(".").map(Number);
1612
- const parts2 = v2.split(".").map(Number);
1613
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
1614
- const num1 = parts1[i] || 0;
1615
- const num2 = parts2[i] || 0;
1616
- if (num1 > num2) return 1;
1617
- if (num1 < num2) return -1;
1618
- }
1619
- return 0;
1620
- }
1621
- async function checkForUpdates(currentVersion) {
1622
- try {
1623
- const controller = new AbortController();
1624
- const timeoutId = setTimeout(() => controller.abort(), VERSION_CHECK_TIMEOUT);
1625
- const response = await fetch(`${MONOLITH_URL2}/v1/cli/version`, {
1626
- signal: controller.signal
1627
- });
1628
- clearTimeout(timeoutId);
1629
- if (!response.ok) return;
1630
- const data = await response.json();
1631
- const latestVersion = data.version;
1632
- if (compareVersions(latestVersion, currentVersion) > 0) {
1633
- console.error(import_chalk14.default.gray(`
1634
- Update available: ${currentVersion} \u2192 ${latestVersion}`));
1635
- console.error(import_chalk14.default.gray(`Run: npm i -g replicas-cli@latest
1636
- `));
1551
+ // src/commands/tools.ts
1552
+ var import_chalk9 = __toESM(require("chalk"));
1553
+ var import_figlet = __toESM(require("figlet"));
1554
+ async function toolsCommand() {
1555
+ const banner = import_figlet.default.textSync("Replicas Machine Image", {
1556
+ horizontalLayout: "default",
1557
+ verticalLayout: "default",
1558
+ whitespaceBreak: true
1559
+ });
1560
+ console.log(import_chalk9.default.cyan(banner));
1561
+ console.log();
1562
+ console.log(import_chalk9.default.bold("RMI Version: v0.1.3"));
1563
+ console.log();
1564
+ console.log(
1565
+ import_chalk9.default.gray(
1566
+ "The following tooling and dependencies are provided by default on the Replicas Machine Image.\n"
1567
+ )
1568
+ );
1569
+ const sections = [
1570
+ {
1571
+ title: "Python",
1572
+ tools: ["Python 3.12.3", "pip 24", "Poetry 2.2.1", "uv 0.9.5"]
1573
+ },
1574
+ {
1575
+ title: "Node.js",
1576
+ tools: [
1577
+ "Node.js 22.21.0",
1578
+ "npm 10.9.4",
1579
+ "nvm 0.40.3",
1580
+ "Yarn 1.22.22",
1581
+ "pnpm 10.18.3",
1582
+ "ESLint 9.38.0",
1583
+ "Prettier 3.6.2"
1584
+ ]
1585
+ },
1586
+ {
1587
+ title: "C/C++",
1588
+ tools: ["gcc 13.3.0", "g++ 13.3.0", "make 4.3"]
1589
+ },
1590
+ {
1591
+ title: "Perl",
1592
+ tools: ["Perl 5.38.2"]
1593
+ },
1594
+ {
1595
+ title: "AI Coding Assistants",
1596
+ tools: ["Claude Code (@anthropic-ai/claude-code)", "OpenAI Codex (@openai/codex)"]
1597
+ },
1598
+ {
1599
+ title: "Misc",
1600
+ tools: [
1601
+ "git 2.43.0",
1602
+ "vim 9.1.697",
1603
+ "nano 7.2",
1604
+ "curl",
1605
+ "wget",
1606
+ "GitHub CLI (gh)",
1607
+ "Docker Engine"
1608
+ ]
1637
1609
  }
1638
- } catch {
1639
- }
1610
+ ];
1611
+ sections.forEach((section) => {
1612
+ console.log(import_chalk9.default.blue(section.title));
1613
+ section.tools.forEach((tool) => {
1614
+ console.log(` - ${tool}`);
1615
+ });
1616
+ console.log();
1617
+ });
1640
1618
  }
1641
1619
 
1642
- // src/commands/replica.ts
1643
- var import_chalk15 = __toESM(require("chalk"));
1644
- var import_prompts3 = __toESM(require("prompts"));
1620
+ // src/commands/codex-auth.ts
1621
+ var import_chalk10 = __toESM(require("chalk"));
1645
1622
 
1646
- // ../shared/src/sandbox.ts
1647
- var SANDBOX_LIFECYCLE = {
1648
- AUTO_STOP_MINUTES: 60,
1649
- AUTO_ARCHIVE_MINUTES: 60 * 24 * 7,
1650
- AUTO_DELETE_MINUTES: -1,
1651
- SSH_TOKEN_EXPIRATION_MINUTES: 3 * 60
1652
- };
1623
+ // src/lib/codex-oauth.ts
1624
+ var import_http2 = __toESM(require("http"));
1625
+ var import_url2 = require("url");
1626
+ var import_open2 = __toESM(require("open"));
1653
1627
 
1654
- // ../shared/src/display-message/parsers/codex-parser.ts
1655
- function safeJsonParse(str, fallback) {
1656
- try {
1657
- return JSON.parse(str);
1658
- } catch {
1659
- return fallback;
1660
- }
1661
- }
1662
- function getStatusFromExitCode(exitCode) {
1663
- return exitCode === 0 ? "completed" : "failed";
1664
- }
1665
- function parseShellOutput(raw) {
1666
- const exitCodeMatch = raw.match(/^Exit code: (\d+)/);
1667
- const exitCode = exitCodeMatch ? parseInt(exitCodeMatch[1], 10) : 0;
1668
- const outputIndex = raw.indexOf("Output:\n");
1669
- const output = outputIndex >= 0 ? raw.slice(outputIndex + "Output:\n".length) : raw;
1670
- return { exitCode, output };
1671
- }
1672
- function parsePatch(input) {
1673
- const operations = [];
1674
- const lines = input.split("\n");
1675
- let currentOp = null;
1676
- let diffLines = [];
1677
- for (let i = 0; i < lines.length; i++) {
1678
- const line = lines[i];
1679
- if (line.startsWith("*** Add File:")) {
1680
- if (currentOp) {
1681
- if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
1682
- operations.push(currentOp);
1683
- diffLines = [];
1684
- }
1685
- currentOp = {
1686
- action: "add",
1687
- path: line.replace("*** Add File:", "").trim()
1688
- };
1689
- } else if (line.startsWith("*** Update File:")) {
1690
- if (currentOp) {
1691
- if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
1692
- operations.push(currentOp);
1693
- diffLines = [];
1694
- }
1695
- currentOp = {
1696
- action: "update",
1697
- path: line.replace("*** Update File:", "").trim()
1698
- };
1699
- } else if (line.startsWith("*** Delete File:")) {
1700
- if (currentOp) {
1701
- if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
1702
- operations.push(currentOp);
1703
- diffLines = [];
1704
- }
1705
- currentOp = {
1706
- action: "delete",
1707
- path: line.replace("*** Delete File:", "").trim()
1708
- };
1709
- } else if (line.startsWith("*** Move to:")) {
1710
- if (currentOp) {
1711
- currentOp.moveTo = line.replace("*** Move to:", "").trim();
1712
- }
1713
- } else if (line.startsWith("*** Begin Patch") || line.startsWith("*** End Patch")) {
1714
- continue;
1715
- } else if (currentOp && (line.startsWith("+") || line.startsWith("-") || line.startsWith("@@") || line.trim() === "")) {
1716
- diffLines.push(line);
1717
- }
1718
- }
1719
- if (currentOp) {
1720
- if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
1721
- operations.push(currentOp);
1628
+ // src/lib/pkce.ts
1629
+ var import_crypto = require("crypto");
1630
+ function generatePkceCodes() {
1631
+ const verifierBytes = (0, import_crypto.randomBytes)(32);
1632
+ const codeVerifier = verifierBytes.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1633
+ const hash = (0, import_crypto.createHash)("sha256");
1634
+ hash.update(codeVerifier);
1635
+ const codeChallenge = hash.digest("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1636
+ return {
1637
+ codeVerifier,
1638
+ codeChallenge
1639
+ };
1640
+ }
1641
+ function generateState2() {
1642
+ return (0, import_crypto.randomBytes)(16).toString("hex");
1643
+ }
1644
+
1645
+ // src/lib/codex-oauth.ts
1646
+ var CODEX_OAUTH_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
1647
+ var AUTHORIZATION_ENDPOINT = "https://auth.openai.com/oauth/authorize";
1648
+ var TOKEN_ENDPOINT = "https://auth.openai.com/oauth/token";
1649
+ var CALLBACK_PORT = 1455;
1650
+ var CALLBACK_PATH = "/auth/callback";
1651
+ var WEB_APP_URL2 = process.env.REPLICAS_WEB_URL || "https://replicas.dev";
1652
+ function buildAuthorizationUrl(codeChallenge, state) {
1653
+ const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
1654
+ const params = new URLSearchParams({
1655
+ response_type: "code",
1656
+ client_id: CODEX_OAUTH_CLIENT_ID,
1657
+ redirect_uri: redirectUri,
1658
+ scope: "openid profile email offline_access",
1659
+ code_challenge: codeChallenge,
1660
+ code_challenge_method: "S256",
1661
+ id_token_add_organizations: "true",
1662
+ codex_cli_simplified_flow: "true",
1663
+ state,
1664
+ originator: "codex_cli_rs"
1665
+ });
1666
+ return `${AUTHORIZATION_ENDPOINT}?${params.toString()}`;
1667
+ }
1668
+ async function exchangeCodeForTokens(code, codeVerifier) {
1669
+ const redirectUri = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
1670
+ const params = new URLSearchParams({
1671
+ grant_type: "authorization_code",
1672
+ code,
1673
+ redirect_uri: redirectUri,
1674
+ client_id: CODEX_OAUTH_CLIENT_ID,
1675
+ code_verifier: codeVerifier
1676
+ });
1677
+ const response = await fetch(TOKEN_ENDPOINT, {
1678
+ method: "POST",
1679
+ headers: {
1680
+ "Content-Type": "application/x-www-form-urlencoded"
1681
+ },
1682
+ body: params.toString()
1683
+ });
1684
+ if (!response.ok) {
1685
+ const errorText = await response.text();
1686
+ throw new Error(`Failed to exchange code for tokens: ${response.status} ${errorText}`);
1722
1687
  }
1723
- return operations;
1688
+ const data = await response.json();
1689
+ return {
1690
+ idToken: data.id_token,
1691
+ accessToken: data.access_token,
1692
+ refreshToken: data.refresh_token
1693
+ };
1724
1694
  }
1725
- function parseCodexEvents(events) {
1726
- const messages = [];
1727
- const pendingCommands = /* @__PURE__ */ new Map();
1728
- const pendingPatches = /* @__PURE__ */ new Map();
1729
- events.forEach((event, eventIndex) => {
1730
- if (event.type === "event_msg" && event.payload?.type === "user_message") {
1731
- messages.push({
1732
- id: `user-${event.timestamp}-${eventIndex}`,
1733
- type: "user",
1734
- content: event.payload.message || "",
1735
- timestamp: event.timestamp
1736
- });
1737
- }
1738
- if (event.type === "event_msg" && event.payload?.type === "agent_reasoning") {
1739
- messages.push({
1740
- id: `reasoning-${event.timestamp}-${eventIndex}-${messages.length}`,
1741
- type: "reasoning",
1742
- content: event.payload.text || "",
1743
- status: "completed",
1744
- timestamp: event.timestamp
1745
- });
1746
- }
1747
- if (event.type === "response_item") {
1748
- const payloadType = event.payload?.type;
1749
- if (payloadType === "message" && event.payload?.role === "assistant") {
1750
- const content = event.payload.content || [];
1751
- const textContent = content.filter((c) => c.type === "output_text").map((c) => c.text || "").join("\n");
1752
- if (textContent) {
1753
- messages.push({
1754
- id: `agent-${event.timestamp}-${eventIndex}`,
1755
- type: "agent",
1756
- content: textContent,
1757
- timestamp: event.timestamp
1758
- });
1759
- }
1760
- }
1761
- if (payloadType === "function_call" && (event.payload?.name === "shell" || event.payload?.name === "shell_command")) {
1762
- const callId = event.payload.call_id;
1763
- const args = safeJsonParse(event.payload.arguments || "{}", {});
1764
- const command = Array.isArray(args.command) ? args.command.join(" ") : args.command || "";
1765
- const msg = {
1766
- id: `command-${callId || "no-call-id"}-${eventIndex}`,
1767
- type: "command",
1768
- command,
1769
- output: "",
1770
- status: "in_progress",
1771
- timestamp: event.timestamp
1772
- };
1773
- messages.push(msg);
1774
- if (callId) {
1775
- pendingCommands.set(callId, msg);
1776
- }
1777
- }
1778
- if (payloadType === "function_call_output") {
1779
- const callId = event.payload.call_id;
1780
- const rawOutput = event.payload.output || "";
1781
- const commandMsg = callId ? pendingCommands.get(callId) : void 0;
1782
- if (commandMsg) {
1783
- const { exitCode, output } = parseShellOutput(rawOutput);
1784
- commandMsg.output = output;
1785
- commandMsg.exitCode = exitCode;
1786
- commandMsg.status = getStatusFromExitCode(exitCode);
1787
- pendingCommands.delete(callId);
1695
+ function startCallbackServer(expectedState, codeVerifier) {
1696
+ return new Promise((resolve, reject) => {
1697
+ const server = import_http2.default.createServer(async (req, res) => {
1698
+ try {
1699
+ if (!req.url) {
1700
+ res.writeHead(400);
1701
+ res.end("Bad Request");
1702
+ return;
1788
1703
  }
1789
- }
1790
- if (payloadType === "custom_tool_call") {
1791
- const callId = event.payload.call_id;
1792
- const name = event.payload.name;
1793
- const input = event.payload.input || "";
1794
- const status = event.payload.status || "in_progress";
1795
- if (name === "apply_patch") {
1796
- const operations = parsePatch(input);
1797
- pendingPatches.set(callId, { input, status, timestamp: event.timestamp, operations });
1704
+ const url = new import_url2.URL(req.url, `http://127.0.0.1:${CALLBACK_PORT}`);
1705
+ if (url.pathname === CALLBACK_PATH) {
1706
+ const code = url.searchParams.get("code");
1707
+ const state = url.searchParams.get("state");
1708
+ const error = url.searchParams.get("error");
1709
+ const errorDescription = url.searchParams.get("error_description");
1710
+ if (error) {
1711
+ const errorMessage = encodeURIComponent(`${error}: ${errorDescription || "Unknown error"}`);
1712
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1713
+ res.end();
1714
+ server.close();
1715
+ reject(new Error(`OAuth error: ${error} - ${errorDescription}`));
1716
+ return;
1717
+ }
1718
+ if (state !== expectedState) {
1719
+ const errorMessage = encodeURIComponent("State parameter mismatch. Possible CSRF attack.");
1720
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1721
+ res.end();
1722
+ server.close();
1723
+ reject(new Error("State mismatch - possible CSRF attack"));
1724
+ return;
1725
+ }
1726
+ if (!code) {
1727
+ const errorMessage = encodeURIComponent("No authorization code received.");
1728
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1729
+ res.end();
1730
+ server.close();
1731
+ reject(new Error("No authorization code received"));
1732
+ return;
1733
+ }
1734
+ try {
1735
+ const tokens = await exchangeCodeForTokens(code, codeVerifier);
1736
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/success` });
1737
+ res.end();
1738
+ server.close();
1739
+ resolve(tokens);
1740
+ } catch (tokenError) {
1741
+ const errorMessage = encodeURIComponent(tokenError instanceof Error ? tokenError.message : "Unknown error");
1742
+ res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
1743
+ res.end();
1744
+ server.close();
1745
+ reject(tokenError);
1746
+ }
1798
1747
  } else {
1799
- messages.push({
1800
- id: `toolcall-${event.timestamp}-${eventIndex}`,
1801
- type: "tool_call",
1802
- server: "custom",
1803
- tool: name,
1804
- status,
1805
- timestamp: event.timestamp
1806
- });
1748
+ res.writeHead(404);
1749
+ res.end("Not Found");
1807
1750
  }
1751
+ } catch (serverError) {
1752
+ res.writeHead(500);
1753
+ res.end("Internal Server Error");
1754
+ server.close();
1755
+ reject(serverError);
1808
1756
  }
1809
- if (payloadType === "custom_tool_call_output") {
1810
- const callId = event.payload.call_id;
1811
- const output = safeJsonParse(
1812
- event.payload.output || "{}",
1813
- {}
1814
- );
1815
- const pendingPatch = pendingPatches.get(callId);
1816
- if (pendingPatch) {
1817
- messages.push({
1818
- id: `patch-${pendingPatch.timestamp}-${eventIndex}`,
1819
- type: "patch",
1820
- operations: pendingPatch.operations,
1821
- output: output.output || "",
1822
- exitCode: output.metadata?.exit_code,
1823
- status: getStatusFromExitCode(output.metadata?.exit_code),
1824
- timestamp: pendingPatch.timestamp
1825
- });
1826
- pendingPatches.delete(callId);
1827
- } else {
1828
- const toolCallMsg = messages.findLast((m) => m.type === "tool_call");
1829
- if (toolCallMsg) {
1830
- toolCallMsg.status = getStatusFromExitCode(output.metadata?.exit_code);
1831
- }
1757
+ });
1758
+ server.on("error", (err) => {
1759
+ if (err.code === "EADDRINUSE") {
1760
+ reject(new Error(`Port ${CALLBACK_PORT} is already in use. Please close any other OAuth flows and try again.`));
1761
+ } else {
1762
+ reject(err);
1763
+ }
1764
+ });
1765
+ server.listen(CALLBACK_PORT, "127.0.0.1");
1766
+ });
1767
+ }
1768
+ async function runCodexOAuthFlow() {
1769
+ const pkce = generatePkceCodes();
1770
+ const state = generateState2();
1771
+ const authUrl = buildAuthorizationUrl(pkce.codeChallenge, state);
1772
+ const tokensPromise = startCallbackServer(state, pkce.codeVerifier);
1773
+ await new Promise((resolve) => setTimeout(resolve, 500));
1774
+ console.log("If the browser does not open automatically, visit:");
1775
+ console.log(authUrl);
1776
+ console.log();
1777
+ try {
1778
+ await (0, import_open2.default)(authUrl);
1779
+ } catch (openError) {
1780
+ console.warn("Failed to open browser automatically. Please open the URL manually.");
1781
+ }
1782
+ console.log("Waiting for authentication...");
1783
+ const tokens = await tokensPromise;
1784
+ console.log("\u2713 Successfully obtained Codex credentials");
1785
+ return tokens;
1786
+ }
1787
+
1788
+ // src/commands/codex-auth.ts
1789
+ async function codexAuthCommand(options = {}) {
1790
+ const isUserScoped = options.user === true;
1791
+ const scopeLabel = isUserScoped ? "personal" : "organization";
1792
+ console.log(import_chalk10.default.cyan(`Authenticating with Codex (${scopeLabel})...
1793
+ `));
1794
+ try {
1795
+ const tokens = await runCodexOAuthFlow();
1796
+ console.log(import_chalk10.default.gray("\nUploading credentials to Replicas..."));
1797
+ const endpoint = isUserScoped ? "/v1/codex/user/credentials" : "/v1/codex/credentials";
1798
+ const response = await orgAuthenticatedFetch(
1799
+ endpoint,
1800
+ {
1801
+ method: "POST",
1802
+ body: {
1803
+ access_token: tokens.accessToken,
1804
+ refresh_token: tokens.refreshToken,
1805
+ id_token: tokens.idToken
1832
1806
  }
1833
1807
  }
1834
- if (payloadType === "function_call" && event.payload?.name === "update_plan") {
1835
- const args = safeJsonParse(
1836
- event.payload.arguments || "{}",
1837
- {}
1838
- );
1839
- if (args.plan && Array.isArray(args.plan)) {
1840
- const todoItems = args.plan.map((item) => ({
1841
- text: item.step,
1842
- completed: item.status === "completed"
1843
- }));
1844
- messages.push({
1845
- id: `todo-${event.timestamp}-${eventIndex}`,
1846
- type: "todo_list",
1847
- items: todoItems,
1848
- status: "completed",
1849
- timestamp: event.timestamp
1850
- });
1851
- }
1808
+ );
1809
+ if (response.success) {
1810
+ console.log(import_chalk10.default.green("\n\u2713 Codex authentication complete!"));
1811
+ if (response.email) {
1812
+ console.log(import_chalk10.default.gray(` Account: ${response.email}`));
1813
+ }
1814
+ if (response.planType) {
1815
+ console.log(import_chalk10.default.gray(` Plan: ${response.planType}`));
1852
1816
  }
1817
+ if (isUserScoped) {
1818
+ console.log(import_chalk10.default.gray("\nYour personal Codex credentials have been saved.\n"));
1819
+ } else {
1820
+ console.log(import_chalk10.default.gray("\nYou can now use Codex in your workspaces.\n"));
1821
+ }
1822
+ } else {
1823
+ throw new Error(response.error || "Failed to upload Codex credentials to Replicas");
1853
1824
  }
1854
- });
1855
- return messages;
1856
- }
1857
-
1858
- // ../shared/src/display-message/parsers/claude-parser.ts
1859
- function safeJsonParse2(str, fallback) {
1860
- try {
1861
- return JSON.parse(str);
1862
- } catch {
1863
- return fallback;
1825
+ } catch (error) {
1826
+ console.log(import_chalk10.default.red("\n\u2717 Error:"), error instanceof Error ? error.message : error);
1827
+ process.exit(1);
1864
1828
  }
1865
1829
  }
1866
- function extractToolResultContent(content) {
1867
- if (!content) return "";
1868
- if (typeof content === "string") return content;
1869
- if (Array.isArray(content)) {
1870
- return content.filter((item) => item.type === "text").map((item) => item.text || "").join("\n");
1871
- }
1872
- return "";
1830
+
1831
+ // src/commands/claude-auth.ts
1832
+ var import_chalk12 = __toESM(require("chalk"));
1833
+
1834
+ // src/lib/claude-oauth.ts
1835
+ var import_open3 = __toESM(require("open"));
1836
+ var import_readline = __toESM(require("readline"));
1837
+ var import_chalk11 = __toESM(require("chalk"));
1838
+ var DEFAULT_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
1839
+ var CLAUDE_CLIENT_ID = process.env.CLAUDE_OAUTH_CLIENT_ID || DEFAULT_CLIENT_ID;
1840
+ var AUTHORIZATION_ENDPOINT2 = "https://claude.ai/oauth/authorize";
1841
+ var TOKEN_ENDPOINT2 = "https://console.anthropic.com/v1/oauth/token";
1842
+ var REDIRECT_URI = "https://console.anthropic.com/oauth/code/callback";
1843
+ var SCOPES = "org:create_api_key user:profile user:inference";
1844
+ function buildAuthorizationUrl2(codeChallenge, state) {
1845
+ const params = new URLSearchParams({
1846
+ code: "true",
1847
+ client_id: CLAUDE_CLIENT_ID,
1848
+ response_type: "code",
1849
+ redirect_uri: REDIRECT_URI,
1850
+ scope: SCOPES,
1851
+ code_challenge: codeChallenge,
1852
+ code_challenge_method: "S256",
1853
+ state
1854
+ });
1855
+ return `${AUTHORIZATION_ENDPOINT2}?${params.toString()}`;
1873
1856
  }
1874
- function parseClaudeEvents(events, parentToolUseId) {
1875
- const messages = [];
1876
- const filterValue = parentToolUseId !== void 0 ? parentToolUseId : null;
1877
- const filteredEvents = events.filter(
1878
- (e) => e.payload.parent_tool_use_id === filterValue
1879
- );
1880
- const toolCallMap = /* @__PURE__ */ new Map();
1881
- filteredEvents.forEach((event) => {
1882
- if (event.type === "claude-user") {
1883
- const content = event.payload.message?.content || [];
1884
- const toolResult = content.find((c) => c.type === "tool_result");
1885
- if (toolResult && toolResult.tool_use_id) {
1886
- return;
1887
- }
1888
- const textContent = content.filter((c) => c.type === "text").map((c) => c.text || "").join("\n");
1889
- const images = content.filter((c) => c.type === "image" && c.source).map((c) => {
1890
- const source = c.source;
1891
- return {
1892
- type: "image",
1893
- mediaType: source.media_type || "image/png",
1894
- data: source.data || ""
1895
- };
1896
- }).filter((img) => img.data);
1897
- if (textContent || images.length > 0) {
1898
- messages.push({
1899
- id: `user-${event.timestamp}`,
1900
- type: "user",
1901
- content: textContent || (images.length > 0 ? `[${images.length} image${images.length > 1 ? "s" : ""} attached]` : ""),
1902
- images: images.length > 0 ? images : void 0,
1903
- timestamp: event.timestamp
1904
- });
1905
- }
1906
- }
1907
- if (event.type === "claude-assistant") {
1908
- const contentBlocks = event.payload.message?.content || [];
1909
- contentBlocks.forEach((block) => {
1910
- if (block.type === "text" && block.text) {
1911
- messages.push({
1912
- id: `agent-${event.timestamp}-${messages.length}`,
1913
- type: "agent",
1914
- content: block.text,
1915
- timestamp: event.timestamp
1916
- });
1917
- }
1918
- if (block.type === "thinking" && block.text) {
1919
- messages.push({
1920
- id: `reasoning-${event.timestamp}-${messages.length}`,
1921
- type: "reasoning",
1922
- content: block.text,
1923
- status: "completed",
1924
- timestamp: event.timestamp
1925
- });
1926
- }
1927
- if (block.type === "tool_use" && block.id) {
1928
- const toolName = block.name || "unknown";
1929
- const toolInput = block.input || {};
1930
- const toolUseId = block.id;
1931
- toolCallMap.set(toolUseId, {
1932
- messageIndex: messages.length,
1933
- toolName,
1934
- input: toolInput
1935
- });
1936
- if (toolName === "Task") {
1937
- const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
1938
- const nestedEvents = events.filter((e) => e.payload.parent_tool_use_id === toolUseId).map((e) => ({
1939
- timestamp: e.timestamp,
1940
- type: e.type,
1941
- payload: e.payload
1942
- }));
1943
- messages.push({
1944
- id: `subagent-${event.timestamp}-${messages.length}`,
1945
- type: "subagent",
1946
- toolUseId,
1947
- description: inputObj.description || "Subagent Task",
1948
- prompt: inputObj.prompt || "",
1949
- subagentType: inputObj.subagent_type || "general",
1950
- model: inputObj.model,
1951
- status: "in_progress",
1952
- nestedEvents,
1953
- timestamp: event.timestamp
1954
- });
1955
- } else if (toolName === "Bash" || toolName === "bash" || toolName === "shell") {
1956
- const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
1957
- const command = inputObj.command || "";
1958
- messages.push({
1959
- id: `command-${event.timestamp}-${messages.length}`,
1960
- type: "command",
1961
- command,
1962
- output: "",
1963
- status: "in_progress",
1964
- timestamp: event.timestamp
1965
- });
1966
- } else if (toolName === "Write" || toolName === "Edit") {
1967
- const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
1968
- const filePath = inputObj.file_path || "";
1969
- const action = toolName === "Write" ? "add" : "update";
1970
- messages.push({
1971
- id: `file-${event.timestamp}-${messages.length}`,
1972
- type: "file_change",
1973
- changes: [{
1974
- path: filePath,
1975
- kind: action
1976
- }],
1977
- status: "in_progress",
1978
- timestamp: event.timestamp
1979
- });
1980
- } else if (toolName === "TodoWrite") {
1981
- const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
1982
- const todos = inputObj.todos || [];
1983
- const todoItems = todos.map((todo) => ({
1984
- text: todo.content,
1985
- completed: todo.status === "completed"
1986
- }));
1987
- if (todoItems.length > 0) {
1988
- messages.push({
1989
- id: `todo-${event.timestamp}-${messages.length}`,
1990
- type: "todo_list",
1991
- items: todoItems,
1992
- status: "completed",
1993
- timestamp: event.timestamp
1994
- });
1995
- }
1996
- } else if (toolName === "WebSearch") {
1997
- const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
1998
- const query = inputObj.query || "";
1999
- messages.push({
2000
- id: `search-${event.timestamp}-${messages.length}`,
2001
- type: "web_search",
2002
- query,
2003
- status: "in_progress",
2004
- timestamp: event.timestamp
2005
- });
2006
- } else {
2007
- messages.push({
2008
- id: `toolcall-${event.timestamp}-${messages.length}`,
2009
- type: "tool_call",
2010
- server: "claude",
2011
- tool: toolName,
2012
- input: toolInput,
2013
- status: "in_progress",
2014
- timestamp: event.timestamp
2015
- });
2016
- }
2017
- }
2018
- });
2019
- }
2020
- if (event.type === "claude-result") {
2021
- const payload = event.payload;
2022
- if (payload.is_error || payload.subtype !== "success") {
2023
- const errorList = payload.errors || [];
2024
- const errorMessage = errorList.length > 0 ? errorList.join("\n") : "Claude session encountered an unexpected error.";
2025
- messages.push({
2026
- id: `error-${event.timestamp}`,
2027
- type: "error",
2028
- message: errorMessage,
2029
- timestamp: event.timestamp
2030
- });
1857
+ async function promptForAuthorizationCode(instruction) {
1858
+ const rl = import_readline.default.createInterface({
1859
+ input: process.stdin,
1860
+ output: process.stdout
1861
+ });
1862
+ return new Promise((resolve) => {
1863
+ rl.question(instruction, (answer) => {
1864
+ rl.close();
1865
+ resolve(answer.trim());
1866
+ });
1867
+ });
1868
+ }
1869
+ function parseUserInput(input) {
1870
+ if (!input.includes("#") && !input.includes("code=")) {
1871
+ return { code: input };
1872
+ }
1873
+ if (input.startsWith("http://") || input.startsWith("https://")) {
1874
+ try {
1875
+ const url = new URL(input);
1876
+ const fragment = url.hash.startsWith("#") ? url.hash.slice(1) : url.hash;
1877
+ const params = new URLSearchParams(fragment);
1878
+ const code2 = params.get("code") ?? void 0;
1879
+ const state2 = params.get("state") ?? void 0;
1880
+ if (code2) {
1881
+ return { code: code2, state: state2 };
2031
1882
  }
1883
+ } catch {
2032
1884
  }
1885
+ }
1886
+ const [code, state] = input.split("#");
1887
+ return { code, state };
1888
+ }
1889
+ async function exchangeCodeForTokens2(code, state, expectedState, codeVerifier) {
1890
+ if (state && state !== expectedState) {
1891
+ throw new Error("State mismatch detected. Please restart the authentication flow.");
1892
+ }
1893
+ const payload = {
1894
+ code,
1895
+ state: state ?? expectedState,
1896
+ grant_type: "authorization_code",
1897
+ client_id: CLAUDE_CLIENT_ID,
1898
+ redirect_uri: REDIRECT_URI,
1899
+ code_verifier: codeVerifier
1900
+ };
1901
+ const response = await fetch(TOKEN_ENDPOINT2, {
1902
+ method: "POST",
1903
+ headers: {
1904
+ "Content-Type": "application/json"
1905
+ },
1906
+ body: JSON.stringify(payload)
2033
1907
  });
2034
- filteredEvents.forEach((event) => {
2035
- if (event.type === "claude-user") {
2036
- const content = event.payload.message?.content || [];
2037
- const toolResult = content.find((c) => c.type === "tool_result");
2038
- if (toolResult && toolResult.tool_use_id) {
2039
- const toolInfo = toolCallMap.get(toolResult.tool_use_id);
2040
- if (!toolInfo) return;
2041
- const resultContent = extractToolResultContent(toolResult.content);
2042
- const isError = toolResult.is_error || false;
2043
- const status = isError ? "failed" : "completed";
2044
- const message = messages[toolInfo.messageIndex];
2045
- if (!message) return;
2046
- if (message.type === "command") {
2047
- message.output = resultContent;
2048
- message.status = status;
2049
- const exitCodeMatch = resultContent.match(/exit code:?\s*(\d+)/i);
2050
- if (exitCodeMatch) {
2051
- message.exitCode = parseInt(exitCodeMatch[1], 10);
2052
- } else {
2053
- message.exitCode = isError ? 1 : 0;
2054
- }
2055
- } else if (message.type === "file_change") {
2056
- message.status = status;
2057
- } else if (message.type === "web_search") {
2058
- message.status = status;
2059
- } else if (message.type === "tool_call") {
2060
- message.output = resultContent;
2061
- message.status = status;
2062
- } else if (message.type === "subagent") {
2063
- message.output = resultContent;
2064
- message.status = status;
1908
+ if (!response.ok) {
1909
+ const text = await response.text();
1910
+ throw new Error(`Failed to exchange authorization code: ${response.status} ${text}`);
1911
+ }
1912
+ const data = await response.json();
1913
+ const now = Date.now();
1914
+ const expiresAt = new Date(now + data.expires_in * 1e3).toISOString();
1915
+ const refreshTokenExpiresAt = data.refresh_token_expires_in ? new Date(now + data.refresh_token_expires_in * 1e3).toISOString() : void 0;
1916
+ return {
1917
+ accessToken: data.access_token,
1918
+ refreshToken: data.refresh_token,
1919
+ tokenType: data.token_type,
1920
+ scope: data.scope,
1921
+ expiresAt,
1922
+ refreshTokenExpiresAt
1923
+ };
1924
+ }
1925
+ async function runClaudeOAuthFlow() {
1926
+ const pkce = generatePkceCodes();
1927
+ const state = generateState2();
1928
+ const authUrl = buildAuthorizationUrl2(pkce.codeChallenge, state);
1929
+ console.log(import_chalk11.default.gray("We will open your browser so you can authenticate with Claude Code."));
1930
+ console.log(import_chalk11.default.gray("If the browser does not open automatically, use the URL below:\n"));
1931
+ console.log(authUrl);
1932
+ console.log();
1933
+ try {
1934
+ await (0, import_open3.default)(authUrl);
1935
+ } catch {
1936
+ console.warn(import_chalk11.default.yellow("Failed to open browser automatically. Please copy and open the URL manually."));
1937
+ }
1938
+ console.log(import_chalk11.default.cyan("After completing authentication, copy the code shown on the success page."));
1939
+ console.log(import_chalk11.default.cyan("You can paste either the full URL, or a value formatted as CODE#STATE.\n"));
1940
+ const userInput = await promptForAuthorizationCode("Paste the authorization code (or URL) here: ");
1941
+ if (!userInput) {
1942
+ throw new Error("No authorization code provided.");
1943
+ }
1944
+ const { code, state: returnedState } = parseUserInput(userInput);
1945
+ if (!code) {
1946
+ throw new Error("Unable to parse authorization code. Please try again.");
1947
+ }
1948
+ console.log(import_chalk11.default.gray("\nExchanging authorization code for tokens..."));
1949
+ const tokens = await exchangeCodeForTokens2(code, returnedState, state, pkce.codeVerifier);
1950
+ console.log(import_chalk11.default.green("\u2713 Successfully obtained Claude credentials"));
1951
+ return tokens;
1952
+ }
1953
+
1954
+ // src/commands/claude-auth.ts
1955
+ async function claudeAuthCommand(options = {}) {
1956
+ const isUserScoped = options.user === true;
1957
+ const scopeLabel = isUserScoped ? "personal" : "organization";
1958
+ console.log(import_chalk12.default.cyan(`Authenticating with Claude Code (${scopeLabel})...
1959
+ `));
1960
+ try {
1961
+ const tokens = await runClaudeOAuthFlow();
1962
+ console.log(import_chalk12.default.gray("\nUploading credentials to Replicas..."));
1963
+ const endpoint = isUserScoped ? "/v1/claude/user/credentials" : "/v1/claude/credentials";
1964
+ const response = await orgAuthenticatedFetch(
1965
+ endpoint,
1966
+ {
1967
+ method: "POST",
1968
+ body: {
1969
+ access_token: tokens.accessToken,
1970
+ refresh_token: tokens.refreshToken,
1971
+ token_type: tokens.tokenType,
1972
+ scope: tokens.scope,
1973
+ expires_at: tokens.expiresAt,
1974
+ refresh_token_expires_at: tokens.refreshTokenExpiresAt
2065
1975
  }
2066
1976
  }
1977
+ );
1978
+ if (!response.success) {
1979
+ throw new Error(response.error || "Failed to upload Claude credentials to Replicas");
2067
1980
  }
2068
- });
2069
- return messages;
1981
+ console.log(import_chalk12.default.green("\n\u2713 Claude authentication complete!"));
1982
+ if (response.email) {
1983
+ console.log(import_chalk12.default.gray(` Account: ${response.email}`));
1984
+ }
1985
+ if (response.planType) {
1986
+ console.log(import_chalk12.default.gray(` Plan: ${response.planType}`));
1987
+ }
1988
+ if (isUserScoped) {
1989
+ console.log(import_chalk12.default.gray("\nYour personal Claude credentials have been saved.\n"));
1990
+ } else {
1991
+ console.log(import_chalk12.default.gray("\nYou can now use Claude Code in your workspaces.\n"));
1992
+ }
1993
+ } catch (error) {
1994
+ console.log(import_chalk12.default.red("\n\u2717 Error:"), error instanceof Error ? error.message : error);
1995
+ process.exit(1);
1996
+ }
2070
1997
  }
2071
1998
 
2072
- // ../shared/src/display-message/parsers/index.ts
2073
- function parseAgentEvents(events, agentType) {
2074
- if (agentType === "codex") {
2075
- return parseCodexEvents(events);
2076
- } else if (agentType === "claude") {
2077
- return parseClaudeEvents(events);
1999
+ // src/commands/init.ts
2000
+ var import_chalk13 = __toESM(require("chalk"));
2001
+ var import_fs4 = __toESM(require("fs"));
2002
+ var import_path5 = __toESM(require("path"));
2003
+ var REPLICAS_CONFIG_FILENAME = "replicas.json";
2004
+ function getDefaultConfig() {
2005
+ return {
2006
+ copy: [],
2007
+ ports: [],
2008
+ systemPrompt: "",
2009
+ startHook: {
2010
+ timeout: 3e5,
2011
+ commands: []
2012
+ }
2013
+ };
2014
+ }
2015
+ function initCommand(options) {
2016
+ const configPath = import_path5.default.join(process.cwd(), REPLICAS_CONFIG_FILENAME);
2017
+ if (import_fs4.default.existsSync(configPath) && !options.force) {
2018
+ console.log(
2019
+ import_chalk13.default.yellow(
2020
+ `${REPLICAS_CONFIG_FILENAME} already exists in this directory.`
2021
+ )
2022
+ );
2023
+ console.log(import_chalk13.default.gray("Use --force to overwrite the existing file."));
2024
+ return;
2025
+ }
2026
+ const defaultConfig = getDefaultConfig();
2027
+ const configContent = JSON.stringify(defaultConfig, null, 2);
2028
+ try {
2029
+ import_fs4.default.writeFileSync(configPath, configContent + "\n", "utf-8");
2030
+ console.log(import_chalk13.default.green(`\u2713 Created ${REPLICAS_CONFIG_FILENAME}`));
2031
+ console.log("");
2032
+ console.log(import_chalk13.default.gray("Configuration options:"));
2033
+ console.log(
2034
+ import_chalk13.default.gray(' copy - Files to sync to the workspace (e.g., [".env"])')
2035
+ );
2036
+ console.log(
2037
+ import_chalk13.default.gray(" ports - Ports to forward from the workspace (e.g., [3000, 8080])")
2038
+ );
2039
+ console.log(
2040
+ import_chalk13.default.gray(" systemPrompt - Custom instructions for AI coding assistants")
2041
+ );
2042
+ console.log(
2043
+ import_chalk13.default.gray(" startHook - Commands to run on workspace startup")
2044
+ );
2045
+ } catch (error) {
2046
+ throw new Error(
2047
+ `Failed to create ${REPLICAS_CONFIG_FILENAME}: ${error instanceof Error ? error.message : "Unknown error"}`
2048
+ );
2049
+ }
2050
+ }
2051
+
2052
+ // src/lib/version-check.ts
2053
+ var import_chalk14 = __toESM(require("chalk"));
2054
+ var MONOLITH_URL2 = process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
2055
+ var VERSION_CHECK_TIMEOUT = 2e3;
2056
+ function compareVersions(v1, v2) {
2057
+ const parts1 = v1.split(".").map(Number);
2058
+ const parts2 = v2.split(".").map(Number);
2059
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
2060
+ const num1 = parts1[i] || 0;
2061
+ const num2 = parts2[i] || 0;
2062
+ if (num1 > num2) return 1;
2063
+ if (num1 < num2) return -1;
2064
+ }
2065
+ return 0;
2066
+ }
2067
+ async function checkForUpdates(currentVersion) {
2068
+ try {
2069
+ const controller = new AbortController();
2070
+ const timeoutId = setTimeout(() => controller.abort(), VERSION_CHECK_TIMEOUT);
2071
+ const response = await fetch(`${MONOLITH_URL2}/v1/cli/version`, {
2072
+ signal: controller.signal
2073
+ });
2074
+ clearTimeout(timeoutId);
2075
+ if (!response.ok) return;
2076
+ const data = await response.json();
2077
+ const latestVersion = data.version;
2078
+ if (compareVersions(latestVersion, currentVersion) > 0) {
2079
+ console.error(import_chalk14.default.gray(`
2080
+ Update available: ${currentVersion} \u2192 ${latestVersion}`));
2081
+ console.error(import_chalk14.default.gray(`Run: npm i -g replicas-cli@latest
2082
+ `));
2083
+ }
2084
+ } catch {
2078
2085
  }
2079
- return [];
2080
2086
  }
2081
2087
 
2082
2088
  // src/commands/replica.ts
2089
+ var import_chalk15 = __toESM(require("chalk"));
2090
+ var import_prompts3 = __toESM(require("prompts"));
2083
2091
  function formatDate(dateString) {
2084
2092
  return new Date(dateString).toLocaleString();
2085
2093
  }
@@ -2541,7 +2549,7 @@ Repositories (${response.repositories.length}):
2541
2549
  }
2542
2550
 
2543
2551
  // src/index.ts
2544
- var CLI_VERSION = "0.2.30";
2552
+ var CLI_VERSION = "0.2.32";
2545
2553
  var program = new import_commander.Command();
2546
2554
  program.name("replicas").description("CLI for managing Replicas workspaces").version(CLI_VERSION);
2547
2555
  program.command("login").description("Authenticate with your Replicas account").action(async () => {