replicas-cli 0.3.103 → 0.3.104
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 +424 -94
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -7363,9 +7363,9 @@ var require_dist = __commonJS({
|
|
|
7363
7363
|
|
|
7364
7364
|
// src/index.ts
|
|
7365
7365
|
var import_config20 = require("dotenv/config");
|
|
7366
|
-
var
|
|
7366
|
+
var import_node_child_process4 = require("child_process");
|
|
7367
7367
|
var import_commander = require("commander");
|
|
7368
|
-
var
|
|
7368
|
+
var import_chalk32 = __toESM(require("chalk"));
|
|
7369
7369
|
|
|
7370
7370
|
// ../shared/src/urls.ts
|
|
7371
7371
|
var DEFAULT_WEB_APP_URL = "https://app.replicas.dev";
|
|
@@ -22611,9 +22611,9 @@ var EXT_TO_LANGUAGE = {
|
|
|
22611
22611
|
function detectLanguageByPath(filePath) {
|
|
22612
22612
|
const dot = filePath.lastIndexOf(".");
|
|
22613
22613
|
if (dot === -1) {
|
|
22614
|
-
const
|
|
22615
|
-
if (
|
|
22616
|
-
if (
|
|
22614
|
+
const basename2 = filePath.split("/").pop() ?? "";
|
|
22615
|
+
if (basename2 === "Dockerfile") return "dockerfile";
|
|
22616
|
+
if (basename2 === "Makefile") return "makefile";
|
|
22617
22617
|
return null;
|
|
22618
22618
|
}
|
|
22619
22619
|
const ext = filePath.slice(dot).toLowerCase();
|
|
@@ -24964,8 +24964,103 @@ var GITLAB_ABILITY = {
|
|
|
24964
24964
|
referenceFile: { name: "GITLAB.md", content: REFERENCE5 }
|
|
24965
24965
|
};
|
|
24966
24966
|
|
|
24967
|
+
// ../shared/src/routes/compute.ts
|
|
24968
|
+
var COMPUTE_SANDBOX_KINDS = ["gpu"];
|
|
24969
|
+
var GPU_TYPES = ["T4", "L4", "A10G", "L40S", "A100", "A100-80GB", "H100", "H200", "B200"];
|
|
24970
|
+
var DEFAULT_GPU_TYPE = "L4";
|
|
24971
|
+
var DEFAULT_GPU_IMAGE = "pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime";
|
|
24972
|
+
var COMPUTE_SANDBOX_WORKDIR = "/workspace";
|
|
24973
|
+
var COMPUTE_SANDBOX_LIMITS = {
|
|
24974
|
+
maxActivePerWorkspace: 3,
|
|
24975
|
+
maxActivePerOrganization: 10,
|
|
24976
|
+
maxGpuCount: 8,
|
|
24977
|
+
maxPorts: 5,
|
|
24978
|
+
defaultTimeoutMinutes: 60,
|
|
24979
|
+
maxTimeoutMinutes: 24 * 60,
|
|
24980
|
+
defaultIdleTimeoutMinutes: 10,
|
|
24981
|
+
maxIdleTimeoutMinutes: 60,
|
|
24982
|
+
maxTransferBytes: 1024 ** 3
|
|
24983
|
+
};
|
|
24984
|
+
var computeEnvSchema = external_exports.record(external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(128), external_exports.string().max(32768)).refine((env) => Object.keys(env).length <= 64, "At most 64 environment variables are allowed");
|
|
24985
|
+
var createComputeSandboxRequestSchema = external_exports.object({
|
|
24986
|
+
kind: external_exports.enum(COMPUTE_SANDBOX_KINDS),
|
|
24987
|
+
gpu: external_exports.string().trim().optional(),
|
|
24988
|
+
image: external_exports.string().trim().min(1).max(256).regex(/^\S+$/, "Image must not contain whitespace").optional(),
|
|
24989
|
+
timeoutMinutes: external_exports.number().int().min(1).max(COMPUTE_SANDBOX_LIMITS.maxTimeoutMinutes).optional(),
|
|
24990
|
+
idleTimeoutMinutes: external_exports.number().int().min(1).max(COMPUTE_SANDBOX_LIMITS.maxIdleTimeoutMinutes).optional(),
|
|
24991
|
+
ports: external_exports.array(external_exports.number().int().min(1).max(65535)).max(COMPUTE_SANDBOX_LIMITS.maxPorts).optional(),
|
|
24992
|
+
env: computeEnvSchema.optional()
|
|
24993
|
+
});
|
|
24994
|
+
var computeExecRequestSchema = external_exports.object({
|
|
24995
|
+
command: external_exports.string().min(1).max(1e5),
|
|
24996
|
+
workdir: external_exports.string().min(1).max(4096).optional(),
|
|
24997
|
+
env: computeEnvSchema.optional(),
|
|
24998
|
+
detach: external_exports.boolean().optional()
|
|
24999
|
+
});
|
|
25000
|
+
|
|
25001
|
+
// ../shared/src/default-skills/replicas-agent/abilities/gpu.ts
|
|
25002
|
+
var SECTION6 = `### GPU sandboxes
|
|
25003
|
+
Run GPU workloads (training, inference, CUDA builds, rendering) on cloud GPU sandboxes with \`replicas gpu\`. The workspace itself has no GPU.
|
|
25004
|
+
|
|
25005
|
+
**Reference:** \`references/GPU.md\`
|
|
25006
|
+
|
|
25007
|
+
Use this when:
|
|
25008
|
+
- A task needs a GPU: training or fine-tuning, model inference, CUDA code, benchmarks, or GPU rendering
|
|
25009
|
+
- You need to verify GPU code actually runs instead of only writing it`;
|
|
25010
|
+
var REFERENCE6 = `# GPU sandboxes
|
|
25011
|
+
|
|
25012
|
+
\`replicas gpu\` starts cloud GPU sandboxes and runs commands in them. It is authenticated automatically, and usage is billed at cost to the organization, so stop sandboxes when you are done.
|
|
25013
|
+
|
|
25014
|
+
## One-shot runs
|
|
25015
|
+
|
|
25016
|
+
\`\`\`bash
|
|
25017
|
+
replicas gpu run --gpu H100 --pull outputs -- python train.py
|
|
25018
|
+
\`\`\`
|
|
25019
|
+
|
|
25020
|
+
This starts a sandbox, copies the current directory to \`${COMPUTE_SANDBOX_WORKDIR}\` (git repositories honor \`.gitignore\`), streams the command's output, copies \`${COMPUTE_SANDBOX_WORKDIR}/outputs\` back to \`./outputs\`, stops the sandbox, and exits with the command's exit code. Add \`--keep\` to leave it running.
|
|
25021
|
+
|
|
25022
|
+
## Interactive sessions
|
|
25023
|
+
|
|
25024
|
+
\`\`\`bash
|
|
25025
|
+
replicas gpu create --gpu A100-80GB --timeout 3h # waits until running
|
|
25026
|
+
replicas gpu push # . -> ${COMPUTE_SANDBOX_WORKDIR}
|
|
25027
|
+
replicas gpu exec -- nvidia-smi
|
|
25028
|
+
replicas gpu exec -- pip install -r requirements.txt
|
|
25029
|
+
replicas gpu exec --detach -- python train.py # prints a job ID
|
|
25030
|
+
replicas gpu logs <jobId> -f # streams until the job exits
|
|
25031
|
+
replicas gpu pull ${COMPUTE_SANDBOX_WORKDIR}/checkpoints ./
|
|
25032
|
+
replicas gpu delete
|
|
25033
|
+
\`\`\`
|
|
25034
|
+
|
|
25035
|
+
- Commands run with \`sh -c\` in \`${COMPUTE_SANDBOX_WORKDIR}\`; quote shell operators: \`replicas gpu exec -- "cd src && make"\`.
|
|
25036
|
+
- \`--sandbox <id>\` picks a sandbox when more than one is running; \`replicas gpu list\` shows them.
|
|
25037
|
+
- Use \`--detach\` and \`logs -f\` for anything longer than a few minutes. A foreground \`exec\` dies with its connection.
|
|
25038
|
+
- \`--port 8000\` on create exposes a container port as a public HTTPS URL (shown by \`list\`).
|
|
25039
|
+
- \`--env KEY=value\` passes secrets such as \`HF_TOKEN\`; never bake them into files you push.
|
|
25040
|
+
|
|
25041
|
+
## Choosing hardware and images
|
|
25042
|
+
|
|
25043
|
+
- GPUs: ${GPU_TYPES.join(", ")} (default ${DEFAULT_GPU_TYPE}). Add a count for multi-GPU, e.g. \`H100:4\` (max ${COMPUTE_SANDBOX_LIMITS.maxGpuCount}). Pick the smallest GPU that fits: T4/L4 for small models and smoke tests, A100/H100 for real training.
|
|
25044
|
+
- Default image: \`${DEFAULT_GPU_IMAGE}\`. Pass any public registry image with \`--image\`; the first start of a new image takes a few minutes to pull.
|
|
25045
|
+
- The sandbox has internet access. Download datasets and weights inside it rather than pushing them; pushes and pulls are capped at ${COMPUTE_SANDBOX_LIMITS.maxTransferBytes / 1024 ** 3} GiB.
|
|
25046
|
+
|
|
25047
|
+
## Limits
|
|
25048
|
+
|
|
25049
|
+
- ${COMPUTE_SANDBOX_LIMITS.maxActivePerWorkspace} running sandboxes per workspace and ${COMPUTE_SANDBOX_LIMITS.maxActivePerOrganization} per organization
|
|
25050
|
+
- Lifetime 1 hour by default, up to 24 hours with \`--timeout\`
|
|
25051
|
+
- Sandboxes stop after 10 minutes with no running command (\`--idle-timeout\`, up to 1 hour)
|
|
25052
|
+
- Sandboxes stop when this workspace sleeps, is archived, or is deleted, and their files are lost; pull results before finishing
|
|
25053
|
+
`;
|
|
25054
|
+
var GPU_ABILITY = {
|
|
25055
|
+
label: "GPU sandboxes",
|
|
25056
|
+
description: "Run training, inference, and other GPU workloads on cloud GPUs with `replicas gpu`.",
|
|
25057
|
+
bullet: "- Running GPU workloads (training, inference, CUDA, rendering) on cloud GPU sandboxes with `replicas gpu`",
|
|
25058
|
+
section: SECTION6,
|
|
25059
|
+
referenceFile: { name: "GPU.md", content: REFERENCE6 }
|
|
25060
|
+
};
|
|
25061
|
+
|
|
24967
25062
|
// ../shared/src/default-skills/replicas-agent/abilities/linear.ts
|
|
24968
|
-
var
|
|
25063
|
+
var SECTION7 = `### Linear
|
|
24969
25064
|
Create tickets, fetch issues, update state, add comments, and search through \`replicas.linear.graphql\` in \`@replicas/sdk\`. This works without a Linear MCP server and refreshes authentication automatically.
|
|
24970
25065
|
|
|
24971
25066
|
**Reference:** \`references/LINEAR.md\`
|
|
@@ -24976,7 +25071,7 @@ Use this when:
|
|
|
24976
25071
|
- You need to update an issue's state or add a comment
|
|
24977
25072
|
- You need to search Linear issues
|
|
24978
25073
|
- A Linear request failed with an authentication error`;
|
|
24979
|
-
var
|
|
25074
|
+
var REFERENCE7 = `# Linear Integration
|
|
24980
25075
|
|
|
24981
25076
|
Use \`@replicas/sdk\`; never request or handle a Linear OAuth token. Replicas refreshes the organization credential behind the gateway.
|
|
24982
25077
|
|
|
@@ -25069,12 +25164,12 @@ var LINEAR_ABILITY = {
|
|
|
25069
25164
|
label: "Linear",
|
|
25070
25165
|
description: "Create tickets, fetch issues, comment, and update state through @replicas/sdk.",
|
|
25071
25166
|
bullet: "- Interacting with Linear (creating tickets, fetching issues including any linear.app link the user shares, updating state, commenting, or troubleshooting authentication): use replicas.linear.graphql from @replicas/sdk, which refreshes tokens automatically and needs no Linear MCP server. Read references/LINEAR.md before treating a rejected workspace token as a disconnected integration.",
|
|
25072
|
-
section:
|
|
25073
|
-
referenceFile: { name: "LINEAR.md", content:
|
|
25167
|
+
section: SECTION7,
|
|
25168
|
+
referenceFile: { name: "LINEAR.md", content: REFERENCE7 }
|
|
25074
25169
|
};
|
|
25075
25170
|
|
|
25076
25171
|
// ../shared/src/default-skills/replicas-agent/abilities/media.ts
|
|
25077
|
-
var
|
|
25172
|
+
var SECTION8 = `### Media
|
|
25078
25173
|
Deliver screenshots, GIFs, screen recordings, generated diagrams, audio clips, and self-contained HTML pages to the Replicas dashboard and, when applicable, inline in the destination workflow.
|
|
25079
25174
|
|
|
25080
25175
|
**Reference:** \`references/MEDIA.md\`
|
|
@@ -25086,7 +25181,7 @@ Use this when:
|
|
|
25086
25181
|
- You produce a self-contained HTML report that should be linked from a pull request
|
|
25087
25182
|
- You record video output (browser automation, screen capture) \u2014 including the recommended aspect ratio and FPS
|
|
25088
25183
|
- You need to embed media in a Slack/Linear/GitHub/GitLab message AND keep a referenceable copy in the Replicas dashboard`;
|
|
25089
|
-
var
|
|
25184
|
+
var REFERENCE8 = `# Media (Screenshots, GIFs, Recordings, Audio, HTML)
|
|
25090
25185
|
|
|
25091
25186
|
This is the single source of truth for delivering screenshots, GIFs, screen recordings, generated diagrams, audio clips, and self-contained HTML pages through Replicas and external surfaces (Slack, Linear, GitHub, GitLab).
|
|
25092
25187
|
|
|
@@ -25466,12 +25561,12 @@ var MEDIA_ABILITY = {
|
|
|
25466
25561
|
label: "Media",
|
|
25467
25562
|
description: "Deliver media to Replicas and destination workflows with inline uploads.",
|
|
25468
25563
|
bullet: "- Producing or showing the user any media \u2014 screenshots, screen recordings, generated images or diagrams, audio clips, or HTML recap pages \u2014 including in your Replicas chat reply, PR descriptions/comments, and other external platforms. Paste the printed `` embeds in your chat reply, never only dashboard links, and upload the file itself to Slack, Linear, or GitLab. To show motion, turn the recording into a short GIF with `replicas media gif` instead of a grid of frames",
|
|
25469
|
-
section:
|
|
25470
|
-
referenceFile: { name: "MEDIA.md", content:
|
|
25564
|
+
section: SECTION8,
|
|
25565
|
+
referenceFile: { name: "MEDIA.md", content: REFERENCE8 }
|
|
25471
25566
|
};
|
|
25472
25567
|
|
|
25473
25568
|
// ../shared/src/default-skills/replicas-agent/abilities/previews.ts
|
|
25474
|
-
var
|
|
25569
|
+
var SECTION9 = `### Previews
|
|
25475
25570
|
Expose locally running services (web apps, APIs, databases) as public preview URLs so humans can interact with them directly.
|
|
25476
25571
|
|
|
25477
25572
|
**Reference:** \`references/PREVIEWS.md\`
|
|
@@ -25481,7 +25576,7 @@ Use this when:
|
|
|
25481
25576
|
- The task involves UI work that benefits from human review
|
|
25482
25577
|
- You are verifying frontend/backend integrations visually
|
|
25483
25578
|
- You need a URL for OAuth redirects, GitHub App callbacks, or webhooks (never the sandbox's \`*.e2b.app\` address)`;
|
|
25484
|
-
var
|
|
25579
|
+
var REFERENCE9 = `# Preview URLs
|
|
25485
25580
|
|
|
25486
25581
|
When you run services on ports \u2014 such as a web app, API server, or database \u2014 humans may want to interact with them directly. You can expose your locally running services as public preview URLs.
|
|
25487
25582
|
|
|
@@ -25570,12 +25665,12 @@ var PREVIEWS_ABILITY = {
|
|
|
25570
25665
|
label: "Previews",
|
|
25571
25666
|
description: "Expose locally running services on public preview URLs for humans.",
|
|
25572
25667
|
bullet: "- Creating preview URLs for locally running services, including OAuth redirect, app callback, and webhook URLs (never a raw `*.e2b.app` address)",
|
|
25573
|
-
section:
|
|
25574
|
-
referenceFile: { name: "PREVIEWS.md", content:
|
|
25668
|
+
section: SECTION9,
|
|
25669
|
+
referenceFile: { name: "PREVIEWS.md", content: REFERENCE9 }
|
|
25575
25670
|
};
|
|
25576
25671
|
|
|
25577
25672
|
// ../shared/src/default-skills/replicas-agent/abilities/plugins.ts
|
|
25578
|
-
var
|
|
25673
|
+
var SECTION10 = `### Plugins
|
|
25579
25674
|
Discover and execute installed marketplace tools through the workspace-scoped TypeScript SDK.
|
|
25580
25675
|
|
|
25581
25676
|
**Reference:** \`references/PLUGINS.md\`
|
|
@@ -25594,7 +25689,7 @@ Use this when:
|
|
|
25594
25689
|
- The user asks to read, triage, label, or draft replies to LinkedIn messages through Kondo
|
|
25595
25690
|
- The user asks to query Sazabi logs, investigate Sazabi issues, or ask the Sazabi agent about production
|
|
25596
25691
|
- The user asks to analyze text with Pangram`;
|
|
25597
|
-
var
|
|
25692
|
+
var REFERENCE10 = `# Plugins
|
|
25598
25693
|
|
|
25599
25694
|
Plugins are TypeScript libraries, not MCP servers. Import \`@replicas/sdk\`; Replicas authenticates the workspace and selects the most specific environment the workspace inherits from, then Global. Provider credentials, sessions, and project keys are never exposed.
|
|
25600
25695
|
|
|
@@ -25800,12 +25895,12 @@ var PLUGINS_ABILITY = {
|
|
|
25800
25895
|
label: "Plugins",
|
|
25801
25896
|
description: "Use installed marketplace tools through @replicas/sdk.",
|
|
25802
25897
|
bullet: "- Interacting with installed marketplace plugins through TypeScript",
|
|
25803
|
-
section:
|
|
25804
|
-
referenceFile: { name: "PLUGINS.md", content:
|
|
25898
|
+
section: SECTION10,
|
|
25899
|
+
referenceFile: { name: "PLUGINS.md", content: REFERENCE10 }
|
|
25805
25900
|
};
|
|
25806
25901
|
|
|
25807
25902
|
// ../shared/src/default-skills/replicas-agent/abilities/replicas.ts
|
|
25808
|
-
var
|
|
25903
|
+
var SECTION11 = `### Replicas (in-workspace CLI)
|
|
25809
25904
|
Take action *with* Replicas itself \u2014 manage automations, environments (variables, files), repos, and \`replicas.json\` config \u2014 using the pre-installed, pre-authenticated \`replicas\` CLI.
|
|
25810
25905
|
|
|
25811
25906
|
**Reference:** \`references/REPLICAS.md\`
|
|
@@ -25817,7 +25912,7 @@ Use this when:
|
|
|
25817
25912
|
- The user asks "what envs / repos / automations do I have?"
|
|
25818
25913
|
- The user asks you to scaffold a \`replicas.json\` / \`replicas.yaml\` in a repo
|
|
25819
25914
|
- You need to run a long-lived service (dev server, daemon) \u2014 always use \`replicas service start\` so it survives workspace sleep/wake`;
|
|
25820
|
-
var
|
|
25915
|
+
var REFERENCE11 = `# Replicas (in-workspace CLI)
|
|
25821
25916
|
|
|
25822
25917
|
This guide covers how to take action *with* Replicas itself from inside a Replicas workspace \u2014 managing automations, environments (and their variables/files), repos, previews, and the user's \`replicas.json\` config \u2014 using the pre-installed \`replicas\` CLI.
|
|
25823
25918
|
|
|
@@ -26078,13 +26173,13 @@ var REPLICAS_ABILITY = {
|
|
|
26078
26173
|
description: "Teach the agent about Replicas itself \u2014 automations, environments, the in-workspace CLI.",
|
|
26079
26174
|
// No bullet — help_instructions covers the `replicas` CLI surface in detail.
|
|
26080
26175
|
bullet: "",
|
|
26081
|
-
section:
|
|
26082
|
-
referenceFile: { name: "REPLICAS.md", content:
|
|
26176
|
+
section: SECTION11,
|
|
26177
|
+
referenceFile: { name: "REPLICAS.md", content: REFERENCE11 },
|
|
26083
26178
|
locked: true
|
|
26084
26179
|
};
|
|
26085
26180
|
|
|
26086
26181
|
// ../shared/src/default-skills/replicas-agent/abilities/sentry.ts
|
|
26087
|
-
var
|
|
26182
|
+
var SECTION12 = `### Sentry
|
|
26088
26183
|
Read issues and project data through the workspace-scoped TypeScript SDK.
|
|
26089
26184
|
|
|
26090
26185
|
**Reference:** \`references/SENTRY.md\`
|
|
@@ -26093,7 +26188,7 @@ Use this when:
|
|
|
26093
26188
|
- A user shares a sentry.io link
|
|
26094
26189
|
- You need to inspect Sentry projects or issues
|
|
26095
26190
|
- A user asks for error-monitoring context outside a webhook-triggered task`;
|
|
26096
|
-
var
|
|
26191
|
+
var REFERENCE12 = `# Sentry Integration
|
|
26097
26192
|
|
|
26098
26193
|
Use \`@replicas/sdk\`; never request or read a Sentry token. Sentry access is organization-scoped and read-only.
|
|
26099
26194
|
|
|
@@ -26122,8 +26217,8 @@ var SENTRY_ABILITY = {
|
|
|
26122
26217
|
label: "Sentry",
|
|
26123
26218
|
description: "Read Sentry projects and issues through @replicas/sdk.",
|
|
26124
26219
|
bullet: "- Interacting with Sentry (reading projects, issues, and error context, including any sentry.io link the user shares): use replicas.sentry.request from @replicas/sdk. Read references/SENTRY.md to turn an issue link into API paths.",
|
|
26125
|
-
section:
|
|
26126
|
-
referenceFile: { name: "SENTRY.md", content:
|
|
26220
|
+
section: SECTION12,
|
|
26221
|
+
referenceFile: { name: "SENTRY.md", content: REFERENCE12 }
|
|
26127
26222
|
};
|
|
26128
26223
|
|
|
26129
26224
|
// ../shared/src/memory.ts
|
|
@@ -26131,11 +26226,11 @@ var HISTORY_ROOT = `${SANDBOX_PATHS.REPLICAS_DIR}/history`;
|
|
|
26131
26226
|
var HISTORY_INDEX_FILENAME = "INDEX.md";
|
|
26132
26227
|
|
|
26133
26228
|
// ../shared/src/default-skills/replicas-agent/abilities/session-history.ts
|
|
26134
|
-
var
|
|
26229
|
+
var SECTION13 = `### Session history
|
|
26135
26230
|
Search past agent sessions in this organization when the user refers to prior work, a date, a teammate's session, or "last time".
|
|
26136
26231
|
|
|
26137
26232
|
**Reference:** \`references/SESSION-HISTORY.md\``;
|
|
26138
|
-
var
|
|
26233
|
+
var REFERENCE13 = `# Session history
|
|
26139
26234
|
|
|
26140
26235
|
Past sessions are installed under \`${HISTORY_ROOT}\`:
|
|
26141
26236
|
|
|
@@ -26164,8 +26259,8 @@ var SESSION_HISTORY_ABILITY = {
|
|
|
26164
26259
|
label: "Session history",
|
|
26165
26260
|
description: "Search past agent sessions in this organization.",
|
|
26166
26261
|
bullet: "",
|
|
26167
|
-
section:
|
|
26168
|
-
referenceFile: { name: "SESSION-HISTORY.md", content:
|
|
26262
|
+
section: SECTION13,
|
|
26263
|
+
referenceFile: { name: "SESSION-HISTORY.md", content: REFERENCE13 },
|
|
26169
26264
|
locked: true
|
|
26170
26265
|
};
|
|
26171
26266
|
|
|
@@ -26176,7 +26271,7 @@ var SLACK_BLOCK_KIT_GUIDANCE = "Use Block Kit whenever structure or a link would
|
|
|
26176
26271
|
var SLACK_PR_BUTTON_GUIDANCE = 'The first time you share a given pull request or merge request link in a thread \u2014 the message announcing you opened it \u2014 attach a button for it: send `blocks` alongside `text` \u2014 a `section` block holding your message as `mrkdwn`, then an `actions` block with one element per newly shared link, `{"type": "button", "style": "primary", "text": {"type": "plain_text", "text": "View PR"}, "url": "<pr url>", "action_id": "view_pr"}`. Later messages about a pull request you already linked in this thread keep the link in the sentence and get no button, and never re-post a button for a link someone already has. Label it "View MR" for GitLab, and if there are several give each button a unique `action_id` and name its repository in the label ("View PR \xB7 web"). Always keep `text` populated with the same message \u2014 Slack uses it for notifications and fallback \u2014 and keep the link in the sentence too.';
|
|
26177
26272
|
|
|
26178
26273
|
// ../shared/src/default-skills/replicas-agent/abilities/slack.ts
|
|
26179
|
-
var
|
|
26274
|
+
var SECTION14 = `### Slack
|
|
26180
26275
|
Send messages, read threads, search conversations, and transfer files through the workspace-scoped TypeScript SDK.
|
|
26181
26276
|
|
|
26182
26277
|
**Reference:** \`references/SLACK.md\`
|
|
@@ -26186,7 +26281,7 @@ Use this when:
|
|
|
26186
26281
|
- You need to read or search Slack conversations
|
|
26187
26282
|
- You need to download a file attached to a Slack message
|
|
26188
26283
|
- You encounter a Slack message link and need its full thread`;
|
|
26189
|
-
var
|
|
26284
|
+
var REFERENCE14 = `# Slack Integration
|
|
26190
26285
|
|
|
26191
26286
|
Use \`@replicas/sdk\`; never read or send a Slack token directly. The SDK calls Replicas, which selects only this workspace's organization connection.
|
|
26192
26287
|
|
|
@@ -26264,8 +26359,8 @@ var SLACK_ABILITY = {
|
|
|
26264
26359
|
label: "Slack",
|
|
26265
26360
|
description: "Send messages, read threads, search, and transfer files through @replicas/sdk.",
|
|
26266
26361
|
bullet: "- Interacting with Slack (listing channels, sending messages, reading threads, or searching): use replicas.slack.call from @replicas/sdk. Read references/SLACK.md; no Slack MCP server or provider token in the VM is required.",
|
|
26267
|
-
section:
|
|
26268
|
-
referenceFile: { name: "SLACK.md", content:
|
|
26362
|
+
section: SECTION14,
|
|
26363
|
+
referenceFile: { name: "SLACK.md", content: REFERENCE14 }
|
|
26269
26364
|
};
|
|
26270
26365
|
|
|
26271
26366
|
// ../shared/src/default-skills/replicas-agent/registry.ts
|
|
@@ -26277,6 +26372,7 @@ var REPLICAS_AGENT_ABILITY_REGISTRY = {
|
|
|
26277
26372
|
environment_setup: ENVIRONMENT_SETUP_ABILITY,
|
|
26278
26373
|
github: GITHUB_ABILITY,
|
|
26279
26374
|
gitlab: GITLAB_ABILITY,
|
|
26375
|
+
gpu: GPU_ABILITY,
|
|
26280
26376
|
linear: LINEAR_ABILITY,
|
|
26281
26377
|
media: MEDIA_ABILITY,
|
|
26282
26378
|
plugins: PLUGINS_ABILITY,
|
|
@@ -28669,7 +28765,7 @@ var headlessAgentRequestSchema = external_exports.object({
|
|
|
28669
28765
|
});
|
|
28670
28766
|
|
|
28671
28767
|
// ../shared/src/cli-version.ts
|
|
28672
|
-
var CLI_VERSION = "0.3.
|
|
28768
|
+
var CLI_VERSION = "0.3.104";
|
|
28673
28769
|
|
|
28674
28770
|
// ../shared/src/version.ts
|
|
28675
28771
|
function compareVersions(v1, v2) {
|
|
@@ -29931,7 +30027,7 @@ async function apiKeyLoginCommand(key) {
|
|
|
29931
30027
|
}
|
|
29932
30028
|
async function loginCommand() {
|
|
29933
30029
|
const state = generateState();
|
|
29934
|
-
return new Promise((
|
|
30030
|
+
return new Promise((resolve3, reject) => {
|
|
29935
30031
|
let authTimeout;
|
|
29936
30032
|
let hasHandledCallback = false;
|
|
29937
30033
|
let lastRedirectUrl = null;
|
|
@@ -30081,7 +30177,7 @@ async function loginCommand() {
|
|
|
30081
30177
|
setImmediate(() => {
|
|
30082
30178
|
server.closeAllConnections?.();
|
|
30083
30179
|
server.close();
|
|
30084
|
-
|
|
30180
|
+
resolve3();
|
|
30085
30181
|
});
|
|
30086
30182
|
} catch (error52) {
|
|
30087
30183
|
const errorUrl = `${WEB_APP_URL}/cli-login/error?message=${encodeURIComponent("Failed to verify authentication.")}`;
|
|
@@ -30321,7 +30417,7 @@ function buildSSHArgs(token, host, proxyCommand, localForward) {
|
|
|
30321
30417
|
];
|
|
30322
30418
|
}
|
|
30323
30419
|
async function connectSSH(token, host, proxyCommand, localForward) {
|
|
30324
|
-
return new Promise((
|
|
30420
|
+
return new Promise((resolve3, reject) => {
|
|
30325
30421
|
const ssh = (0, import_child_process.spawn)("ssh", buildSSHArgs(token, host, proxyCommand, localForward), {
|
|
30326
30422
|
stdio: "inherit"
|
|
30327
30423
|
});
|
|
@@ -30329,7 +30425,7 @@ async function connectSSH(token, host, proxyCommand, localForward) {
|
|
|
30329
30425
|
if (localForward && code) {
|
|
30330
30426
|
reject(new Error(`SSH tunnel exited with code ${code}`));
|
|
30331
30427
|
} else {
|
|
30332
|
-
|
|
30428
|
+
resolve3();
|
|
30333
30429
|
}
|
|
30334
30430
|
});
|
|
30335
30431
|
ssh.on("error", reject);
|
|
@@ -30827,7 +30923,7 @@ async function exchangeCodeForTokens(code, codeVerifier) {
|
|
|
30827
30923
|
};
|
|
30828
30924
|
}
|
|
30829
30925
|
function startCallbackServer(expectedState, codeVerifier, save) {
|
|
30830
|
-
return new Promise((
|
|
30926
|
+
return new Promise((resolve3, reject) => {
|
|
30831
30927
|
const server = import_http2.default.createServer(async (req, res) => {
|
|
30832
30928
|
try {
|
|
30833
30929
|
if (!req.url) {
|
|
@@ -30871,7 +30967,7 @@ function startCallbackServer(expectedState, codeVerifier, save) {
|
|
|
30871
30967
|
res.writeHead(302, { "Location": completionUrl });
|
|
30872
30968
|
res.end();
|
|
30873
30969
|
server.close();
|
|
30874
|
-
|
|
30970
|
+
resolve3();
|
|
30875
30971
|
} catch (tokenError) {
|
|
30876
30972
|
const errorMessage = encodeURIComponent(tokenError instanceof Error ? tokenError.message : "Unknown error");
|
|
30877
30973
|
res.writeHead(302, { "Location": `${WEB_APP_URL2}/codex/oauth/error?message=${errorMessage}` });
|
|
@@ -30905,7 +31001,7 @@ async function runCodexOAuthFlow(totalSteps, save) {
|
|
|
30905
31001
|
const state = generateState2();
|
|
30906
31002
|
const authUrl = buildAuthorizationUrl(pkce.codeChallenge, state);
|
|
30907
31003
|
const tokensPromise = startCallbackServer(state, pkce.codeVerifier, save);
|
|
30908
|
-
await new Promise((
|
|
31004
|
+
await new Promise((resolve3) => setTimeout(resolve3, 500));
|
|
30909
31005
|
renderAuthFlowStep(1, totalSteps, "Approve access in your browser");
|
|
30910
31006
|
await openForApproval("auth.openai.com", authUrl);
|
|
30911
31007
|
renderAuthFlowDetail("waiting for sign-in\u2026");
|
|
@@ -31053,10 +31149,10 @@ async function promptForAuthorizationCode() {
|
|
|
31053
31149
|
process.stdin.setRawMode(false);
|
|
31054
31150
|
process.stdin.resume();
|
|
31055
31151
|
const rl = import_readline.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
31056
|
-
return new Promise((
|
|
31057
|
-
rl.on("close", () =>
|
|
31152
|
+
return new Promise((resolve3) => {
|
|
31153
|
+
rl.on("close", () => resolve3(""));
|
|
31058
31154
|
rl.question(import_chalk15.default.gray(" Paste code here: "), (answer) => {
|
|
31059
|
-
|
|
31155
|
+
resolve3(answer.trim());
|
|
31060
31156
|
rl.close();
|
|
31061
31157
|
});
|
|
31062
31158
|
});
|
|
@@ -31203,7 +31299,7 @@ async function runMuseOAuthFlow(totalSteps) {
|
|
|
31203
31299
|
const deadline = Date.now() + (typeof device.expires_in === "number" ? device.expires_in : 900) * 1e3;
|
|
31204
31300
|
let intervalMs = Math.max(1, typeof device.interval === "number" ? device.interval : 5) * 1e3;
|
|
31205
31301
|
while (Date.now() < deadline) {
|
|
31206
|
-
await new Promise((
|
|
31302
|
+
await new Promise((resolve3) => setTimeout(resolve3, intervalMs));
|
|
31207
31303
|
const response = await post("/oidc/device/token/", new URLSearchParams({
|
|
31208
31304
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
31209
31305
|
device_code: device.device_code,
|
|
@@ -32861,7 +32957,7 @@ async function environmentSetupWaitVariablesCommand(options) {
|
|
|
32861
32957
|
Waiting up to ${formatDuration(timeoutMs)} for: ${blocking(before).map((request) => request.key).join(", ")}`));
|
|
32862
32958
|
const deadline = Date.now() + timeoutMs;
|
|
32863
32959
|
while (blocking(run2.variable_requests).length > 0 && Date.now() < deadline) {
|
|
32864
|
-
await new Promise((
|
|
32960
|
+
await new Promise((resolve3) => setTimeout(resolve3, ENVIRONMENT_SETUP_VARIABLE_WAIT.pollMs));
|
|
32865
32961
|
run2 = (await fetchCurrentRun()).run;
|
|
32866
32962
|
}
|
|
32867
32963
|
}
|
|
@@ -33755,19 +33851,22 @@ async function agentRequest(path8, options) {
|
|
|
33755
33851
|
if (!config3) {
|
|
33756
33852
|
throw new Error("Agent mode config not found");
|
|
33757
33853
|
}
|
|
33758
|
-
const
|
|
33854
|
+
const rawBody = options?.body;
|
|
33855
|
+
const isFormData = rawBody instanceof FormData;
|
|
33856
|
+
const isStream = rawBody instanceof ReadableStream;
|
|
33759
33857
|
const headers = {
|
|
33760
33858
|
"Authorization": `Bearer ${config3.engine_secret}`,
|
|
33761
33859
|
"X-Workspace-Id": config3.workspace_id,
|
|
33762
33860
|
// FormData sets its own multipart Content-Type with boundary.
|
|
33763
|
-
...isFormData ? {} : { "Content-Type": "application/json" },
|
|
33861
|
+
...isFormData ? {} : { "Content-Type": isStream ? "application/octet-stream" : "application/json" },
|
|
33764
33862
|
...options?.headers || {}
|
|
33765
33863
|
};
|
|
33766
|
-
const body =
|
|
33864
|
+
const body = rawBody === void 0 ? void 0 : rawBody instanceof FormData || rawBody instanceof ReadableStream ? rawBody : JSON.stringify(rawBody);
|
|
33767
33865
|
const response = await fetch(`${MONOLITH_URL}${path8}`, {
|
|
33768
33866
|
...options,
|
|
33769
33867
|
headers,
|
|
33770
|
-
body
|
|
33868
|
+
body,
|
|
33869
|
+
...isStream ? { duplex: "half" } : {}
|
|
33771
33870
|
});
|
|
33772
33871
|
if (!response.ok) {
|
|
33773
33872
|
const error51 = await response.json().catch(() => ({ error: "Request failed" }));
|
|
@@ -34139,27 +34238,247 @@ async function mediaGifCommand(input, options) {
|
|
|
34139
34238
|
);
|
|
34140
34239
|
}
|
|
34141
34240
|
|
|
34142
|
-
// src/commands/
|
|
34241
|
+
// src/commands/gpu.ts
|
|
34242
|
+
var import_chalk28 = __toESM(require("chalk"));
|
|
34243
|
+
var import_node_child_process2 = require("child_process");
|
|
34143
34244
|
var import_node_fs2 = require("fs");
|
|
34144
34245
|
var import_node_path2 = require("path");
|
|
34246
|
+
var import_node_stream = require("stream");
|
|
34247
|
+
var import_promises = require("stream/promises");
|
|
34248
|
+
var BASE = "/v1/engine/compute/sandboxes";
|
|
34249
|
+
var CREATE_POLL_INTERVAL_MS = 3e3;
|
|
34250
|
+
var CREATE_WAIT_TIMEOUT_MS = 25 * 6e4;
|
|
34251
|
+
function parseMinutes(value, flag) {
|
|
34252
|
+
if (value === void 0) return void 0;
|
|
34253
|
+
const match = value.trim().match(/^(\d+)(m|h)?$/);
|
|
34254
|
+
if (!match) throw new Error(`${flag} must be minutes or a duration like 30m or 2h`);
|
|
34255
|
+
return Number(match[1]) * (match[2] === "h" ? 60 : 1);
|
|
34256
|
+
}
|
|
34257
|
+
function parseEnv(values) {
|
|
34258
|
+
return values?.length ? parseKeyValuePairs(values) : void 0;
|
|
34259
|
+
}
|
|
34260
|
+
function describe3(sandbox) {
|
|
34261
|
+
console.log(`${import_chalk28.default.bold(sandbox.id)} ${sandbox.status} ${sandbox.gpu ?? "no GPU"} ${sandbox.image}`);
|
|
34262
|
+
console.log(import_chalk28.default.gray(` expires ${new Date(sandbox.expiresAt).toLocaleString()}`));
|
|
34263
|
+
for (const tunnel of sandbox.tunnels) console.log(` port ${tunnel.port}: ${tunnel.url}`);
|
|
34264
|
+
if (sandbox.error) console.log(import_chalk28.default.red(` ${sandbox.error}`));
|
|
34265
|
+
}
|
|
34266
|
+
async function resolveSandboxId(options) {
|
|
34267
|
+
if (options.sandbox) return options.sandbox;
|
|
34268
|
+
const { sandboxes } = await agentFetch(BASE);
|
|
34269
|
+
if (sandboxes.length === 1) return sandboxes[0].id;
|
|
34270
|
+
if (sandboxes.length === 0) throw new Error('No GPU sandbox is running. Start one with "replicas gpu create".');
|
|
34271
|
+
throw new Error(`Several GPU sandboxes are running; pick one with --sandbox: ${sandboxes.map((sandbox) => sandbox.id).join(", ")}`);
|
|
34272
|
+
}
|
|
34273
|
+
async function createSandbox(options) {
|
|
34274
|
+
const request = {
|
|
34275
|
+
kind: "gpu",
|
|
34276
|
+
gpu: options.gpu,
|
|
34277
|
+
image: options.image,
|
|
34278
|
+
timeoutMinutes: parseMinutes(options.timeout, "--timeout"),
|
|
34279
|
+
idleTimeoutMinutes: parseMinutes(options.idleTimeout, "--idle-timeout"),
|
|
34280
|
+
ports: options.port?.map(parsePort),
|
|
34281
|
+
env: parseEnv(options.env)
|
|
34282
|
+
};
|
|
34283
|
+
let { sandbox } = await agentFetch(BASE, { method: "POST", body: request });
|
|
34284
|
+
if (options.wait === false) return sandbox;
|
|
34285
|
+
process.stderr.write(import_chalk28.default.gray(`Starting ${sandbox.gpu} sandbox ${sandbox.id} (a new image can take a few minutes to pull)...
|
|
34286
|
+
`));
|
|
34287
|
+
const deadline = Date.now() + CREATE_WAIT_TIMEOUT_MS;
|
|
34288
|
+
while (sandbox.status === "creating") {
|
|
34289
|
+
if (Date.now() > deadline) throw new Error(`GPU sandbox ${sandbox.id} is still starting; check it with "replicas gpu list"`);
|
|
34290
|
+
await new Promise((done) => setTimeout(done, CREATE_POLL_INTERVAL_MS));
|
|
34291
|
+
({ sandbox } = await agentFetch(`${BASE}/${sandbox.id}`));
|
|
34292
|
+
}
|
|
34293
|
+
if (sandbox.status !== "running") throw new Error(`GPU sandbox failed to start: ${sandbox.error ?? "it stopped while starting"}`);
|
|
34294
|
+
return sandbox;
|
|
34295
|
+
}
|
|
34296
|
+
async function gpuCreateCommand(options) {
|
|
34297
|
+
output(await createSandbox(options), describe3);
|
|
34298
|
+
}
|
|
34299
|
+
async function gpuListCommand() {
|
|
34300
|
+
const { sandboxes } = await agentFetch(BASE);
|
|
34301
|
+
output(sandboxes, (list2) => {
|
|
34302
|
+
if (list2.length === 0) console.log("No GPU sandboxes are running.");
|
|
34303
|
+
list2.forEach(describe3);
|
|
34304
|
+
});
|
|
34305
|
+
}
|
|
34306
|
+
async function gpuDeleteCommand(id, options) {
|
|
34307
|
+
const sandboxId = id ?? await resolveSandboxId(options);
|
|
34308
|
+
await agentRequest(`${BASE}/${sandboxId}`, { method: "DELETE" });
|
|
34309
|
+
outputSuccess(`Stopped GPU sandbox ${sandboxId}`);
|
|
34310
|
+
}
|
|
34311
|
+
async function streamOutput(response) {
|
|
34312
|
+
if (!response.body) throw new Error("Empty response from the GPU sandbox");
|
|
34313
|
+
const decoder2 = new TextDecoder();
|
|
34314
|
+
let buffer = "";
|
|
34315
|
+
let exit;
|
|
34316
|
+
const handle = (line) => {
|
|
34317
|
+
if (!line.trim()) return;
|
|
34318
|
+
const event = JSON.parse(line);
|
|
34319
|
+
if (event.type === "stdout") (isJsonMode() ? process.stderr : process.stdout).write(event.data);
|
|
34320
|
+
else if (event.type === "stderr") process.stderr.write(event.data);
|
|
34321
|
+
else if (event.type === "error") throw new Error(event.message);
|
|
34322
|
+
else if (event.type === "exit") exit = { exitCode: event.exitCode };
|
|
34323
|
+
};
|
|
34324
|
+
for await (const chunk of import_node_stream.Readable.fromWeb(response.body)) {
|
|
34325
|
+
buffer += decoder2.decode(chunk, { stream: true });
|
|
34326
|
+
const lines = buffer.split("\n");
|
|
34327
|
+
buffer = lines.pop() ?? "";
|
|
34328
|
+
lines.forEach(handle);
|
|
34329
|
+
}
|
|
34330
|
+
handle(buffer + decoder2.decode());
|
|
34331
|
+
if (!exit) {
|
|
34332
|
+
throw new Error('Lost the connection to the GPU sandbox; the command may still be running. Use --detach and "replicas gpu logs -f" for long jobs.');
|
|
34333
|
+
}
|
|
34334
|
+
return exit.exitCode;
|
|
34335
|
+
}
|
|
34336
|
+
async function exec(sandboxId, command, options) {
|
|
34337
|
+
return agentRequest(`${BASE}/${sandboxId}/exec`, {
|
|
34338
|
+
method: "POST",
|
|
34339
|
+
body: { command, workdir: options.workdir, env: parseEnv(options.env), detach: options.detach }
|
|
34340
|
+
});
|
|
34341
|
+
}
|
|
34342
|
+
async function gpuExecCommand(command, options) {
|
|
34343
|
+
if (command.length === 0) throw new Error("Pass a command after --, e.g. replicas gpu exec -- nvidia-smi");
|
|
34344
|
+
const sandboxId = await resolveSandboxId(options);
|
|
34345
|
+
const response = await exec(sandboxId, command.join(" "), options);
|
|
34346
|
+
if (options.detach) {
|
|
34347
|
+
const { jobId } = await response.json();
|
|
34348
|
+
output({ jobId, sandboxId }, () => {
|
|
34349
|
+
console.log(`Started job ${import_chalk28.default.bold(jobId)} in ${sandboxId}`);
|
|
34350
|
+
console.log(import_chalk28.default.gray(`Follow it with: replicas gpu logs ${jobId} -f`));
|
|
34351
|
+
});
|
|
34352
|
+
return;
|
|
34353
|
+
}
|
|
34354
|
+
const exitCode = await streamOutput(response);
|
|
34355
|
+
process.exitCode = exitCode ?? 1;
|
|
34356
|
+
if (isJsonMode()) output({ sandboxId, exitCode }, () => {
|
|
34357
|
+
});
|
|
34358
|
+
}
|
|
34359
|
+
async function gpuLogsCommand(jobId, options) {
|
|
34360
|
+
const sandboxId = await resolveSandboxId(options);
|
|
34361
|
+
const response = await agentRequest(`${BASE}/${sandboxId}/jobs/${jobId}/logs${options.follow ? "?follow=true" : ""}`);
|
|
34362
|
+
const exitCode = await streamOutput(response);
|
|
34363
|
+
if (isJsonMode()) {
|
|
34364
|
+
if (options.follow && exitCode !== null) process.exitCode = exitCode;
|
|
34365
|
+
output({ jobId, sandboxId, running: exitCode === null, exitCode }, () => {
|
|
34366
|
+
});
|
|
34367
|
+
return;
|
|
34368
|
+
}
|
|
34369
|
+
if (exitCode === null) {
|
|
34370
|
+
process.stderr.write(import_chalk28.default.gray(`
|
|
34371
|
+
Job ${jobId} is still running.
|
|
34372
|
+
`));
|
|
34373
|
+
return;
|
|
34374
|
+
}
|
|
34375
|
+
if (options.follow) process.exitCode = exitCode;
|
|
34376
|
+
process.stderr.write(import_chalk28.default.gray(`
|
|
34377
|
+
Job ${jobId} exited with code ${exitCode}.
|
|
34378
|
+
`));
|
|
34379
|
+
}
|
|
34380
|
+
function assertTransferSize(bytes, localPath) {
|
|
34381
|
+
if (bytes > COMPUTE_SANDBOX_LIMITS.maxTransferBytes) {
|
|
34382
|
+
throw new Error(`${localPath} is larger than ${COMPUTE_SANDBOX_LIMITS.maxTransferBytes / 1024 ** 3} GiB. Download large datasets and weights inside the sandbox instead (it has internet access).`);
|
|
34383
|
+
}
|
|
34384
|
+
}
|
|
34385
|
+
function archive(localPath) {
|
|
34386
|
+
const path8 = (0, import_node_path2.resolve)(localPath);
|
|
34387
|
+
if (!(0, import_node_fs2.existsSync)(path8)) throw new Error(`${path8} does not exist`);
|
|
34388
|
+
if (!(0, import_node_fs2.statSync)(path8).isDirectory()) {
|
|
34389
|
+
assertTransferSize((0, import_node_fs2.statSync)(path8).size, localPath);
|
|
34390
|
+
return (0, import_node_child_process2.spawn)("tar", ["-czf", "-", "-C", (0, import_node_path2.dirname)(path8), (0, import_node_path2.basename)(path8)], { stdio: ["ignore", "pipe", "pipe"] });
|
|
34391
|
+
}
|
|
34392
|
+
const listed = (0, import_node_child_process2.spawnSync)("git", ["-C", path8, "ls-files", "-co", "--exclude-standard", "-z"], { maxBuffer: 256 * 1024 * 1024 });
|
|
34393
|
+
if (listed.status !== 0) {
|
|
34394
|
+
const du = (0, import_node_child_process2.spawnSync)("du", ["-sk", "--exclude=.git", "--exclude=node_modules", path8]);
|
|
34395
|
+
assertTransferSize(Number(du.stdout.toString().split(" ")[0]) * 1024, localPath);
|
|
34396
|
+
return (0, import_node_child_process2.spawn)("tar", ["-czf", "-", "-C", path8, "--exclude=.git", "--exclude=node_modules", "."], { stdio: ["ignore", "pipe", "pipe"] });
|
|
34397
|
+
}
|
|
34398
|
+
const files = listed.stdout.toString().split("\0").filter((file2) => file2 && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(path8, file2)));
|
|
34399
|
+
assertTransferSize(files.reduce((total, file2) => total + (0, import_node_fs2.statSync)((0, import_node_path2.join)(path8, file2)).size, 0), localPath);
|
|
34400
|
+
const child = (0, import_node_child_process2.spawn)("tar", ["-czf", "-", "-C", path8, "--null", "-T", "-"], { stdio: ["pipe", "pipe", "pipe"] });
|
|
34401
|
+
child.stdin.end(files.map((file2) => `${file2}\0`).join(""));
|
|
34402
|
+
return child;
|
|
34403
|
+
}
|
|
34404
|
+
async function push(sandboxId, localPath, remotePath) {
|
|
34405
|
+
const tar = archive(localPath);
|
|
34406
|
+
let stderr = "";
|
|
34407
|
+
tar.stderr?.on("data", (chunk) => {
|
|
34408
|
+
stderr += chunk.toString();
|
|
34409
|
+
});
|
|
34410
|
+
const exited = new Promise((done) => tar.on("close", done));
|
|
34411
|
+
await agentRequest(`${BASE}/${sandboxId}/files?path=${encodeURIComponent(remotePath)}`, {
|
|
34412
|
+
method: "PUT",
|
|
34413
|
+
body: import_node_stream.Readable.toWeb(tar.stdout)
|
|
34414
|
+
});
|
|
34415
|
+
if (await exited !== 0) throw new Error(`Archiving ${localPath} failed: ${stderr.trim()}`);
|
|
34416
|
+
}
|
|
34417
|
+
async function gpuPushCommand(localPath = ".", remotePath = COMPUTE_SANDBOX_WORKDIR, options = {}) {
|
|
34418
|
+
const sandboxId = await resolveSandboxId(options);
|
|
34419
|
+
await push(sandboxId, localPath, remotePath);
|
|
34420
|
+
outputSuccess(`Copied ${localPath} to ${remotePath} in ${sandboxId}`);
|
|
34421
|
+
}
|
|
34422
|
+
async function pull(sandboxId, remotePath, localPath) {
|
|
34423
|
+
const response = await agentRequest(`${BASE}/${sandboxId}/files?path=${encodeURIComponent(remotePath)}`);
|
|
34424
|
+
(0, import_node_fs2.mkdirSync)(localPath, { recursive: true });
|
|
34425
|
+
const tar = (0, import_node_child_process2.spawn)("tar", ["-xzf", "-", "-C", localPath], { stdio: ["pipe", "ignore", "pipe"] });
|
|
34426
|
+
let stderr = "";
|
|
34427
|
+
tar.stderr.on("data", (chunk) => {
|
|
34428
|
+
stderr += chunk.toString();
|
|
34429
|
+
});
|
|
34430
|
+
const exited = new Promise((done) => tar.on("close", done));
|
|
34431
|
+
await (0, import_promises.pipeline)(import_node_stream.Readable.fromWeb(response.body), tar.stdin);
|
|
34432
|
+
if (await exited !== 0) throw new Error(`Extracting ${remotePath} failed: ${stderr.trim()}`);
|
|
34433
|
+
}
|
|
34434
|
+
async function gpuPullCommand(remotePath, localPath = ".", options = {}) {
|
|
34435
|
+
const sandboxId = await resolveSandboxId(options);
|
|
34436
|
+
await pull(sandboxId, remotePath, localPath);
|
|
34437
|
+
outputSuccess(`Copied ${remotePath} from ${sandboxId} into ${localPath}`);
|
|
34438
|
+
}
|
|
34439
|
+
async function gpuRunCommand(command, options) {
|
|
34440
|
+
if (command.length === 0) throw new Error("Pass a command after --, e.g. replicas gpu run -- python train.py");
|
|
34441
|
+
const { env, ...createOptions } = options;
|
|
34442
|
+
const sandbox = await createSandbox({ ...createOptions, wait: true });
|
|
34443
|
+
try {
|
|
34444
|
+
process.stderr.write(import_chalk28.default.gray(`Copying ${process.cwd()} to ${COMPUTE_SANDBOX_WORKDIR}...
|
|
34445
|
+
`));
|
|
34446
|
+
await push(sandbox.id, ".", COMPUTE_SANDBOX_WORKDIR);
|
|
34447
|
+
const exitCode = await streamOutput(await exec(sandbox.id, command.join(" "), { env }));
|
|
34448
|
+
for (const remotePath of options.pull ?? []) {
|
|
34449
|
+
await pull(sandbox.id, remotePath.startsWith("/") ? remotePath : `${COMPUTE_SANDBOX_WORKDIR}/${remotePath}`, ".");
|
|
34450
|
+
}
|
|
34451
|
+
process.exitCode = exitCode ?? 1;
|
|
34452
|
+
if (isJsonMode()) output({ sandboxId: sandbox.id, exitCode }, () => {
|
|
34453
|
+
});
|
|
34454
|
+
} finally {
|
|
34455
|
+
if (options.keep) process.stderr.write(import_chalk28.default.gray(`Kept GPU sandbox ${sandbox.id} running.
|
|
34456
|
+
`));
|
|
34457
|
+
else await agentRequest(`${BASE}/${sandbox.id}`, { method: "DELETE" });
|
|
34458
|
+
}
|
|
34459
|
+
}
|
|
34460
|
+
|
|
34461
|
+
// src/commands/history.ts
|
|
34462
|
+
var import_node_fs3 = require("fs");
|
|
34463
|
+
var import_node_path3 = require("path");
|
|
34145
34464
|
async function historyTranscriptCommand(name) {
|
|
34146
34465
|
const session = name.trim().replace(/\.md$/, "");
|
|
34147
34466
|
if (!/^[a-zA-Z0-9-]+$/.test(session)) {
|
|
34148
34467
|
throw new Error(`Invalid session name '${name}'. Use the name from INDEX.md, e.g. 2026-01-31-<id>`);
|
|
34149
34468
|
}
|
|
34150
34469
|
const response = await agentRequest(`/v1/engine/history/transcripts/${session}`);
|
|
34151
|
-
const dir = (0,
|
|
34152
|
-
(0,
|
|
34153
|
-
const file2 = (0,
|
|
34154
|
-
(0,
|
|
34470
|
+
const dir = (0, import_node_path3.join)(HISTORY_ROOT, "transcripts");
|
|
34471
|
+
(0, import_node_fs3.mkdirSync)(dir, { recursive: true });
|
|
34472
|
+
const file2 = (0, import_node_path3.join)(dir, `${session}.jsonl`);
|
|
34473
|
+
(0, import_node_fs3.writeFileSync)(file2, Buffer.from(await response.arrayBuffer()));
|
|
34155
34474
|
output({ path: file2 }, () => console.log(file2));
|
|
34156
34475
|
}
|
|
34157
34476
|
|
|
34158
34477
|
// src/commands/skills.ts
|
|
34159
|
-
var
|
|
34160
|
-
var
|
|
34478
|
+
var import_node_fs4 = require("fs");
|
|
34479
|
+
var import_chalk29 = __toESM(require("chalk"));
|
|
34161
34480
|
async function skillsSuggestCommand(file2, options) {
|
|
34162
|
-
const body = { content: (0,
|
|
34481
|
+
const body = { content: (0, import_node_fs4.readFileSync)(file2, "utf-8"), title: options.title };
|
|
34163
34482
|
const { id, name, update } = await agentFetch("/v1/learned-skills/suggestions", {
|
|
34164
34483
|
method: "POST",
|
|
34165
34484
|
body
|
|
@@ -34174,12 +34493,12 @@ async function skillsSuggestCommand(file2, options) {
|
|
|
34174
34493
|
async function skillsListCommand() {
|
|
34175
34494
|
output(await orgAuthenticatedFetch("/v1/learned-skills"), ({ skills: skills2, suggestions }) => {
|
|
34176
34495
|
for (const skill of skills2) console.log(`${skill.id} ${skill.name} ${skill.title}`);
|
|
34177
|
-
if (suggestions.length > 0) console.log(
|
|
34496
|
+
if (suggestions.length > 0) console.log(import_chalk29.default.gray(`${suggestions.length} pending suggestion(s) to review in the dashboard.`));
|
|
34178
34497
|
});
|
|
34179
34498
|
}
|
|
34180
34499
|
async function skillsCreateCommand(file2, options) {
|
|
34181
|
-
const body = { content: (0,
|
|
34182
|
-
output(await orgAuthenticatedFetch("/v1/learned-skills", { method: "POST", body }), ({ id, name }) => console.log(
|
|
34500
|
+
const body = { content: (0, import_node_fs4.readFileSync)(file2, "utf-8"), title: options.title };
|
|
34501
|
+
output(await orgAuthenticatedFetch("/v1/learned-skills", { method: "POST", body }), ({ id, name }) => console.log(import_chalk29.default.green(`Saved learning '${name}' (${id})`)));
|
|
34183
34502
|
}
|
|
34184
34503
|
async function skillsUpdateCommand(id, options) {
|
|
34185
34504
|
const { skills: skills2 } = await orgAuthenticatedFetch("/v1/learned-skills");
|
|
@@ -34187,9 +34506,9 @@ async function skillsUpdateCommand(id, options) {
|
|
|
34187
34506
|
if (!existing) throw new Error(`Learning not found: ${id}`);
|
|
34188
34507
|
const body = {
|
|
34189
34508
|
title: options.title ?? existing.title,
|
|
34190
|
-
content: options.file ? (0,
|
|
34509
|
+
content: options.file ? (0, import_node_fs4.readFileSync)(options.file, "utf-8") : existing.content
|
|
34191
34510
|
};
|
|
34192
|
-
output(await orgAuthenticatedFetch(`/v1/learned-skills/${existing.id}`, { method: "PUT", body }), ({ name }) => console.log(
|
|
34511
|
+
output(await orgAuthenticatedFetch(`/v1/learned-skills/${existing.id}`, { method: "PUT", body }), ({ name }) => console.log(import_chalk29.default.green(`Updated learning '${name}'`)));
|
|
34193
34512
|
}
|
|
34194
34513
|
async function skillsDeleteCommand(id) {
|
|
34195
34514
|
await orgAuthenticatedFetch(`/v1/learned-skills/${id}`, { method: "DELETE" });
|
|
@@ -34197,7 +34516,7 @@ async function skillsDeleteCommand(id) {
|
|
|
34197
34516
|
}
|
|
34198
34517
|
|
|
34199
34518
|
// src/commands/slack.ts
|
|
34200
|
-
var
|
|
34519
|
+
var import_chalk30 = __toESM(require("chalk"));
|
|
34201
34520
|
function getThreadOptions(options) {
|
|
34202
34521
|
const channelId = options.channel || process.env.REPLICAS_SLACK_CHANNEL_ID || process.env.SLACK_CHANNEL_ID;
|
|
34203
34522
|
const threadTs = options.threadTs || process.env.REPLICAS_SLACK_THREAD_TS || process.env.SLACK_THREAD_TS;
|
|
@@ -34215,10 +34534,10 @@ async function attachThread(request) {
|
|
|
34215
34534
|
body: request
|
|
34216
34535
|
});
|
|
34217
34536
|
output(response, ({ thread, workspace }) => {
|
|
34218
|
-
console.log(
|
|
34219
|
-
console.log(
|
|
34220
|
-
console.log(
|
|
34221
|
-
console.log(
|
|
34537
|
+
console.log(import_chalk30.default.green("Slack thread attached."));
|
|
34538
|
+
console.log(import_chalk30.default.gray(` Channel: ${thread.channel_id}`));
|
|
34539
|
+
console.log(import_chalk30.default.gray(` Thread: ${thread.thread_ts}`));
|
|
34540
|
+
console.log(import_chalk30.default.gray(` Workspace: ${workspace.name} (${workspace.id})`));
|
|
34222
34541
|
});
|
|
34223
34542
|
}
|
|
34224
34543
|
async function slackThreadAttachCommand(options) {
|
|
@@ -34248,12 +34567,12 @@ async function slackThreadSwitchCommand(workspace, options) {
|
|
|
34248
34567
|
}
|
|
34249
34568
|
|
|
34250
34569
|
// src/commands/service.ts
|
|
34251
|
-
var
|
|
34252
|
-
var
|
|
34253
|
-
var
|
|
34570
|
+
var import_chalk31 = __toESM(require("chalk"));
|
|
34571
|
+
var import_node_child_process3 = require("child_process");
|
|
34572
|
+
var import_node_fs5 = require("fs");
|
|
34254
34573
|
var import_node_os2 = require("os");
|
|
34255
|
-
var
|
|
34256
|
-
var SERVICES_DIR = (0,
|
|
34574
|
+
var import_node_path4 = require("path");
|
|
34575
|
+
var SERVICES_DIR = (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".replicas", "services");
|
|
34257
34576
|
var NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
34258
34577
|
function isValidServiceName(name) {
|
|
34259
34578
|
return NAME_PATTERN.test(name);
|
|
@@ -34264,7 +34583,7 @@ function tailLines(content, lines) {
|
|
|
34264
34583
|
return all.slice(-lines).join("\n").trim();
|
|
34265
34584
|
}
|
|
34266
34585
|
function statePath(name) {
|
|
34267
|
-
return (0,
|
|
34586
|
+
return (0, import_node_path4.join)(SERVICES_DIR, `${name}.json`);
|
|
34268
34587
|
}
|
|
34269
34588
|
function isServiceState(value) {
|
|
34270
34589
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -34274,7 +34593,7 @@ function isServiceState(value) {
|
|
|
34274
34593
|
function readState(name) {
|
|
34275
34594
|
let parsed;
|
|
34276
34595
|
try {
|
|
34277
|
-
parsed = JSON.parse((0,
|
|
34596
|
+
parsed = JSON.parse((0, import_node_fs5.readFileSync)(statePath(name), "utf-8"));
|
|
34278
34597
|
} catch {
|
|
34279
34598
|
return null;
|
|
34280
34599
|
}
|
|
@@ -34308,7 +34627,7 @@ async function killServiceGroup(pid) {
|
|
|
34308
34627
|
}
|
|
34309
34628
|
function tailLog(logFile, lines) {
|
|
34310
34629
|
try {
|
|
34311
|
-
return tailLines((0,
|
|
34630
|
+
return tailLines((0, import_node_fs5.readFileSync)(logFile, "utf-8"), lines);
|
|
34312
34631
|
} catch {
|
|
34313
34632
|
return "";
|
|
34314
34633
|
}
|
|
@@ -34326,20 +34645,20 @@ async function serviceStartCommand(name, commandParts, options) {
|
|
|
34326
34645
|
console.log(`Stopping existing "${name}" (pid ${existing.pid}) before restart`);
|
|
34327
34646
|
await killServiceGroup(existing.pid);
|
|
34328
34647
|
}
|
|
34329
|
-
(0,
|
|
34330
|
-
const cwd = (0,
|
|
34331
|
-
const logFile = (0,
|
|
34332
|
-
const logFd = (0,
|
|
34333
|
-
(0,
|
|
34648
|
+
(0, import_node_fs5.mkdirSync)(SERVICES_DIR, { recursive: true });
|
|
34649
|
+
const cwd = (0, import_node_path4.resolve)(options.cwd ?? process.cwd());
|
|
34650
|
+
const logFile = (0, import_node_path4.join)(SERVICES_DIR, `${name}.log`);
|
|
34651
|
+
const logFd = (0, import_node_fs5.openSync)(logFile, "a");
|
|
34652
|
+
(0, import_node_fs5.writeFileSync)(logFd, `
|
|
34334
34653
|
=== [replicas service] "${name}" started ${(/* @__PURE__ */ new Date()).toISOString()} in ${cwd}: ${command} ===
|
|
34335
34654
|
`);
|
|
34336
|
-
const child = (0,
|
|
34655
|
+
const child = (0, import_node_child_process3.spawn)("bash", ["-lc", command], {
|
|
34337
34656
|
cwd,
|
|
34338
34657
|
detached: true,
|
|
34339
34658
|
stdio: ["ignore", logFd, logFd],
|
|
34340
34659
|
env: process.env
|
|
34341
34660
|
});
|
|
34342
|
-
(0,
|
|
34661
|
+
(0, import_node_fs5.closeSync)(logFd);
|
|
34343
34662
|
if (child.pid === void 0) {
|
|
34344
34663
|
throw new Error("Failed to spawn service process");
|
|
34345
34664
|
}
|
|
@@ -34352,11 +34671,11 @@ async function serviceStartCommand(name, commandParts, options) {
|
|
|
34352
34671
|
logFile,
|
|
34353
34672
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
34354
34673
|
};
|
|
34355
|
-
(0,
|
|
34674
|
+
(0, import_node_fs5.writeFileSync)(statePath(name), JSON.stringify(state, null, 2));
|
|
34356
34675
|
await sleep(1e3);
|
|
34357
34676
|
if (!isRunning(child.pid)) {
|
|
34358
34677
|
const logs = tailLog(logFile, 20);
|
|
34359
|
-
(0,
|
|
34678
|
+
(0, import_node_fs5.rmSync)(statePath(name), { force: true });
|
|
34360
34679
|
throw new Error(`Service "${name}" exited immediately.${logs ? `
|
|
34361
34680
|
|
|
34362
34681
|
Last log output:
|
|
@@ -34374,7 +34693,7 @@ async function serviceStopCommand(name) {
|
|
|
34374
34693
|
}
|
|
34375
34694
|
const wasRunning = isRunning(state.pid);
|
|
34376
34695
|
if (wasRunning) await killServiceGroup(state.pid);
|
|
34377
|
-
(0,
|
|
34696
|
+
(0, import_node_fs5.rmSync)(statePath(name), { force: true });
|
|
34378
34697
|
output({ name, pid: state.pid, was_running: wasRunning }, () => {
|
|
34379
34698
|
console.log(wasRunning ? `Stopped "${name}" (pid ${state.pid})` : `Service "${name}" was not running`);
|
|
34380
34699
|
});
|
|
@@ -34382,7 +34701,7 @@ async function serviceStopCommand(name) {
|
|
|
34382
34701
|
async function serviceListCommand() {
|
|
34383
34702
|
let entries;
|
|
34384
34703
|
try {
|
|
34385
|
-
entries = (0,
|
|
34704
|
+
entries = (0, import_node_fs5.readdirSync)(SERVICES_DIR).filter((f) => f.endsWith(".json"));
|
|
34386
34705
|
} catch {
|
|
34387
34706
|
entries = [];
|
|
34388
34707
|
}
|
|
@@ -34393,7 +34712,7 @@ async function serviceListCommand() {
|
|
|
34393
34712
|
return;
|
|
34394
34713
|
}
|
|
34395
34714
|
for (const service of services) {
|
|
34396
|
-
const status = service.running ?
|
|
34715
|
+
const status = service.running ? import_chalk31.default.green("running") : import_chalk31.default.red("stopped");
|
|
34397
34716
|
console.log(`${service.name} ${status} pid ${service.pid} started ${service.startedAt}`);
|
|
34398
34717
|
console.log(` command: ${service.command} (cwd: ${service.cwd})`);
|
|
34399
34718
|
console.log(` logs: ${service.logFile}`);
|
|
@@ -34672,6 +34991,16 @@ if (isAgentMode()) {
|
|
|
34672
34991
|
media.command("share <mediaId>").description("Rotate an image's public forge URL, or create one for video or audio media").action(run(mediaShareCommand));
|
|
34673
34992
|
media.command("revoke <mediaId>").description("Revoke the public forge URL for uploaded media").action(run(mediaRevokeCommand));
|
|
34674
34993
|
media.command("delete <mediaId>").description("Permanently delete media uploaded by this workspace").action(run(mediaDeleteCommand));
|
|
34994
|
+
const gpu = program.command("gpu").description("Run commands on cloud GPU sandboxes (billed at cost to your organization)");
|
|
34995
|
+
const addCreateOptions = (command) => command.option("-g, --gpu <type>", `GPU type with an optional count: ${GPU_TYPES.join(", ")}, e.g. H100:2 (default ${DEFAULT_GPU_TYPE})`).option("-i, --image <image>", `Public registry image (default ${DEFAULT_GPU_IMAGE})`).option("-t, --timeout <duration>", "Maximum lifetime, e.g. 90m or 4h (default 1h, max 24h)").option("--idle-timeout <duration>", "Stop after this long with no running command (default 10m, max 1h)").option("-p, --port <port>", "Expose a container port as a public HTTPS URL (repeatable)", collect, []).option("-e, --env <KEY=value>", "Environment variable for the sandbox (repeatable)", collect, []);
|
|
34996
|
+
addCreateOptions(gpu.command("create")).description("Start a GPU sandbox and wait until it is running").option("--no-wait", "Return while the sandbox is still starting").action(run(gpuCreateCommand));
|
|
34997
|
+
gpu.command("list").description("List the running GPU sandboxes of this workspace").action(run(gpuListCommand));
|
|
34998
|
+
gpu.command("exec [command...]").description("Run a shell command in a GPU sandbox and stream its output: replicas gpu exec -- python train.py").option("-s, --sandbox <id>", "Sandbox ID (defaults to the only running sandbox)").option("-w, --workdir <dir>", `Working directory (default ${COMPUTE_SANDBOX_WORKDIR})`).option("-e, --env <KEY=value>", "Environment variable for the command (repeatable)", collect, []).option("-d, --detach", 'Run in the background and print a job ID for "replicas gpu logs"').action(run(gpuExecCommand));
|
|
34999
|
+
gpu.command("logs <jobId>").description("Print the output of a detached job").option("-s, --sandbox <id>", "Sandbox ID (defaults to the only running sandbox)").option("-f, --follow", "Stream until the job exits and exit with its code").action(run(gpuLogsCommand));
|
|
35000
|
+
gpu.command("push [local] [remote]").description(`Copy a local file or directory into a GPU sandbox (default . to ${COMPUTE_SANDBOX_WORKDIR}); git repositories honor .gitignore`).option("-s, --sandbox <id>", "Sandbox ID (defaults to the only running sandbox)").action(run(gpuPushCommand));
|
|
35001
|
+
gpu.command("pull <remote> [local]").description("Copy a file or directory out of a GPU sandbox into a local directory (default .)").option("-s, --sandbox <id>", "Sandbox ID (defaults to the only running sandbox)").action(run(gpuPullCommand));
|
|
35002
|
+
addCreateOptions(gpu.command("run [command...]")).description("Start a GPU sandbox, copy the current directory in, run a command, then stop it: replicas gpu run --gpu H100 -- python train.py").option("--pull <path>", `Copy a path (relative to ${COMPUTE_SANDBOX_WORKDIR}) back into the current directory afterwards (repeatable)`, collect, []).option("--keep", "Keep the sandbox running afterwards").action(run(gpuRunCommand));
|
|
35003
|
+
gpu.command("delete [id]").description("Stop a GPU sandbox").option("-s, --sandbox <id>", "Sandbox ID (defaults to the only running sandbox)").action(run(gpuDeleteCommand));
|
|
34675
35004
|
program.command("history").description("Read past session history").command("transcript <name>").description("Download the full transcript of a session in INDEX.md and print its path").action(run(historyTranscriptCommand));
|
|
34676
35005
|
skills.command("suggest <file>").description("Suggest saving a SKILL.md as an organization skill; the user reviews it before it is saved").requiredOption("--title <text>", 'Short title shown to the user, e.g. "Always use Conventional Commits"').action(run(skillsSuggestCommand));
|
|
34677
35006
|
program.command("computer").description("Drive the workspace Linux desktop through replicas-computer");
|
|
@@ -34684,6 +35013,7 @@ if (isAgentMode()) {
|
|
|
34684
35013
|
"preview",
|
|
34685
35014
|
"service",
|
|
34686
35015
|
"media",
|
|
35016
|
+
"gpu",
|
|
34687
35017
|
"history",
|
|
34688
35018
|
"skills",
|
|
34689
35019
|
"slack",
|
|
@@ -34721,9 +35051,9 @@ if (isAgentMode()) {
|
|
|
34721
35051
|
}
|
|
34722
35052
|
async function main() {
|
|
34723
35053
|
if (process.argv[2] === "computer" && isAgentMode()) {
|
|
34724
|
-
const result = (0,
|
|
35054
|
+
const result = (0, import_node_child_process4.spawnSync)("replicas-computer", process.argv.slice(3), { stdio: "inherit" });
|
|
34725
35055
|
if (result.error) {
|
|
34726
|
-
console.error(
|
|
35056
|
+
console.error(import_chalk32.default.red(`
|
|
34727
35057
|
\u2717 replicas-computer is unavailable in this workspace image
|
|
34728
35058
|
`));
|
|
34729
35059
|
process.exitCode = 1;
|