replicas-cli 0.2.29 → 0.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1199 -1179
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -813,1266 +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
898
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
if (finalConfig.length > 0) {
|
|
890
|
-
finalConfig += "\n\n";
|
|
899
|
+
if (currentOp) {
|
|
900
|
+
if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
|
|
901
|
+
operations.push(currentOp);
|
|
891
902
|
}
|
|
892
|
-
|
|
893
|
-
writeSSHConfig(finalConfig);
|
|
903
|
+
return operations;
|
|
894
904
|
}
|
|
895
|
-
function
|
|
896
|
-
|
|
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
|
+
});
|
|
917
|
+
}
|
|
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
|
+
});
|
|
926
|
+
}
|
|
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
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
});
|
|
1035
|
+
return messages;
|
|
897
1036
|
}
|
|
898
1037
|
|
|
899
|
-
// src/
|
|
900
|
-
|
|
901
|
-
if (!isAuthenticated()) {
|
|
902
|
-
console.log(import_chalk6.default.red('Not logged in. Please run "replicas login" first.'));
|
|
903
|
-
process.exit(1);
|
|
904
|
-
}
|
|
1038
|
+
// ../shared/src/display-message/parsers/claude-parser.ts
|
|
1039
|
+
function safeJsonParse2(str, fallback) {
|
|
905
1040
|
try {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// src/commands/org.ts
|
|
964
|
-
var import_chalk7 = __toESM(require("chalk"));
|
|
965
|
-
var import_prompts2 = __toESM(require("prompts"));
|
|
966
|
-
async function orgCommand() {
|
|
967
|
-
if (!isAuthenticated()) {
|
|
968
|
-
console.log(import_chalk7.default.red('Not logged in. Please run "replicas login" first.'));
|
|
969
|
-
process.exit(1);
|
|
970
|
-
}
|
|
971
|
-
try {
|
|
972
|
-
const currentOrgId = getOrganizationId();
|
|
973
|
-
const organizations = await fetchOrganizations();
|
|
974
|
-
if (!currentOrgId) {
|
|
975
|
-
console.log(import_chalk7.default.yellow("No organization selected."));
|
|
976
|
-
console.log(import_chalk7.default.gray('Run "replicas org switch" to select an organization.\n'));
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
const currentOrg = organizations.find((org2) => org2.id === currentOrgId);
|
|
980
|
-
if (!currentOrg) {
|
|
981
|
-
console.log(import_chalk7.default.yellow(`Current organization ID (${currentOrgId}) not found.`));
|
|
982
|
-
console.log(import_chalk7.default.gray('Run "replicas org switch" to select a new organization.\n'));
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
console.log(import_chalk7.default.green("\nCurrent organization:"));
|
|
986
|
-
console.log(import_chalk7.default.gray(` Name: ${currentOrg.name}`));
|
|
987
|
-
console.log(import_chalk7.default.gray(` ID: ${currentOrg.id}
|
|
988
|
-
`));
|
|
989
|
-
} catch (error) {
|
|
990
|
-
console.error(import_chalk7.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
991
|
-
process.exit(1);
|
|
1041
|
+
return JSON.parse(str);
|
|
1042
|
+
} catch {
|
|
1043
|
+
return fallback;
|
|
992
1044
|
}
|
|
993
1045
|
}
|
|
994
|
-
|
|
995
|
-
if (!
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
try {
|
|
1000
|
-
const organizations = await fetchOrganizations();
|
|
1001
|
-
if (organizations.length === 0) {
|
|
1002
|
-
console.log(import_chalk7.default.yellow("You are not a member of any organization."));
|
|
1003
|
-
console.log(import_chalk7.default.gray("Please contact support.\n"));
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
if (organizations.length === 1) {
|
|
1007
|
-
await setActiveOrganization(organizations[0].id);
|
|
1008
|
-
console.log(import_chalk7.default.green(`
|
|
1009
|
-
\u2713 Organization set to: ${organizations[0].name}
|
|
1010
|
-
`));
|
|
1011
|
-
return;
|
|
1012
|
-
}
|
|
1013
|
-
const currentOrgId = getOrganizationId();
|
|
1014
|
-
const response = await (0, import_prompts2.default)({
|
|
1015
|
-
type: "select",
|
|
1016
|
-
name: "organizationId",
|
|
1017
|
-
message: "Select an organization:",
|
|
1018
|
-
choices: organizations.map((org2) => ({
|
|
1019
|
-
title: `${org2.name}${org2.id === currentOrgId ? " (current)" : ""}`,
|
|
1020
|
-
value: org2.id,
|
|
1021
|
-
description: org2.id
|
|
1022
|
-
})),
|
|
1023
|
-
initial: organizations.findIndex((org2) => org2.id === currentOrgId)
|
|
1024
|
-
});
|
|
1025
|
-
if (!response.organizationId) {
|
|
1026
|
-
console.log(import_chalk7.default.yellow("\nCancelled."));
|
|
1027
|
-
return;
|
|
1028
|
-
}
|
|
1029
|
-
await setActiveOrganization(response.organizationId);
|
|
1030
|
-
const selectedOrg = organizations.find((org2) => org2.id === response.organizationId);
|
|
1031
|
-
console.log(import_chalk7.default.green(`
|
|
1032
|
-
\u2713 Switched to organization: ${selectedOrg?.name}
|
|
1033
|
-
`));
|
|
1034
|
-
} catch (error) {
|
|
1035
|
-
console.error(import_chalk7.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1036
|
-
process.exit(1);
|
|
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");
|
|
1037
1051
|
}
|
|
1052
|
+
return "";
|
|
1038
1053
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
const
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
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
|
|
1059
|
+
);
|
|
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
|
+
}
|
|
1057
1086
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
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
|
+
});
|
|
1097
|
+
}
|
|
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
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
});
|
|
1078
1199
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
if (!config2) {
|
|
1092
|
-
console.log(import_chalk8.default.red("No config found. Please login first."));
|
|
1093
|
-
process.exit(1);
|
|
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
|
+
});
|
|
1211
|
+
}
|
|
1094
1212
|
}
|
|
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);
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
|
-
// src/commands/tools.ts
|
|
1106
|
-
var import_chalk9 = __toESM(require("chalk"));
|
|
1107
|
-
var import_figlet = __toESM(require("figlet"));
|
|
1108
|
-
async function toolsCommand() {
|
|
1109
|
-
const banner = import_figlet.default.textSync("Replicas Machine Image", {
|
|
1110
|
-
horizontalLayout: "default",
|
|
1111
|
-
verticalLayout: "default",
|
|
1112
|
-
whitespaceBreak: true
|
|
1113
1213
|
});
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
-
]
|
|
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
|
+
}
|
|
1246
|
+
}
|
|
1163
1247
|
}
|
|
1164
|
-
];
|
|
1165
|
-
sections.forEach((section) => {
|
|
1166
|
-
console.log(import_chalk9.default.blue(section.title));
|
|
1167
|
-
section.tools.forEach((tool) => {
|
|
1168
|
-
console.log(` - ${tool}`);
|
|
1169
|
-
});
|
|
1170
|
-
console.log();
|
|
1171
1248
|
});
|
|
1249
|
+
return messages;
|
|
1172
1250
|
}
|
|
1173
1251
|
|
|
1174
|
-
// src/
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
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);
|
|
1258
|
+
}
|
|
1259
|
+
return [];
|
|
1260
|
+
}
|
|
1181
1261
|
|
|
1182
|
-
// src/lib/
|
|
1183
|
-
var
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}
|
|
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
|
+
}
|
|
1194
1274
|
}
|
|
1195
|
-
function
|
|
1196
|
-
|
|
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");
|
|
1197
1281
|
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
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()}`;
|
|
1282
|
+
function writeSSHConfig(content) {
|
|
1283
|
+
ensureSSHDir();
|
|
1284
|
+
import_fs3.default.writeFileSync(SSH_CONFIG_PATH, content, { mode: 384 });
|
|
1221
1285
|
}
|
|
1222
|
-
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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}`);
|
|
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
|
+
}
|
|
1241
1299
|
}
|
|
1242
|
-
|
|
1243
|
-
return
|
|
1244
|
-
idToken: data.id_token,
|
|
1245
|
-
accessToken: data.access_token,
|
|
1246
|
-
refreshToken: data.refresh_token
|
|
1247
|
-
};
|
|
1300
|
+
lines.push(REPLICAS_MARKER_END);
|
|
1301
|
+
return lines.join("\n");
|
|
1248
1302
|
}
|
|
1249
|
-
function
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
const state = url.searchParams.get("state");
|
|
1262
|
-
const error = url.searchParams.get("error");
|
|
1263
|
-
const errorDescription = url.searchParams.get("error_description");
|
|
1264
|
-
if (error) {
|
|
1265
|
-
const errorMessage = encodeURIComponent(`${error}: ${errorDescription || "Unknown error"}`);
|
|
1266
|
-
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
|
|
1267
|
-
res.end();
|
|
1268
|
-
server.close();
|
|
1269
|
-
reject(new Error(`OAuth error: ${error} - ${errorDescription}`));
|
|
1270
|
-
return;
|
|
1271
|
-
}
|
|
1272
|
-
if (state !== expectedState) {
|
|
1273
|
-
const errorMessage = encodeURIComponent("State parameter mismatch. Possible CSRF attack.");
|
|
1274
|
-
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
|
|
1275
|
-
res.end();
|
|
1276
|
-
server.close();
|
|
1277
|
-
reject(new Error("State mismatch - possible CSRF attack"));
|
|
1278
|
-
return;
|
|
1279
|
-
}
|
|
1280
|
-
if (!code) {
|
|
1281
|
-
const errorMessage = encodeURIComponent("No authorization code received.");
|
|
1282
|
-
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
|
|
1283
|
-
res.end();
|
|
1284
|
-
server.close();
|
|
1285
|
-
reject(new Error("No authorization code received"));
|
|
1286
|
-
return;
|
|
1287
|
-
}
|
|
1288
|
-
try {
|
|
1289
|
-
const tokens = await exchangeCodeForTokens(code, codeVerifier);
|
|
1290
|
-
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/success` });
|
|
1291
|
-
res.end();
|
|
1292
|
-
server.close();
|
|
1293
|
-
resolve(tokens);
|
|
1294
|
-
} catch (tokenError) {
|
|
1295
|
-
const errorMessage = encodeURIComponent(tokenError instanceof Error ? tokenError.message : "Unknown error");
|
|
1296
|
-
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
|
|
1297
|
-
res.end();
|
|
1298
|
-
server.close();
|
|
1299
|
-
reject(tokenError);
|
|
1300
|
-
}
|
|
1301
|
-
} else {
|
|
1302
|
-
res.writeHead(404);
|
|
1303
|
-
res.end("Not Found");
|
|
1304
|
-
}
|
|
1305
|
-
} catch (serverError) {
|
|
1306
|
-
res.writeHead(500);
|
|
1307
|
-
res.end("Internal Server Error");
|
|
1308
|
-
server.close();
|
|
1309
|
-
reject(serverError);
|
|
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;
|
|
1310
1315
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1316
|
+
result.push(line);
|
|
1317
|
+
continue;
|
|
1318
|
+
}
|
|
1319
|
+
if (line.trim() === REPLICAS_MARKER_END) {
|
|
1320
|
+
if (inTargetBlock) {
|
|
1321
|
+
inTargetBlock = false;
|
|
1322
|
+
continue;
|
|
1317
1323
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
const authUrl = buildAuthorizationUrl(pkce.codeChallenge, state);
|
|
1326
|
-
const tokensPromise = startCallbackServer(state, pkce.codeVerifier);
|
|
1327
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1328
|
-
console.log("If the browser does not open automatically, visit:");
|
|
1329
|
-
console.log(authUrl);
|
|
1330
|
-
console.log();
|
|
1331
|
-
try {
|
|
1332
|
-
await (0, import_open2.default)(authUrl);
|
|
1333
|
-
} catch (openError) {
|
|
1334
|
-
console.warn("Failed to open browser automatically. Please open the URL manually.");
|
|
1324
|
+
result.push(line);
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
if (inTargetBlock) {
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
result.push(line);
|
|
1335
1331
|
}
|
|
1336
|
-
|
|
1337
|
-
const
|
|
1338
|
-
|
|
1339
|
-
|
|
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, "-")}`;
|
|
1340
1343
|
}
|
|
1341
1344
|
|
|
1342
|
-
// src/commands/
|
|
1343
|
-
async function
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
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
|
+
}
|
|
1348
1351
|
try {
|
|
1349
|
-
const
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const response = await orgAuthenticatedFetch(
|
|
1353
|
-
endpoint,
|
|
1354
|
-
{
|
|
1355
|
-
method: "POST",
|
|
1356
|
-
body: {
|
|
1357
|
-
access_token: tokens.accessToken,
|
|
1358
|
-
refresh_token: tokens.refreshToken,
|
|
1359
|
-
id_token: tokens.idToken
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1352
|
+
const { workspace, sshToken, sshHost, portMappings, repoName } = await prepareWorkspaceConnection(
|
|
1353
|
+
workspaceName,
|
|
1354
|
+
options
|
|
1362
1355
|
);
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1356
|
+
const hostAlias = getWorkspaceHostAlias(workspace.name);
|
|
1357
|
+
console.log(import_chalk6.default.blue("\nConfiguring SSH connection..."));
|
|
1358
|
+
addOrUpdateSSHConfigEntry({
|
|
1359
|
+
host: hostAlias,
|
|
1360
|
+
hostname: sshHost,
|
|
1361
|
+
user: sshToken,
|
|
1362
|
+
portForwards: portMappings
|
|
1363
|
+
});
|
|
1364
|
+
console.log(import_chalk6.default.green(`\u2713 SSH config entry created: ${hostAlias}`));
|
|
1365
|
+
const remotePath = repoName ? `${SANDBOX_PATHS.WORKSPACES_DIR}/${repoName}` : SANDBOX_PATHS.HOME_DIR;
|
|
1366
|
+
const ideCommand = getIdeCommand();
|
|
1367
|
+
const fullCommand = `${ideCommand} --remote ssh-remote+${hostAlias} ${remotePath}`;
|
|
1368
|
+
console.log(import_chalk6.default.blue(`
|
|
1369
|
+
Opening ${ideCommand} for workspace ${workspace.name}...`));
|
|
1370
|
+
console.log(import_chalk6.default.gray(`Command: ${fullCommand}`));
|
|
1371
|
+
if (portMappings.size > 0) {
|
|
1372
|
+
console.log(import_chalk6.default.cyan("\nPort forwarding configured:"));
|
|
1373
|
+
for (const [remotePort, localPort] of portMappings) {
|
|
1374
|
+
console.log(import_chalk6.default.cyan(` \u2022 localhost:${localPort} \u2192 workspace:${remotePort}`));
|
|
1375
1375
|
}
|
|
1376
|
-
} else {
|
|
1377
|
-
throw new Error(response.error || "Failed to upload Codex credentials to Replicas");
|
|
1378
1376
|
}
|
|
1377
|
+
console.log(import_chalk6.default.yellow(`
|
|
1378
|
+
Note: SSH tokens expire after 3 hours. Run this command again to refresh.`));
|
|
1379
|
+
const ide = (0, import_child_process4.spawn)(ideCommand, [
|
|
1380
|
+
"--remote",
|
|
1381
|
+
`ssh-remote+${hostAlias}`,
|
|
1382
|
+
remotePath
|
|
1383
|
+
], {
|
|
1384
|
+
stdio: "inherit",
|
|
1385
|
+
detached: false
|
|
1386
|
+
});
|
|
1387
|
+
ide.on("error", (error) => {
|
|
1388
|
+
console.error(import_chalk6.default.red(`
|
|
1389
|
+
Failed to launch ${ideCommand}: ${error.message}`));
|
|
1390
|
+
console.log(import_chalk6.default.yellow(`
|
|
1391
|
+
Make sure ${ideCommand} is installed and available in your PATH.`));
|
|
1392
|
+
console.log(import_chalk6.default.gray(`You can configure a different IDE with: replicas config set ide <command>`));
|
|
1393
|
+
process.exit(1);
|
|
1394
|
+
});
|
|
1395
|
+
ide.on("close", (code) => {
|
|
1396
|
+
if (code === 0) {
|
|
1397
|
+
console.log(import_chalk6.default.green(`
|
|
1398
|
+
\u2713 ${ideCommand} closed successfully.
|
|
1399
|
+
`));
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1379
1402
|
} catch (error) {
|
|
1380
|
-
console.
|
|
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
|
-
}
|
|
1443
|
-
async function exchangeCodeForTokens2(code, state, expectedState, codeVerifier) {
|
|
1444
|
-
if (state && state !== expectedState) {
|
|
1445
|
-
throw new Error("State mismatch detected. Please restart the authentication flow.");
|
|
1446
|
-
}
|
|
1447
|
-
const payload = {
|
|
1448
|
-
code,
|
|
1449
|
-
state: state ?? expectedState,
|
|
1450
|
-
grant_type: "authorization_code",
|
|
1451
|
-
client_id: CLAUDE_CLIENT_ID,
|
|
1452
|
-
redirect_uri: REDIRECT_URI,
|
|
1453
|
-
code_verifier: codeVerifier
|
|
1454
|
-
};
|
|
1455
|
-
const response = await fetch(TOKEN_ENDPOINT2, {
|
|
1456
|
-
method: "POST",
|
|
1457
|
-
headers: {
|
|
1458
|
-
"Content-Type": "application/json"
|
|
1459
|
-
},
|
|
1460
|
-
body: JSON.stringify(payload)
|
|
1461
|
-
});
|
|
1462
|
-
if (!response.ok) {
|
|
1463
|
-
const text = await response.text();
|
|
1464
|
-
throw new Error(`Failed to exchange authorization code: ${response.status} ${text}`);
|
|
1465
|
-
}
|
|
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
1439
|
}
|
|
1479
|
-
async function
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
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
|
-
try {
|
|
1488
|
-
await (0, import_open3.default)(authUrl);
|
|
1489
|
-
} catch {
|
|
1490
|
-
console.warn(import_chalk11.default.yellow("Failed to open browser automatically. Please copy and open the URL manually."));
|
|
1491
|
-
}
|
|
1492
|
-
console.log(import_chalk11.default.cyan("After completing authentication, copy the code shown on the success page."));
|
|
1493
|
-
console.log(import_chalk11.default.cyan("You can paste either the full URL, or a value formatted as CODE#STATE.\n"));
|
|
1494
|
-
const userInput = await promptForAuthorizationCode("Paste the authorization code (or URL) here: ");
|
|
1495
|
-
if (!userInput) {
|
|
1496
|
-
throw new Error("No authorization code provided.");
|
|
1497
|
-
}
|
|
1498
|
-
const { code, state: returnedState } = parseUserInput(userInput);
|
|
1499
|
-
if (!code) {
|
|
1500
|
-
throw new Error("Unable to parse authorization code. Please try again.");
|
|
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);
|
|
1501
1444
|
}
|
|
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
|
-
}
|
|
1507
|
-
|
|
1508
|
-
// src/commands/claude-auth.ts
|
|
1509
|
-
async function claudeAuthCommand(options = {}) {
|
|
1510
|
-
const isUserScoped = options.user === true;
|
|
1511
|
-
const scopeLabel = isUserScoped ? "personal" : "organization";
|
|
1512
|
-
console.log(import_chalk12.default.cyan(`Authenticating with Claude Code (${scopeLabel})...
|
|
1513
|
-
`));
|
|
1514
1445
|
try {
|
|
1515
|
-
const
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
{
|
|
1521
|
-
method: "POST",
|
|
1522
|
-
body: {
|
|
1523
|
-
access_token: tokens.accessToken,
|
|
1524
|
-
refresh_token: tokens.refreshToken,
|
|
1525
|
-
token_type: tokens.tokenType,
|
|
1526
|
-
scope: tokens.scope,
|
|
1527
|
-
expires_at: tokens.expiresAt,
|
|
1528
|
-
refresh_token_expires_at: tokens.refreshTokenExpiresAt
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
);
|
|
1532
|
-
if (!response.success) {
|
|
1533
|
-
throw new Error(response.error || "Failed to upload Claude credentials to Replicas");
|
|
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;
|
|
1534
1451
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
console.log(
|
|
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;
|
|
1538
1458
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
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;
|
|
1541
1474
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
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);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
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
|
+
}
|
|
1493
|
+
try {
|
|
1494
|
+
if (key === "ide") {
|
|
1495
|
+
const ideCommand = getIdeCommand();
|
|
1496
|
+
console.log(import_chalk8.default.green(`
|
|
1497
|
+
IDE command: ${ideCommand}
|
|
1498
|
+
`));
|
|
1544
1499
|
} else {
|
|
1545
|
-
console.log(
|
|
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);
|
|
1546
1503
|
}
|
|
1547
1504
|
} catch (error) {
|
|
1548
|
-
console.
|
|
1505
|
+
console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1549
1506
|
process.exit(1);
|
|
1550
1507
|
}
|
|
1551
1508
|
}
|
|
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;
|
|
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);
|
|
1579
1513
|
}
|
|
1580
|
-
const defaultConfig = getDefaultConfig();
|
|
1581
|
-
const configContent = JSON.stringify(defaultConfig, null, 2);
|
|
1582
1514
|
try {
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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
|
-
);
|
|
1515
|
+
if (key === "ide") {
|
|
1516
|
+
setIdeCommand(value);
|
|
1517
|
+
console.log(import_chalk8.default.green(`
|
|
1518
|
+
\u2713 IDE command set to: ${value}
|
|
1519
|
+
`));
|
|
1520
|
+
} else {
|
|
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);
|
|
1524
|
+
}
|
|
1599
1525
|
} catch (error) {
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
);
|
|
1526
|
+
console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1527
|
+
process.exit(1);
|
|
1603
1528
|
}
|
|
1604
1529
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
var VERSION_CHECK_TIMEOUT = 2e3;
|
|
1610
|
-
function compareVersions(v1, v2) {
|
|
1611
|
-
const parts1 = v1.split(".").map(Number);
|
|
1612
|
-
const parts2 = v2.split(".").map(Number);
|
|
1613
|
-
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
1614
|
-
const num1 = parts1[i] || 0;
|
|
1615
|
-
const num2 = parts2[i] || 0;
|
|
1616
|
-
if (num1 > num2) return 1;
|
|
1617
|
-
if (num1 < num2) return -1;
|
|
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);
|
|
1618
1534
|
}
|
|
1619
|
-
return 0;
|
|
1620
|
-
}
|
|
1621
|
-
async function checkForUpdates(currentVersion) {
|
|
1622
1535
|
try {
|
|
1623
|
-
const
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
});
|
|
1628
|
-
clearTimeout(timeoutId);
|
|
1629
|
-
if (!response.ok) return;
|
|
1630
|
-
const data = await response.json();
|
|
1631
|
-
const latestVersion = data.version;
|
|
1632
|
-
if (compareVersions(latestVersion, currentVersion) > 0) {
|
|
1633
|
-
console.error(import_chalk14.default.gray(`
|
|
1634
|
-
Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
1635
|
-
console.error(import_chalk14.default.gray(`Run: npm i -g replicas-cli@latest
|
|
1636
|
-
`));
|
|
1536
|
+
const config2 = readConfig();
|
|
1537
|
+
if (!config2) {
|
|
1538
|
+
console.log(import_chalk8.default.red("No config found. Please login first."));
|
|
1539
|
+
process.exit(1);
|
|
1637
1540
|
}
|
|
1638
|
-
|
|
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();
|
|
1545
|
+
} catch (error) {
|
|
1546
|
+
console.error(import_chalk8.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1547
|
+
process.exit(1);
|
|
1639
1548
|
}
|
|
1640
1549
|
}
|
|
1641
1550
|
|
|
1642
|
-
// src/commands/
|
|
1643
|
-
var
|
|
1644
|
-
var
|
|
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
|
+
]
|
|
1609
|
+
}
|
|
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
|
+
});
|
|
1618
|
+
}
|
|
1645
1619
|
|
|
1646
|
-
//
|
|
1647
|
-
var
|
|
1648
|
-
AUTO_STOP_MINUTES: 60,
|
|
1649
|
-
AUTO_ARCHIVE_MINUTES: 60 * 24 * 7,
|
|
1650
|
-
AUTO_DELETE_MINUTES: -1,
|
|
1651
|
-
SSH_TOKEN_EXPIRATION_MINUTES: 3 * 60
|
|
1652
|
-
};
|
|
1620
|
+
// src/commands/codex-auth.ts
|
|
1621
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
1653
1622
|
|
|
1654
|
-
//
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
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"));
|
|
1627
|
+
|
|
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
|
+
};
|
|
1661
1640
|
}
|
|
1662
|
-
function
|
|
1663
|
-
return
|
|
1641
|
+
function generateState2() {
|
|
1642
|
+
return (0, import_crypto.randomBytes)(16).toString("hex");
|
|
1664
1643
|
}
|
|
1665
|
-
|
|
1666
|
-
|
|
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
1667
|
}
|
|
1668
|
-
function
|
|
1669
|
-
const
|
|
1670
|
-
const
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
|
|
1688
|
-
operations.push(currentOp);
|
|
1689
|
-
diffLines = [];
|
|
1690
|
-
}
|
|
1691
|
-
currentOp = {
|
|
1692
|
-
action: "update",
|
|
1693
|
-
path: line.replace("*** Update File:", "").trim()
|
|
1694
|
-
};
|
|
1695
|
-
} else if (line.startsWith("*** Delete File:")) {
|
|
1696
|
-
if (currentOp) {
|
|
1697
|
-
if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
|
|
1698
|
-
operations.push(currentOp);
|
|
1699
|
-
diffLines = [];
|
|
1700
|
-
}
|
|
1701
|
-
currentOp = {
|
|
1702
|
-
action: "delete",
|
|
1703
|
-
path: line.replace("*** Delete File:", "").trim()
|
|
1704
|
-
};
|
|
1705
|
-
} else if (line.startsWith("*** Move to:")) {
|
|
1706
|
-
if (currentOp) {
|
|
1707
|
-
currentOp.moveTo = line.replace("*** Move to:", "").trim();
|
|
1708
|
-
}
|
|
1709
|
-
} else if (line.startsWith("*** Begin Patch") || line.startsWith("*** End Patch")) {
|
|
1710
|
-
continue;
|
|
1711
|
-
} else if (currentOp && (line.startsWith("+") || line.startsWith("-") || line.startsWith("@@") || line.trim() === "")) {
|
|
1712
|
-
diffLines.push(line);
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
if (currentOp) {
|
|
1716
|
-
if (diffLines.length > 0) currentOp.diff = diffLines.join("\n");
|
|
1717
|
-
operations.push(currentOp);
|
|
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}`);
|
|
1718
1687
|
}
|
|
1719
|
-
|
|
1688
|
+
const data = await response.json();
|
|
1689
|
+
return {
|
|
1690
|
+
idToken: data.id_token,
|
|
1691
|
+
accessToken: data.access_token,
|
|
1692
|
+
refreshToken: data.refresh_token
|
|
1693
|
+
};
|
|
1720
1694
|
}
|
|
1721
|
-
function
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
content: event.payload.message || "",
|
|
1730
|
-
timestamp: event.timestamp
|
|
1731
|
-
});
|
|
1732
|
-
}
|
|
1733
|
-
if (event.type === "event_msg" && event.payload?.type === "agent_reasoning") {
|
|
1734
|
-
messages.push({
|
|
1735
|
-
id: `reasoning-${event.timestamp}-${messages.length}`,
|
|
1736
|
-
type: "reasoning",
|
|
1737
|
-
content: event.payload.text || "",
|
|
1738
|
-
status: "completed",
|
|
1739
|
-
timestamp: event.timestamp
|
|
1740
|
-
});
|
|
1741
|
-
}
|
|
1742
|
-
if (event.type === "response_item") {
|
|
1743
|
-
const payloadType = event.payload?.type;
|
|
1744
|
-
if (payloadType === "message" && event.payload?.role === "assistant") {
|
|
1745
|
-
const content = event.payload.content || [];
|
|
1746
|
-
const textContent = content.filter((c) => c.type === "output_text").map((c) => c.text || "").join("\n");
|
|
1747
|
-
if (textContent) {
|
|
1748
|
-
messages.push({
|
|
1749
|
-
id: `agent-${event.timestamp}`,
|
|
1750
|
-
type: "agent",
|
|
1751
|
-
content: textContent,
|
|
1752
|
-
timestamp: event.timestamp
|
|
1753
|
-
});
|
|
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;
|
|
1754
1703
|
}
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1704
|
+
const url = new import_url2.URL(req.url, `http://127.0.0.1:${CALLBACK_PORT}`);
|
|
1705
|
+
if (url.pathname === CALLBACK_PATH) {
|
|
1706
|
+
const code = url.searchParams.get("code");
|
|
1707
|
+
const state = url.searchParams.get("state");
|
|
1708
|
+
const error = url.searchParams.get("error");
|
|
1709
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
1710
|
+
if (error) {
|
|
1711
|
+
const errorMessage = encodeURIComponent(`${error}: ${errorDescription || "Unknown error"}`);
|
|
1712
|
+
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
|
|
1713
|
+
res.end();
|
|
1714
|
+
server.close();
|
|
1715
|
+
reject(new Error(`OAuth error: ${error} - ${errorDescription}`));
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
if (state !== expectedState) {
|
|
1719
|
+
const errorMessage = encodeURIComponent("State parameter mismatch. Possible CSRF attack.");
|
|
1720
|
+
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
|
|
1721
|
+
res.end();
|
|
1722
|
+
server.close();
|
|
1723
|
+
reject(new Error("State mismatch - possible CSRF attack"));
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
if (!code) {
|
|
1727
|
+
const errorMessage = encodeURIComponent("No authorization code received.");
|
|
1728
|
+
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
|
|
1729
|
+
res.end();
|
|
1730
|
+
server.close();
|
|
1731
|
+
reject(new Error("No authorization code received"));
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
try {
|
|
1735
|
+
const tokens = await exchangeCodeForTokens(code, codeVerifier);
|
|
1736
|
+
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/success` });
|
|
1737
|
+
res.end();
|
|
1738
|
+
server.close();
|
|
1739
|
+
resolve(tokens);
|
|
1740
|
+
} catch (tokenError) {
|
|
1741
|
+
const errorMessage = encodeURIComponent(tokenError instanceof Error ? tokenError.message : "Unknown error");
|
|
1742
|
+
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
|
|
1743
|
+
res.end();
|
|
1744
|
+
server.close();
|
|
1745
|
+
reject(tokenError);
|
|
1780
1746
|
}
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
if (payloadType === "custom_tool_call") {
|
|
1784
|
-
const callId = event.payload.call_id;
|
|
1785
|
-
const name = event.payload.name;
|
|
1786
|
-
const input = event.payload.input || "";
|
|
1787
|
-
const status = event.payload.status || "in_progress";
|
|
1788
|
-
if (name === "apply_patch") {
|
|
1789
|
-
const operations = parsePatch(input);
|
|
1790
|
-
pendingPatches.set(callId, { input, status, timestamp: event.timestamp, operations });
|
|
1791
1747
|
} else {
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
type: "tool_call",
|
|
1795
|
-
server: "custom",
|
|
1796
|
-
tool: name,
|
|
1797
|
-
status,
|
|
1798
|
-
timestamp: event.timestamp
|
|
1799
|
-
});
|
|
1748
|
+
res.writeHead(404);
|
|
1749
|
+
res.end("Not Found");
|
|
1800
1750
|
}
|
|
1751
|
+
} catch (serverError) {
|
|
1752
|
+
res.writeHead(500);
|
|
1753
|
+
res.end("Internal Server Error");
|
|
1754
|
+
server.close();
|
|
1755
|
+
reject(serverError);
|
|
1801
1756
|
}
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
);
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1757
|
+
});
|
|
1758
|
+
server.on("error", (err) => {
|
|
1759
|
+
if (err.code === "EADDRINUSE") {
|
|
1760
|
+
reject(new Error(`Port ${CALLBACK_PORT} is already in use. Please close any other OAuth flows and try again.`));
|
|
1761
|
+
} else {
|
|
1762
|
+
reject(err);
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
server.listen(CALLBACK_PORT, "127.0.0.1");
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
async function runCodexOAuthFlow() {
|
|
1769
|
+
const pkce = generatePkceCodes();
|
|
1770
|
+
const state = generateState2();
|
|
1771
|
+
const authUrl = buildAuthorizationUrl(pkce.codeChallenge, state);
|
|
1772
|
+
const tokensPromise = startCallbackServer(state, pkce.codeVerifier);
|
|
1773
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1774
|
+
console.log("If the browser does not open automatically, visit:");
|
|
1775
|
+
console.log(authUrl);
|
|
1776
|
+
console.log();
|
|
1777
|
+
try {
|
|
1778
|
+
await (0, import_open2.default)(authUrl);
|
|
1779
|
+
} catch (openError) {
|
|
1780
|
+
console.warn("Failed to open browser automatically. Please open the URL manually.");
|
|
1781
|
+
}
|
|
1782
|
+
console.log("Waiting for authentication...");
|
|
1783
|
+
const tokens = await tokensPromise;
|
|
1784
|
+
console.log("\u2713 Successfully obtained Codex credentials");
|
|
1785
|
+
return tokens;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// src/commands/codex-auth.ts
|
|
1789
|
+
async function codexAuthCommand(options = {}) {
|
|
1790
|
+
const isUserScoped = options.user === true;
|
|
1791
|
+
const scopeLabel = isUserScoped ? "personal" : "organization";
|
|
1792
|
+
console.log(import_chalk10.default.cyan(`Authenticating with Codex (${scopeLabel})...
|
|
1793
|
+
`));
|
|
1794
|
+
try {
|
|
1795
|
+
const tokens = await runCodexOAuthFlow();
|
|
1796
|
+
console.log(import_chalk10.default.gray("\nUploading credentials to Replicas..."));
|
|
1797
|
+
const endpoint = isUserScoped ? "/v1/codex/user/credentials" : "/v1/codex/credentials";
|
|
1798
|
+
const response = await orgAuthenticatedFetch(
|
|
1799
|
+
endpoint,
|
|
1800
|
+
{
|
|
1801
|
+
method: "POST",
|
|
1802
|
+
body: {
|
|
1803
|
+
access_token: tokens.accessToken,
|
|
1804
|
+
refresh_token: tokens.refreshToken,
|
|
1805
|
+
id_token: tokens.idToken
|
|
1825
1806
|
}
|
|
1826
1807
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
);
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
items: todoItems,
|
|
1841
|
-
status: "completed",
|
|
1842
|
-
timestamp: event.timestamp
|
|
1843
|
-
});
|
|
1844
|
-
}
|
|
1808
|
+
);
|
|
1809
|
+
if (response.success) {
|
|
1810
|
+
console.log(import_chalk10.default.green("\n\u2713 Codex authentication complete!"));
|
|
1811
|
+
if (response.email) {
|
|
1812
|
+
console.log(import_chalk10.default.gray(` Account: ${response.email}`));
|
|
1813
|
+
}
|
|
1814
|
+
if (response.planType) {
|
|
1815
|
+
console.log(import_chalk10.default.gray(` Plan: ${response.planType}`));
|
|
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"));
|
|
1845
1821
|
}
|
|
1822
|
+
} else {
|
|
1823
|
+
throw new Error(response.error || "Failed to upload Codex credentials to Replicas");
|
|
1846
1824
|
}
|
|
1847
|
-
})
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
// ../shared/src/display-message/parsers/claude-parser.ts
|
|
1852
|
-
function safeJsonParse2(str, fallback) {
|
|
1853
|
-
try {
|
|
1854
|
-
return JSON.parse(str);
|
|
1855
|
-
} catch {
|
|
1856
|
-
return fallback;
|
|
1825
|
+
} catch (error) {
|
|
1826
|
+
console.log(import_chalk10.default.red("\n\u2717 Error:"), error instanceof Error ? error.message : error);
|
|
1827
|
+
process.exit(1);
|
|
1857
1828
|
}
|
|
1858
1829
|
}
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
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()}`;
|
|
1866
1856
|
}
|
|
1867
|
-
function
|
|
1868
|
-
const
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
)
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
if (
|
|
1891
|
-
|
|
1892
|
-
id: `user-${event.timestamp}`,
|
|
1893
|
-
type: "user",
|
|
1894
|
-
content: textContent || (images.length > 0 ? `[${images.length} image${images.length > 1 ? "s" : ""} attached]` : ""),
|
|
1895
|
-
images: images.length > 0 ? images : void 0,
|
|
1896
|
-
timestamp: event.timestamp
|
|
1897
|
-
});
|
|
1898
|
-
}
|
|
1899
|
-
}
|
|
1900
|
-
if (event.type === "claude-assistant") {
|
|
1901
|
-
const contentBlocks = event.payload.message?.content || [];
|
|
1902
|
-
contentBlocks.forEach((block) => {
|
|
1903
|
-
if (block.type === "text" && block.text) {
|
|
1904
|
-
messages.push({
|
|
1905
|
-
id: `agent-${event.timestamp}-${messages.length}`,
|
|
1906
|
-
type: "agent",
|
|
1907
|
-
content: block.text,
|
|
1908
|
-
timestamp: event.timestamp
|
|
1909
|
-
});
|
|
1910
|
-
}
|
|
1911
|
-
if (block.type === "thinking" && block.text) {
|
|
1912
|
-
messages.push({
|
|
1913
|
-
id: `reasoning-${event.timestamp}-${messages.length}`,
|
|
1914
|
-
type: "reasoning",
|
|
1915
|
-
content: block.text,
|
|
1916
|
-
status: "completed",
|
|
1917
|
-
timestamp: event.timestamp
|
|
1918
|
-
});
|
|
1919
|
-
}
|
|
1920
|
-
if (block.type === "tool_use" && block.id) {
|
|
1921
|
-
const toolName = block.name || "unknown";
|
|
1922
|
-
const toolInput = block.input || {};
|
|
1923
|
-
const toolUseId = block.id;
|
|
1924
|
-
toolCallMap.set(toolUseId, {
|
|
1925
|
-
messageIndex: messages.length,
|
|
1926
|
-
toolName,
|
|
1927
|
-
input: toolInput
|
|
1928
|
-
});
|
|
1929
|
-
if (toolName === "Task") {
|
|
1930
|
-
const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
|
|
1931
|
-
const nestedEvents = events.filter((e) => e.payload.parent_tool_use_id === toolUseId).map((e) => ({
|
|
1932
|
-
timestamp: e.timestamp,
|
|
1933
|
-
type: e.type,
|
|
1934
|
-
payload: e.payload
|
|
1935
|
-
}));
|
|
1936
|
-
messages.push({
|
|
1937
|
-
id: `subagent-${event.timestamp}-${messages.length}`,
|
|
1938
|
-
type: "subagent",
|
|
1939
|
-
toolUseId,
|
|
1940
|
-
description: inputObj.description || "Subagent Task",
|
|
1941
|
-
prompt: inputObj.prompt || "",
|
|
1942
|
-
subagentType: inputObj.subagent_type || "general",
|
|
1943
|
-
model: inputObj.model,
|
|
1944
|
-
status: "in_progress",
|
|
1945
|
-
nestedEvents,
|
|
1946
|
-
timestamp: event.timestamp
|
|
1947
|
-
});
|
|
1948
|
-
} else if (toolName === "Bash" || toolName === "bash" || toolName === "shell") {
|
|
1949
|
-
const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
|
|
1950
|
-
const command = inputObj.command || "";
|
|
1951
|
-
messages.push({
|
|
1952
|
-
id: `command-${event.timestamp}-${messages.length}`,
|
|
1953
|
-
type: "command",
|
|
1954
|
-
command,
|
|
1955
|
-
output: "",
|
|
1956
|
-
status: "in_progress",
|
|
1957
|
-
timestamp: event.timestamp
|
|
1958
|
-
});
|
|
1959
|
-
} else if (toolName === "Write" || toolName === "Edit") {
|
|
1960
|
-
const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
|
|
1961
|
-
const filePath = inputObj.file_path || "";
|
|
1962
|
-
const action = toolName === "Write" ? "add" : "update";
|
|
1963
|
-
messages.push({
|
|
1964
|
-
id: `file-${event.timestamp}-${messages.length}`,
|
|
1965
|
-
type: "file_change",
|
|
1966
|
-
changes: [{
|
|
1967
|
-
path: filePath,
|
|
1968
|
-
kind: action
|
|
1969
|
-
}],
|
|
1970
|
-
status: "in_progress",
|
|
1971
|
-
timestamp: event.timestamp
|
|
1972
|
-
});
|
|
1973
|
-
} else if (toolName === "TodoWrite") {
|
|
1974
|
-
const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
|
|
1975
|
-
const todos = inputObj.todos || [];
|
|
1976
|
-
const todoItems = todos.map((todo) => ({
|
|
1977
|
-
text: todo.content,
|
|
1978
|
-
completed: todo.status === "completed"
|
|
1979
|
-
}));
|
|
1980
|
-
if (todoItems.length > 0) {
|
|
1981
|
-
messages.push({
|
|
1982
|
-
id: `todo-${event.timestamp}-${messages.length}`,
|
|
1983
|
-
type: "todo_list",
|
|
1984
|
-
items: todoItems,
|
|
1985
|
-
status: "completed",
|
|
1986
|
-
timestamp: event.timestamp
|
|
1987
|
-
});
|
|
1988
|
-
}
|
|
1989
|
-
} else if (toolName === "WebSearch") {
|
|
1990
|
-
const inputObj = typeof toolInput === "string" ? safeJsonParse2(toolInput, {}) : toolInput;
|
|
1991
|
-
const query = inputObj.query || "";
|
|
1992
|
-
messages.push({
|
|
1993
|
-
id: `search-${event.timestamp}-${messages.length}`,
|
|
1994
|
-
type: "web_search",
|
|
1995
|
-
query,
|
|
1996
|
-
status: "in_progress",
|
|
1997
|
-
timestamp: event.timestamp
|
|
1998
|
-
});
|
|
1999
|
-
} else {
|
|
2000
|
-
messages.push({
|
|
2001
|
-
id: `toolcall-${event.timestamp}-${messages.length}`,
|
|
2002
|
-
type: "tool_call",
|
|
2003
|
-
server: "claude",
|
|
2004
|
-
tool: toolName,
|
|
2005
|
-
input: toolInput,
|
|
2006
|
-
status: "in_progress",
|
|
2007
|
-
timestamp: event.timestamp
|
|
2008
|
-
});
|
|
2009
|
-
}
|
|
2010
|
-
}
|
|
2011
|
-
});
|
|
2012
|
-
}
|
|
2013
|
-
if (event.type === "claude-result") {
|
|
2014
|
-
const payload = event.payload;
|
|
2015
|
-
if (payload.is_error || payload.subtype !== "success") {
|
|
2016
|
-
const errorList = payload.errors || [];
|
|
2017
|
-
const errorMessage = errorList.length > 0 ? errorList.join("\n") : "Claude session encountered an unexpected error.";
|
|
2018
|
-
messages.push({
|
|
2019
|
-
id: `error-${event.timestamp}`,
|
|
2020
|
-
type: "error",
|
|
2021
|
-
message: errorMessage,
|
|
2022
|
-
timestamp: event.timestamp
|
|
2023
|
-
});
|
|
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 };
|
|
2024
1882
|
}
|
|
1883
|
+
} catch {
|
|
2025
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)
|
|
2026
1907
|
});
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
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
|
|
2058
1975
|
}
|
|
2059
1976
|
}
|
|
1977
|
+
);
|
|
1978
|
+
if (!response.success) {
|
|
1979
|
+
throw new Error(response.error || "Failed to upload Claude credentials to Replicas");
|
|
2060
1980
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
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
|
+
}
|
|
2063
1997
|
}
|
|
2064
1998
|
|
|
2065
|
-
//
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
1999
|
+
// src/commands/init.ts
|
|
2000
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
2001
|
+
var import_fs4 = __toESM(require("fs"));
|
|
2002
|
+
var import_path5 = __toESM(require("path"));
|
|
2003
|
+
var REPLICAS_CONFIG_FILENAME = "replicas.json";
|
|
2004
|
+
function getDefaultConfig() {
|
|
2005
|
+
return {
|
|
2006
|
+
copy: [],
|
|
2007
|
+
ports: [],
|
|
2008
|
+
systemPrompt: "",
|
|
2009
|
+
startHook: {
|
|
2010
|
+
timeout: 3e5,
|
|
2011
|
+
commands: []
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
}
|
|
2015
|
+
function initCommand(options) {
|
|
2016
|
+
const configPath = import_path5.default.join(process.cwd(), REPLICAS_CONFIG_FILENAME);
|
|
2017
|
+
if (import_fs4.default.existsSync(configPath) && !options.force) {
|
|
2018
|
+
console.log(
|
|
2019
|
+
import_chalk13.default.yellow(
|
|
2020
|
+
`${REPLICAS_CONFIG_FILENAME} already exists in this directory.`
|
|
2021
|
+
)
|
|
2022
|
+
);
|
|
2023
|
+
console.log(import_chalk13.default.gray("Use --force to overwrite the existing file."));
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
const defaultConfig = getDefaultConfig();
|
|
2027
|
+
const configContent = JSON.stringify(defaultConfig, null, 2);
|
|
2028
|
+
try {
|
|
2029
|
+
import_fs4.default.writeFileSync(configPath, configContent + "\n", "utf-8");
|
|
2030
|
+
console.log(import_chalk13.default.green(`\u2713 Created ${REPLICAS_CONFIG_FILENAME}`));
|
|
2031
|
+
console.log("");
|
|
2032
|
+
console.log(import_chalk13.default.gray("Configuration options:"));
|
|
2033
|
+
console.log(
|
|
2034
|
+
import_chalk13.default.gray(' copy - Files to sync to the workspace (e.g., [".env"])')
|
|
2035
|
+
);
|
|
2036
|
+
console.log(
|
|
2037
|
+
import_chalk13.default.gray(" ports - Ports to forward from the workspace (e.g., [3000, 8080])")
|
|
2038
|
+
);
|
|
2039
|
+
console.log(
|
|
2040
|
+
import_chalk13.default.gray(" systemPrompt - Custom instructions for AI coding assistants")
|
|
2041
|
+
);
|
|
2042
|
+
console.log(
|
|
2043
|
+
import_chalk13.default.gray(" startHook - Commands to run on workspace startup")
|
|
2044
|
+
);
|
|
2045
|
+
} catch (error) {
|
|
2046
|
+
throw new Error(
|
|
2047
|
+
`Failed to create ${REPLICAS_CONFIG_FILENAME}: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
// src/lib/version-check.ts
|
|
2053
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
2054
|
+
var MONOLITH_URL2 = process.env.REPLICAS_MONOLITH_URL || "https://api.replicas.dev";
|
|
2055
|
+
var VERSION_CHECK_TIMEOUT = 2e3;
|
|
2056
|
+
function compareVersions(v1, v2) {
|
|
2057
|
+
const parts1 = v1.split(".").map(Number);
|
|
2058
|
+
const parts2 = v2.split(".").map(Number);
|
|
2059
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
2060
|
+
const num1 = parts1[i] || 0;
|
|
2061
|
+
const num2 = parts2[i] || 0;
|
|
2062
|
+
if (num1 > num2) return 1;
|
|
2063
|
+
if (num1 < num2) return -1;
|
|
2064
|
+
}
|
|
2065
|
+
return 0;
|
|
2066
|
+
}
|
|
2067
|
+
async function checkForUpdates(currentVersion) {
|
|
2068
|
+
try {
|
|
2069
|
+
const controller = new AbortController();
|
|
2070
|
+
const timeoutId = setTimeout(() => controller.abort(), VERSION_CHECK_TIMEOUT);
|
|
2071
|
+
const response = await fetch(`${MONOLITH_URL2}/v1/cli/version`, {
|
|
2072
|
+
signal: controller.signal
|
|
2073
|
+
});
|
|
2074
|
+
clearTimeout(timeoutId);
|
|
2075
|
+
if (!response.ok) return;
|
|
2076
|
+
const data = await response.json();
|
|
2077
|
+
const latestVersion = data.version;
|
|
2078
|
+
if (compareVersions(latestVersion, currentVersion) > 0) {
|
|
2079
|
+
console.error(import_chalk14.default.gray(`
|
|
2080
|
+
Update available: ${currentVersion} \u2192 ${latestVersion}`));
|
|
2081
|
+
console.error(import_chalk14.default.gray(`Run: npm i -g replicas-cli@latest
|
|
2082
|
+
`));
|
|
2083
|
+
}
|
|
2084
|
+
} catch {
|
|
2071
2085
|
}
|
|
2072
|
-
return [];
|
|
2073
2086
|
}
|
|
2074
2087
|
|
|
2075
2088
|
// src/commands/replica.ts
|
|
2089
|
+
var import_chalk15 = __toESM(require("chalk"));
|
|
2090
|
+
var import_prompts3 = __toESM(require("prompts"));
|
|
2076
2091
|
function formatDate(dateString) {
|
|
2077
2092
|
return new Date(dateString).toLocaleString();
|
|
2078
2093
|
}
|
|
@@ -2195,15 +2210,15 @@ Replicas (Page ${response.page} of ${response.totalPages}, Total: ${response.tot
|
|
|
2195
2210
|
for (const replica of response.replicas) {
|
|
2196
2211
|
console.log(import_chalk15.default.white(` ${replica.name}`));
|
|
2197
2212
|
console.log(import_chalk15.default.gray(` ID: ${replica.id}`));
|
|
2198
|
-
if (replica.
|
|
2199
|
-
console.log(import_chalk15.default.gray(`
|
|
2213
|
+
if (replica.repositories.length > 0) {
|
|
2214
|
+
console.log(import_chalk15.default.gray(` Repositories: ${replica.repositories.map((repository) => repository.name).join(", ")}`));
|
|
2200
2215
|
}
|
|
2201
2216
|
console.log(import_chalk15.default.gray(` Status: ${formatStatus(replica.status)}`));
|
|
2202
2217
|
console.log(import_chalk15.default.gray(` Created: ${formatDate(replica.created_at)}`));
|
|
2203
2218
|
if (replica.pull_requests && replica.pull_requests.length > 0) {
|
|
2204
2219
|
console.log(import_chalk15.default.gray(` Pull Requests:`));
|
|
2205
2220
|
for (const pr of replica.pull_requests) {
|
|
2206
|
-
console.log(import_chalk15.default.cyan(` - #${pr.number}: ${pr.url}`));
|
|
2221
|
+
console.log(import_chalk15.default.cyan(` - ${pr.repository} #${pr.number}: ${pr.url}`));
|
|
2207
2222
|
}
|
|
2208
2223
|
}
|
|
2209
2224
|
console.log();
|
|
@@ -2225,8 +2240,8 @@ async function replicaGetCommand(id) {
|
|
|
2225
2240
|
Replica: ${replica.name}
|
|
2226
2241
|
`));
|
|
2227
2242
|
console.log(import_chalk15.default.gray(` ID: ${replica.id}`));
|
|
2228
|
-
if (replica.
|
|
2229
|
-
console.log(import_chalk15.default.gray(`
|
|
2243
|
+
if (replica.repositories.length > 0) {
|
|
2244
|
+
console.log(import_chalk15.default.gray(` Repositories: ${replica.repositories.map((repository) => repository.name).join(", ")}`));
|
|
2230
2245
|
}
|
|
2231
2246
|
console.log(import_chalk15.default.gray(` Status: ${formatStatus(replica.status)}`));
|
|
2232
2247
|
console.log(import_chalk15.default.gray(` Created: ${formatDate(replica.created_at)}`));
|
|
@@ -2236,17 +2251,18 @@ Replica: ${replica.name}
|
|
|
2236
2251
|
if (replica.coding_agent) {
|
|
2237
2252
|
console.log(import_chalk15.default.gray(` Coding Agent: ${replica.coding_agent}`));
|
|
2238
2253
|
}
|
|
2239
|
-
if (replica.
|
|
2240
|
-
console.log(import_chalk15.default.gray(
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2254
|
+
if (replica.repository_statuses && replica.repository_statuses.length > 0) {
|
|
2255
|
+
console.log(import_chalk15.default.gray(" Repository Statuses:"));
|
|
2256
|
+
for (const repositoryStatus of replica.repository_statuses) {
|
|
2257
|
+
const changeText = repositoryStatus.git_diff ? ` (${import_chalk15.default.green(`+${repositoryStatus.git_diff.added}`)} / ${import_chalk15.default.red(`-${repositoryStatus.git_diff.removed}`)})` : "";
|
|
2258
|
+
console.log(import_chalk15.default.gray(` - ${repositoryStatus.repository}: ${repositoryStatus.branch || "unknown"}${changeText}`));
|
|
2259
|
+
}
|
|
2244
2260
|
}
|
|
2245
2261
|
}
|
|
2246
2262
|
if (replica.pull_requests && replica.pull_requests.length > 0) {
|
|
2247
2263
|
console.log(import_chalk15.default.gray(` Pull Requests:`));
|
|
2248
2264
|
for (const pr of replica.pull_requests) {
|
|
2249
|
-
console.log(import_chalk15.default.cyan(` - #${pr.number}: ${pr.url}`));
|
|
2265
|
+
console.log(import_chalk15.default.cyan(` - ${pr.repository} #${pr.number}: ${pr.url}`));
|
|
2250
2266
|
}
|
|
2251
2267
|
}
|
|
2252
2268
|
console.log();
|
|
@@ -2269,7 +2285,7 @@ async function replicaCreateCommand(name, options) {
|
|
|
2269
2285
|
}
|
|
2270
2286
|
let replicaName = name;
|
|
2271
2287
|
let message = options.message;
|
|
2272
|
-
let
|
|
2288
|
+
let selectedRepositories = options.repositories?.split(",").map((repository) => repository.trim()).filter((repository) => repository.length > 0);
|
|
2273
2289
|
let codingAgent = options.agent;
|
|
2274
2290
|
if (replicaName && /\s/.test(replicaName)) {
|
|
2275
2291
|
console.log(import_chalk15.default.red("Replica name cannot contain spaces."));
|
|
@@ -2292,22 +2308,23 @@ async function replicaCreateCommand(name, options) {
|
|
|
2292
2308
|
}
|
|
2293
2309
|
replicaName = response2.name;
|
|
2294
2310
|
}
|
|
2295
|
-
if (!
|
|
2311
|
+
if (!selectedRepositories || selectedRepositories.length === 0) {
|
|
2296
2312
|
const response2 = await (0, import_prompts3.default)({
|
|
2297
|
-
type: "
|
|
2298
|
-
name: "
|
|
2299
|
-
message: "Select
|
|
2313
|
+
type: "multiselect",
|
|
2314
|
+
name: "repositories",
|
|
2315
|
+
message: "Select repositories:",
|
|
2300
2316
|
choices: repositories.map((repo) => ({
|
|
2301
2317
|
title: repo.name,
|
|
2302
2318
|
value: repo.name,
|
|
2303
2319
|
description: repo.url
|
|
2304
|
-
}))
|
|
2320
|
+
})),
|
|
2321
|
+
min: 1
|
|
2305
2322
|
});
|
|
2306
|
-
if (!response2.
|
|
2323
|
+
if (!response2.repositories || response2.repositories.length === 0) {
|
|
2307
2324
|
console.log(import_chalk15.default.yellow("\nCancelled."));
|
|
2308
2325
|
return;
|
|
2309
2326
|
}
|
|
2310
|
-
|
|
2327
|
+
selectedRepositories = response2.repositories;
|
|
2311
2328
|
}
|
|
2312
2329
|
if (!message) {
|
|
2313
2330
|
const response2 = await (0, import_prompts3.default)({
|
|
@@ -2346,7 +2363,7 @@ async function replicaCreateCommand(name, options) {
|
|
|
2346
2363
|
const body = {
|
|
2347
2364
|
name: replicaName,
|
|
2348
2365
|
message,
|
|
2349
|
-
|
|
2366
|
+
repositories: selectedRepositories,
|
|
2350
2367
|
coding_agent: codingAgent
|
|
2351
2368
|
};
|
|
2352
2369
|
console.log(import_chalk15.default.gray("\nCreating replica..."));
|
|
@@ -2359,6 +2376,9 @@ async function replicaCreateCommand(name, options) {
|
|
|
2359
2376
|
Created replica: ${replica.name}`));
|
|
2360
2377
|
console.log(import_chalk15.default.gray(` ID: ${replica.id}`));
|
|
2361
2378
|
console.log(import_chalk15.default.gray(` Status: ${formatStatus(replica.status)}`));
|
|
2379
|
+
if (replica.repositories.length > 0) {
|
|
2380
|
+
console.log(import_chalk15.default.gray(` Repositories: ${replica.repositories.map((repository) => repository.name).join(", ")}`));
|
|
2381
|
+
}
|
|
2362
2382
|
console.log();
|
|
2363
2383
|
} catch (error) {
|
|
2364
2384
|
console.error(import_chalk15.default.red(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
@@ -2529,7 +2549,7 @@ Repositories (${response.repositories.length}):
|
|
|
2529
2549
|
}
|
|
2530
2550
|
|
|
2531
2551
|
// src/index.ts
|
|
2532
|
-
var CLI_VERSION = "0.2.
|
|
2552
|
+
var CLI_VERSION = "0.2.32";
|
|
2533
2553
|
var program = new import_commander.Command();
|
|
2534
2554
|
program.name("replicas").description("CLI for managing Replicas workspaces").version(CLI_VERSION);
|
|
2535
2555
|
program.command("login").description("Authenticate with your Replicas account").action(async () => {
|
|
@@ -2726,7 +2746,7 @@ program.command("get <id>").description("Get replica details by ID").action(asyn
|
|
|
2726
2746
|
process.exit(1);
|
|
2727
2747
|
}
|
|
2728
2748
|
});
|
|
2729
|
-
program.command("create [name]").description("Create a new replica").option("-m, --message <message>", "Initial message for the replica").option("-r, --
|
|
2749
|
+
program.command("create [name]").description("Create a new replica").option("-m, --message <message>", "Initial message for the replica").option("-r, --repositories <repositories>", "Comma-separated repository names").option("-a, --agent <agent>", "Coding agent (claude, codex)").action(async (name, options) => {
|
|
2730
2750
|
try {
|
|
2731
2751
|
await replicaCreateCommand(name, options);
|
|
2732
2752
|
} catch (error) {
|