replicas-cli 0.3.283 → 0.3.285
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.cjs +181 -12
- package/package.json +4 -2
package/dist/index.cjs
CHANGED
|
@@ -17208,7 +17208,7 @@ var init_workspace_identity = __esm({
|
|
|
17208
17208
|
});
|
|
17209
17209
|
|
|
17210
17210
|
// ../shared/src/routes/environment.ts
|
|
17211
|
-
var ENVIRONMENT_MCP_TRANSPORTS, stringMap, createEnvironmentMcpRequestSchema, updateEnvironmentMcpRequestSchema, screenPasswordSchema, createEnvironmentDeviceRequestSchema, updateEnvironmentDeviceRequestSchema, provisionedDeviceSchema;
|
|
17211
|
+
var ENVIRONMENT_MCP_TRANSPORTS, stringMap, createEnvironmentMcpRequestSchema, updateEnvironmentMcpRequestSchema, screenPasswordSchema, hostSchema, createEnvironmentDeviceRequestSchema, updateEnvironmentDeviceRequestSchema, deviceConnectionResponseSchema, deviceHostKeysRequestSchema, DEVICE_CONNECT_COMMAND, provisionedDeviceSchema;
|
|
17212
17212
|
var init_environment = __esm({
|
|
17213
17213
|
"../shared/src/routes/environment.ts"() {
|
|
17214
17214
|
"use strict";
|
|
@@ -17227,19 +17227,30 @@ var init_environment = __esm({
|
|
|
17227
17227
|
});
|
|
17228
17228
|
updateEnvironmentMcpRequestSchema = createEnvironmentMcpRequestSchema.partial();
|
|
17229
17229
|
screenPasswordSchema = external_exports.string().min(1, "Enter the screen password").max(256).describe('Screen Sharing password, for `replicas computer screen`. On a Mac, the "VNC viewers may control screen with password" password. Never returned.');
|
|
17230
|
+
hostSchema = external_exports.string().max(253).regex(/^(?:[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?|[0-9A-Fa-f:]+)$/, "Enter a hostname or IP address");
|
|
17230
17231
|
createEnvironmentDeviceRequestSchema = external_exports.object({
|
|
17231
17232
|
name: external_exports.string().regex(/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/, "Use lowercase letters, numbers and dashes").describe("SSH alias in workspaces, e.g. `mac-mini` for `ssh mac-mini`."),
|
|
17232
|
-
host:
|
|
17233
|
-
port: external_exports.number().int().min(1).max(65535).optional().describe("SSH port. Defaults to 22."),
|
|
17233
|
+
host: hostSchema.optional().describe("Hostname or IP, such as a Tailscale MagicDNS name (`mac-mini`) or a public address. Leave it out when the device connects to Replicas itself with `replicas device connect`."),
|
|
17234
|
+
port: external_exports.number().int().min(1).max(65535).optional().describe("SSH port on the device. Defaults to 22."),
|
|
17234
17235
|
username: external_exports.string().regex(/^[A-Za-z0-9_][A-Za-z0-9._-]{0,63}$/, "Enter the account name on the device").describe("Account the agent signs in as."),
|
|
17235
17236
|
screen_password: screenPasswordSchema.optional()
|
|
17236
17237
|
});
|
|
17237
17238
|
updateEnvironmentDeviceRequestSchema = createEnvironmentDeviceRequestSchema.partial().extend({
|
|
17239
|
+
host: external_exports.union([hostSchema, external_exports.literal("")]).optional().describe("New hostname or IP, or an empty string for a device that connects to Replicas itself."),
|
|
17238
17240
|
screen_password: external_exports.union([screenPasswordSchema, external_exports.literal("")]).optional().describe("New Screen Sharing password, or an empty string to remove it.")
|
|
17239
17241
|
});
|
|
17242
|
+
deviceConnectionResponseSchema = external_exports.object({
|
|
17243
|
+
name: external_exports.string(),
|
|
17244
|
+
port: external_exports.number(),
|
|
17245
|
+
workspace_ids: external_exports.array(external_exports.string())
|
|
17246
|
+
});
|
|
17247
|
+
deviceHostKeysRequestSchema = external_exports.object({
|
|
17248
|
+
host_keys: external_exports.array(external_exports.string().max(2048).regex(/^[a-z0-9@.-]+ [A-Za-z0-9+/]+={0,3}$/, "Each host key must be `<type> <base64>`")).min(1).max(8).transform((keys) => [...new Set(keys)].sort())
|
|
17249
|
+
});
|
|
17250
|
+
DEVICE_CONNECT_COMMAND = "npx -y replicas-cli device connect";
|
|
17240
17251
|
provisionedDeviceSchema = external_exports.object({
|
|
17241
17252
|
name: external_exports.string(),
|
|
17242
|
-
host: external_exports.string(),
|
|
17253
|
+
host: external_exports.string().nullable(),
|
|
17243
17254
|
port: external_exports.number(),
|
|
17244
17255
|
username: external_exports.string(),
|
|
17245
17256
|
private_key: external_exports.string(),
|
|
@@ -31704,7 +31715,7 @@ var CLI_VERSION;
|
|
|
31704
31715
|
var init_cli_version = __esm({
|
|
31705
31716
|
"../shared/src/cli-version.ts"() {
|
|
31706
31717
|
"use strict";
|
|
31707
|
-
CLI_VERSION = "0.3.
|
|
31718
|
+
CLI_VERSION = "0.3.285";
|
|
31708
31719
|
}
|
|
31709
31720
|
});
|
|
31710
31721
|
|
|
@@ -42288,6 +42299,29 @@ var init_variable_requests = __esm({
|
|
|
42288
42299
|
}
|
|
42289
42300
|
});
|
|
42290
42301
|
|
|
42302
|
+
// ../shared/src/websocket-liveness.ts
|
|
42303
|
+
function trackWebSocketLiveness(socket) {
|
|
42304
|
+
let answered = true;
|
|
42305
|
+
for (const event of ["pong", "message"]) socket.on(event, () => {
|
|
42306
|
+
answered = true;
|
|
42307
|
+
});
|
|
42308
|
+
return () => {
|
|
42309
|
+
if (socket.isPaused) {
|
|
42310
|
+
answered = true;
|
|
42311
|
+
return true;
|
|
42312
|
+
}
|
|
42313
|
+
if (!answered) return false;
|
|
42314
|
+
answered = false;
|
|
42315
|
+
socket.ping();
|
|
42316
|
+
return true;
|
|
42317
|
+
};
|
|
42318
|
+
}
|
|
42319
|
+
var init_websocket_liveness = __esm({
|
|
42320
|
+
"../shared/src/websocket-liveness.ts"() {
|
|
42321
|
+
"use strict";
|
|
42322
|
+
}
|
|
42323
|
+
});
|
|
42324
|
+
|
|
42291
42325
|
// ../shared/src/index.ts
|
|
42292
42326
|
var init_src = __esm({
|
|
42293
42327
|
"../shared/src/index.ts"() {
|
|
@@ -42465,6 +42499,7 @@ var init_src = __esm({
|
|
|
42465
42499
|
init_environment_setups();
|
|
42466
42500
|
init_variable_requests();
|
|
42467
42501
|
init_api_casing();
|
|
42502
|
+
init_websocket_liveness();
|
|
42468
42503
|
}
|
|
42469
42504
|
});
|
|
42470
42505
|
|
|
@@ -45428,16 +45463,24 @@ async function envDevicesListCommand(env) {
|
|
|
45428
45463
|
`/v1/environments/${await resolveEnvironmentId(env)}/devices`
|
|
45429
45464
|
);
|
|
45430
45465
|
output(response, ({ environment_devices }) => {
|
|
45431
|
-
for (const device of environment_devices)
|
|
45466
|
+
for (const device of environment_devices) {
|
|
45467
|
+
console.log(`${device.id} ${device.name} ${device.host === null ? `${device.username}, connects from the device` : `${device.username}@${device.host}:${device.port}`}`);
|
|
45468
|
+
}
|
|
45432
45469
|
});
|
|
45433
45470
|
}
|
|
45434
45471
|
async function envDevicesAddCommand(env, name, options) {
|
|
45435
45472
|
const id = await resolveEnvironmentId(env);
|
|
45436
45473
|
const body = { name, host: options.host, username: options.user, port: options.port, screen_password: options.screenPassword };
|
|
45437
45474
|
const response = await orgAuthenticatedFetch(`/v1/environments/${id}/devices`, { method: "POST", body });
|
|
45438
|
-
|
|
45475
|
+
const connect2 = options.host === void 0 ? await orgAuthenticatedFetch(`/v1/environments/${id}/devices/${response.environment_device.id}/connect-token`) : void 0;
|
|
45476
|
+
output({ ...response, connect_token: connect2?.token }, ({ environment_device, connect_token }) => {
|
|
45439
45477
|
console.log(import_chalk20.default.green(`Added device ${name}. Allow its key by running this on the device as ${options.user}:`));
|
|
45440
45478
|
console.log(`mkdir -p ~/.ssh && echo '${environment_device.public_key}' >> ~/.ssh/authorized_keys`);
|
|
45479
|
+
if (connect_token) {
|
|
45480
|
+
console.log(import_chalk20.default.green("Then keep this running on it, so workspaces can reach it, and paste this token when it asks:"));
|
|
45481
|
+
console.log(DEVICE_CONNECT_COMMAND);
|
|
45482
|
+
console.log(connect_token);
|
|
45483
|
+
}
|
|
45441
45484
|
});
|
|
45442
45485
|
}
|
|
45443
45486
|
async function envDevicesRemoveCommand(env, nameOrId) {
|
|
@@ -45448,6 +45491,18 @@ async function envDevicesRemoveCommand(env, nameOrId) {
|
|
|
45448
45491
|
await orgAuthenticatedFetch(`/v1/environments/${id}/devices/${device.id}`, { method: "DELETE" });
|
|
45449
45492
|
outputSuccess(`Removed device ${device.name}`);
|
|
45450
45493
|
}
|
|
45494
|
+
async function envDevicesResetCommand(env, nameOrId) {
|
|
45495
|
+
const id = await resolveEnvironmentId(env);
|
|
45496
|
+
const { environment_devices } = await orgAuthenticatedFetch(`/v1/environments/${id}/devices`);
|
|
45497
|
+
const device = resolveByNameOrId(nameOrId, environment_devices);
|
|
45498
|
+
if (!device) throw new Error(`Device not found: ${nameOrId}`);
|
|
45499
|
+
const response = await orgAuthenticatedFetch(`/v1/environments/${id}/devices/${device.id}/connect-token`, { method: "POST" });
|
|
45500
|
+
output(response, ({ token }) => {
|
|
45501
|
+
console.log(import_chalk20.default.green(`Reset ${device.name}'s connection. Its old token no longer works; run this on it again and paste the new token when it asks:`));
|
|
45502
|
+
console.log(DEVICE_CONNECT_COMMAND);
|
|
45503
|
+
console.log(token);
|
|
45504
|
+
});
|
|
45505
|
+
}
|
|
45451
45506
|
function envWarmPoolCommand(enabled) {
|
|
45452
45507
|
return async (env) => {
|
|
45453
45508
|
const id = await resolveEnvironmentId(env);
|
|
@@ -48592,6 +48647,116 @@ trim "$1"`;
|
|
|
48592
48647
|
}
|
|
48593
48648
|
});
|
|
48594
48649
|
|
|
48650
|
+
// src/commands/device.ts
|
|
48651
|
+
async function refuse(response) {
|
|
48652
|
+
const body = await response.json().catch(() => null);
|
|
48653
|
+
throw new Error(body && typeof body === "object" && "error" in body ? String(body.error) : `Replicas refused this device (${response.status})`);
|
|
48654
|
+
}
|
|
48655
|
+
async function scanHostKeys(port) {
|
|
48656
|
+
const { stdout } = await (0, import_node_util.promisify)(import_node_child_process4.execFile)("ssh-keyscan", ["-T", "5", "-p", String(port), "127.0.0.1"]).catch(() => ({ stdout: "" }));
|
|
48657
|
+
return stdout.split("\n").filter((line) => line && !line.startsWith("#")).map((line) => line.split(" ").slice(1, 3).join(" "));
|
|
48658
|
+
}
|
|
48659
|
+
async function deviceConnectCommand() {
|
|
48660
|
+
const token = process.env.REPLICAS_DEVICE_TOKEN ?? (isInteractive() ? (await ask({ type: "password", name: "token", message: "Device token, from the device in the environment's Devices settings" })).token : void 0);
|
|
48661
|
+
if (!token) throw new Error("Paste the device token when asked, or set REPLICAS_DEVICE_TOKEN to it. It's on the device in the environment's Devices settings.");
|
|
48662
|
+
const headers = { Authorization: `Bearer ${token}` };
|
|
48663
|
+
let pinned = false;
|
|
48664
|
+
const waiting = /* @__PURE__ */ new Map();
|
|
48665
|
+
let port = 22;
|
|
48666
|
+
let summary = "";
|
|
48667
|
+
const wait = (workspaceId, failures = 0) => {
|
|
48668
|
+
if (!waiting.has(workspaceId)) return;
|
|
48669
|
+
const startedAt = Date.now();
|
|
48670
|
+
let opened = false;
|
|
48671
|
+
const socket = new import_ws.default(`${MONOLITH_URL.replace(/^http/, "ws")}/v1/devices/workspaces/${workspaceId}/ws`, { headers, handshakeTimeout: HEARTBEAT_MS });
|
|
48672
|
+
waiting.set(workspaceId, socket);
|
|
48673
|
+
const alive = trackWebSocketLiveness(socket);
|
|
48674
|
+
let heartbeat;
|
|
48675
|
+
socket.once("open", () => {
|
|
48676
|
+
heartbeat = setInterval(() => {
|
|
48677
|
+
if (!alive()) socket.terminate();
|
|
48678
|
+
}, HEARTBEAT_MS);
|
|
48679
|
+
});
|
|
48680
|
+
socket.once("message", (data, isBinary) => {
|
|
48681
|
+
if (isBinary || data.toString() !== "open") return socket.close();
|
|
48682
|
+
opened = true;
|
|
48683
|
+
wait(workspaceId);
|
|
48684
|
+
const sshd = (0, import_node_net.connect)(port, "127.0.0.1");
|
|
48685
|
+
const stream = (0, import_ws.createWebSocketStream)(socket);
|
|
48686
|
+
const close = () => {
|
|
48687
|
+
sshd.destroy();
|
|
48688
|
+
stream.destroy();
|
|
48689
|
+
};
|
|
48690
|
+
sshd.once("error", (error51) => {
|
|
48691
|
+
console.log(import_chalk32.default.yellow(`A workspace couldn't reach SSH on port ${port} here (${error51.message}). ${REMOTE_LOGIN_HINT}`));
|
|
48692
|
+
});
|
|
48693
|
+
sshd.on("error", close).on("close", close);
|
|
48694
|
+
stream.on("error", close).on("close", close);
|
|
48695
|
+
sshd.pipe(stream).pipe(sshd);
|
|
48696
|
+
});
|
|
48697
|
+
socket.on("error", () => void 0);
|
|
48698
|
+
socket.once("close", () => {
|
|
48699
|
+
clearInterval(heartbeat);
|
|
48700
|
+
if (opened) return;
|
|
48701
|
+
const failed = Date.now() - startedAt < POLL_INTERVAL_MS;
|
|
48702
|
+
const delay = failed ? Math.min(5e3 * 2 ** failures, MAX_RETRY_DELAY_MS) : 0;
|
|
48703
|
+
setTimeout(() => {
|
|
48704
|
+
if (waiting.get(workspaceId) === socket) wait(workspaceId, failed ? failures + 1 : 0);
|
|
48705
|
+
}, delay).unref();
|
|
48706
|
+
});
|
|
48707
|
+
};
|
|
48708
|
+
for (; ; ) {
|
|
48709
|
+
const response = await fetch(`${MONOLITH_URL}/v1/devices/workspaces`, { headers }).catch(() => null);
|
|
48710
|
+
if (response && [401, 403, 409].includes(response.status)) await refuse(response);
|
|
48711
|
+
const device = response?.ok ? deviceConnectionResponseSchema.parse(await response.json()) : void 0;
|
|
48712
|
+
let problem = device ? void 0 : `Can't reach Replicas (${response?.status ?? "network error"}), retrying...`;
|
|
48713
|
+
if (device && !pinned) {
|
|
48714
|
+
const body = { host_keys: await scanHostKeys(device.port) };
|
|
48715
|
+
const pin = body.host_keys.length === 0 ? void 0 : await fetch(`${MONOLITH_URL}/v1/devices/host-keys`, { method: "PUT", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify(body) }).catch(() => null);
|
|
48716
|
+
if (pin && [400, 401, 403, 409].includes(pin.status)) await refuse(pin);
|
|
48717
|
+
pinned = pin?.ok === true;
|
|
48718
|
+
if (pin === void 0) problem = `Can't reach SSH on port ${device.port} here, retrying... ${REMOTE_LOGIN_HINT}`;
|
|
48719
|
+
else if (!pinned) problem = `Can't reach Replicas (${pin?.status ?? "network error"}), retrying...`;
|
|
48720
|
+
}
|
|
48721
|
+
if (device && pinned) {
|
|
48722
|
+
port = device.port;
|
|
48723
|
+
for (const [workspaceId, socket] of waiting) {
|
|
48724
|
+
if (device.workspace_ids.includes(workspaceId)) continue;
|
|
48725
|
+
waiting.delete(workspaceId);
|
|
48726
|
+
socket?.close();
|
|
48727
|
+
}
|
|
48728
|
+
for (const workspaceId of device.workspace_ids) {
|
|
48729
|
+
if (waiting.has(workspaceId)) continue;
|
|
48730
|
+
waiting.set(workspaceId, void 0);
|
|
48731
|
+
wait(workspaceId);
|
|
48732
|
+
}
|
|
48733
|
+
}
|
|
48734
|
+
const next = problem ?? `\u2713 ${device?.name}: ready for ${waiting.size} ${waiting.size === 1 ? "workspace" : "workspaces"}, forwarding to SSH on port ${port}`;
|
|
48735
|
+
if (next !== summary) console.log(problem ? import_chalk32.default.yellow(next) : import_chalk32.default.green(next));
|
|
48736
|
+
summary = next;
|
|
48737
|
+
await (0, import_promises2.setTimeout)(POLL_INTERVAL_MS);
|
|
48738
|
+
}
|
|
48739
|
+
}
|
|
48740
|
+
var import_chalk32, import_node_child_process4, import_node_net, import_promises2, import_node_util, import_ws, POLL_INTERVAL_MS, HEARTBEAT_MS, MAX_RETRY_DELAY_MS, REMOTE_LOGIN_HINT;
|
|
48741
|
+
var init_device = __esm({
|
|
48742
|
+
"src/commands/device.ts"() {
|
|
48743
|
+
"use strict";
|
|
48744
|
+
import_chalk32 = __toESM(require("chalk"));
|
|
48745
|
+
import_node_child_process4 = require("child_process");
|
|
48746
|
+
import_node_net = require("net");
|
|
48747
|
+
import_promises2 = require("timers/promises");
|
|
48748
|
+
import_node_util = require("util");
|
|
48749
|
+
import_ws = __toESM(require("ws"));
|
|
48750
|
+
init_src();
|
|
48751
|
+
init_monolith_url();
|
|
48752
|
+
init_output();
|
|
48753
|
+
POLL_INTERVAL_MS = 1e4;
|
|
48754
|
+
HEARTBEAT_MS = 3e4;
|
|
48755
|
+
MAX_RETRY_DELAY_MS = 5 * 6e4;
|
|
48756
|
+
REMOTE_LOGIN_HINT = "On a Mac, turn on Remote Login in System Settings \u2192 General \u2192 Sharing.";
|
|
48757
|
+
}
|
|
48758
|
+
});
|
|
48759
|
+
|
|
48595
48760
|
// src/index.ts
|
|
48596
48761
|
var src_exports = {};
|
|
48597
48762
|
function parseBooleanOption(value) {
|
|
@@ -48663,6 +48828,7 @@ var init_src2 = __esm({
|
|
|
48663
48828
|
init_slack4();
|
|
48664
48829
|
init_service();
|
|
48665
48830
|
init_environment3();
|
|
48831
|
+
init_device();
|
|
48666
48832
|
init_config();
|
|
48667
48833
|
init_organization2();
|
|
48668
48834
|
init_output();
|
|
@@ -48680,6 +48846,7 @@ var init_src2 = __esm({
|
|
|
48680
48846
|
"codex-auth",
|
|
48681
48847
|
"muse-auth",
|
|
48682
48848
|
"config",
|
|
48849
|
+
"device",
|
|
48683
48850
|
"init",
|
|
48684
48851
|
"login",
|
|
48685
48852
|
"logout",
|
|
@@ -48858,12 +49025,14 @@ var init_src2 = __esm({
|
|
|
48858
49025
|
envMcps.command("remove <env> <name-or-id>").description("Remove an MCP server").action(run(envMcpsRemoveCommand));
|
|
48859
49026
|
envDevices = environment.command("devices", { hidden: true }).description("Computers the agent reaches over SSH, like a Mac mini or your own laptop");
|
|
48860
49027
|
envDevices.command("list <env>").description("List devices").action(run(envDevicesListCommand));
|
|
48861
|
-
envDevices.command("add <env> <name>").description("Add a device and print the command that allows its key on the device").
|
|
49028
|
+
envDevices.command("add <env> <name>").description("Add a device and print the command that allows its key on the device").option("--host <host>", "Hostname or IP, such as its Tailscale name. Leave it out to connect from the device instead").requiredOption("--user <user>", "Account the agent signs in as").option("--port <port>", "SSH port (default 22)", (value) => {
|
|
48862
49029
|
const port = Number(value);
|
|
48863
49030
|
if (!Number.isInteger(port)) throw new import_commander.InvalidArgumentError("must be a whole number");
|
|
48864
49031
|
return port;
|
|
48865
49032
|
}).option("--screen-password <password>", "Screen Sharing password, so the agent can see and control the screen").action(run(envDevicesAddCommand));
|
|
48866
49033
|
envDevices.command("remove <env> <name-or-id>").description("Remove a device and delete its key").action(run(envDevicesRemoveCommand));
|
|
49034
|
+
envDevices.command("reset <env> <name-or-id>").description("Replace the connect token of a device that connects from itself and forget its SSH identity, for a leaked token or a reinstalled device").action(run(envDevicesResetCommand));
|
|
49035
|
+
program.command("device", { hidden: true }).description("Run on one of your computers so Replicas workspaces can reach it").command("connect").description("Keep this computer reachable from the workspaces that have it, without opening ports or a tailnet. Asks for the token from the device's settings, or reads REPLICAS_DEVICE_TOKEN").action(run(deviceConnectCommand));
|
|
48867
49036
|
envWarmPool = environment.command("warm-pool").description("Keep pre-warmed workspaces ready for an environment");
|
|
48868
49037
|
envWarmPool.command("enable <env>").description("Enable the warm pool").action(run(envWarmPoolCommand(true)));
|
|
48869
49038
|
envWarmPool.command("disable <env>").description("Disable the warm pool").action(run(envWarmPoolCommand(false)));
|
|
@@ -48975,12 +49144,12 @@ ${VISUAL_PAGE_RULES}`).action(run(visualizeCommand));
|
|
|
48975
49144
|
});
|
|
48976
49145
|
|
|
48977
49146
|
// src/bin.ts
|
|
48978
|
-
var
|
|
48979
|
-
var
|
|
49147
|
+
var import_node_child_process5 = require("child_process");
|
|
49148
|
+
var import_chalk33 = __toESM(require("chalk"));
|
|
48980
49149
|
init_config();
|
|
48981
49150
|
if (process.argv[2] === "computer" && isAgentMode()) {
|
|
48982
|
-
const result = (0,
|
|
48983
|
-
if (result.error) console.error(
|
|
49151
|
+
const result = (0, import_node_child_process5.spawnSync)("replicas-computer", process.argv.slice(3), { stdio: "inherit" });
|
|
49152
|
+
if (result.error) console.error(import_chalk33.default.red(`
|
|
48984
49153
|
\u2717 replicas-computer is unavailable in this workspace image
|
|
48985
49154
|
`));
|
|
48986
49155
|
process.exitCode = result.error ? 1 : result.status ?? 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "replicas-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.285",
|
|
4
4
|
"description": "CLI for managing Replicas workspaces - SSH into cloud dev environments with automatic port forwarding",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"bin": {
|
|
@@ -47,11 +47,13 @@
|
|
|
47
47
|
"dotenv": "^17.2.3",
|
|
48
48
|
"open": "^8.4.2",
|
|
49
49
|
"prompts": "^2.4.2",
|
|
50
|
-
"tslib": "^2.6.2"
|
|
50
|
+
"tslib": "^2.6.2",
|
|
51
|
+
"ws": "8.21.0"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
53
54
|
"@replicas/shared": "*",
|
|
54
55
|
"@types/node": "^20.11.0",
|
|
56
|
+
"@types/ws": "8.18.1",
|
|
55
57
|
"tsup": "^8.5.0",
|
|
56
58
|
"typescript": "^5.3.3"
|
|
57
59
|
}
|