replicas-cli 0.2.30 → 0.2.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1179 -1171
- 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/
|
|
817
|
-
var
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
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
|
|
837
|
-
|
|
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
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
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
|
|
858
|
-
|
|
859
|
-
const lines =
|
|
860
|
-
|
|
861
|
-
let
|
|
862
|
-
for (
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
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
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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
|
-
|
|
906
|
-
|
|
907
|
-
|
|
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
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
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
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
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
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
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
|
-
|
|
1030
|
-
|
|
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/
|
|
1041
|
-
|
|
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
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
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
|
-
|
|
1085
|
-
if (!
|
|
1086
|
-
|
|
1087
|
-
|
|
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
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
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
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
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
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
-
}
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
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
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
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
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
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
|
-
|
|
1337
|
-
const tokens = await tokensPromise;
|
|
1338
|
-
console.log("\u2713 Successfully obtained Codex credentials");
|
|
1339
|
-
return tokens;
|
|
1259
|
+
return [];
|
|
1340
1260
|
}
|
|
1341
1261
|
|
|
1342
|
-
// src/
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
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;
|
|
1361
1315
|
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1316
|
+
result.push(line);
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
if (line.trim() === REPLICAS_MARKER_END) {
|
|
1320
|
+
if (inTargetBlock) {
|
|
1321
|
+
inTargetBlock = false;
|
|
1322
|
+
continue;
|
|
1367
1323
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
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}`));
|
|
1370
1375
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
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
|
+
`));
|
|
1375
1400
|
}
|
|
1376
|
-
}
|
|
1377
|
-
throw new Error(response.error || "Failed to upload Codex credentials to Replicas");
|
|
1378
|
-
}
|
|
1401
|
+
});
|
|
1379
1402
|
} catch (error) {
|
|
1380
|
-
console.
|
|
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/
|
|
1386
|
-
var
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
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
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
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
|
|
1444
|
-
if (
|
|
1445
|
-
|
|
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 (
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
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/
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
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
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
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
|
-
|
|
1543
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
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
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
);
|
|
1590
|
-
console.log(
|
|
1591
|
-
|
|
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
|
-
|
|
1601
|
-
|
|
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/
|
|
1607
|
-
var
|
|
1608
|
-
var
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
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
|
-
|
|
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/
|
|
1643
|
-
var
|
|
1644
|
-
var import_prompts3 = __toESM(require("prompts"));
|
|
1620
|
+
// src/commands/codex-auth.ts
|
|
1621
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
1645
1622
|
|
|
1646
|
-
//
|
|
1647
|
-
var
|
|
1648
|
-
|
|
1649
|
-
|
|
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
|
-
//
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
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
|
-
|
|
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
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
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);
|
|
1788
|
-
}
|
|
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 });
|
|
1798
|
-
} 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
|
-
});
|
|
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;
|
|
1807
1703
|
}
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
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);
|
|
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;
|
|
1831
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
|
+
}
|
|
1747
|
+
} else {
|
|
1748
|
+
res.writeHead(404);
|
|
1749
|
+
res.end("Not Found");
|
|
1832
1750
|
}
|
|
1751
|
+
} catch (serverError) {
|
|
1752
|
+
res.writeHead(500);
|
|
1753
|
+
res.end("Internal Server Error");
|
|
1754
|
+
server.close();
|
|
1755
|
+
reject(serverError);
|
|
1833
1756
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
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
|
-
}
|
|
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);
|
|
1852
1763
|
}
|
|
1853
|
-
}
|
|
1764
|
+
});
|
|
1765
|
+
server.listen(CALLBACK_PORT, "127.0.0.1");
|
|
1854
1766
|
});
|
|
1855
|
-
return messages;
|
|
1856
1767
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
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();
|
|
1860
1777
|
try {
|
|
1861
|
-
|
|
1862
|
-
} catch {
|
|
1863
|
-
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
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");
|
|
1778
|
+
await (0, import_open2.default)(authUrl);
|
|
1779
|
+
} catch (openError) {
|
|
1780
|
+
console.warn("Failed to open browser automatically. Please open the URL manually.");
|
|
1871
1781
|
}
|
|
1872
|
-
|
|
1782
|
+
console.log("Waiting for authentication...");
|
|
1783
|
+
const tokens = await tokensPromise;
|
|
1784
|
+
console.log("\u2713 Successfully obtained Codex credentials");
|
|
1785
|
+
return tokens;
|
|
1873
1786
|
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
const
|
|
1878
|
-
|
|
1879
|
-
)
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
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
|
|
1806
|
+
}
|
|
1887
1807
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
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
|
-
});
|
|
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}`));
|
|
1905
1813
|
}
|
|
1814
|
+
if (response.planType) {
|
|
1815
|
+
console.log(import_chalk10.default.gray(` Plan: ${response.planType}`));
|
|
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");
|
|
1906
1824
|
}
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
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
|
-
});
|
|
1825
|
+
} catch (error) {
|
|
1826
|
+
console.log(import_chalk10.default.red("\n\u2717 Error:"), error instanceof Error ? error.message : error);
|
|
1827
|
+
process.exit(1);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
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()}`;
|
|
1856
|
+
}
|
|
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
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
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.yellow(`
|
|
2080
|
+
Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
2081
|
+
console.error(import_chalk14.default.cyan(`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.
|
|
2552
|
+
var CLI_VERSION = "0.2.33";
|
|
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 () => {
|