replicas-cli 0.2.365 → 0.2.367

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +117 -27
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -7809,12 +7809,11 @@ var PLANS = {
7809
7809
  monthlyPrice: 0,
7810
7810
  seatPriceCents: 0,
7811
7811
  features: [
7812
- "2,400 minutes of workspace usage per month",
7813
- "Use them however you want, manual or automated",
7814
- "Automations (limited to 2)",
7815
- "Warm pools and warm hooks",
7812
+ "2,400 manual/automated workspace minutes per month",
7816
7813
  "Up to 3 repositories",
7817
- "Up to 5 environments"
7814
+ "Up to 5 environments",
7815
+ "Up to 2 automations",
7816
+ "Warm pools and warm hooks"
7818
7817
  ]
7819
7818
  },
7820
7819
  developer: {
@@ -7823,8 +7822,8 @@ var PLANS = {
7823
7822
  monthlyPrice: 120,
7824
7823
  seatPriceCents: 12e3,
7825
7824
  features: [
7826
- "Unlimited manual workspaces",
7827
- "5,000 included automation/API minutes per month",
7825
+ "Unlimited manual workspace minutes",
7826
+ "5,000 automated workspace minutes per month",
7828
7827
  "Up to 10 repositories",
7829
7828
  "Up to 15 environments",
7830
7829
  "Up to 5 automations",
@@ -7838,12 +7837,12 @@ var PLANS = {
7838
7837
  monthlyPrice: 300,
7839
7838
  seatPriceCents: 3e4,
7840
7839
  features: [
7841
- "Unlimited manual workspaces",
7842
- "15,000 included automation/API minutes per month",
7840
+ "Unlimited manual workspace minutes",
7841
+ "15,000 automated workspace minutes per month",
7843
7842
  "Unlimited repositories",
7844
7843
  "Unlimited automations",
7845
- "Higher API rate limits",
7846
7844
  "Warm pools and warm hooks",
7845
+ "Higher API rate limits",
7847
7846
  "Auto-upgraded sandbox resources (4 vCPU, 32 GB disk, 16 GB memory)",
7848
7847
  "Shared Slack support channel"
7849
7848
  ]
@@ -7854,11 +7853,12 @@ var PLANS = {
7854
7853
  monthlyPrice: 0,
7855
7854
  seatPriceCents: 0,
7856
7855
  features: [
7857
- "Unlimited usage",
7856
+ "Unlimited manual workspace minutes",
7857
+ "Unlimited automated workspace minutes",
7858
7858
  "Unlimited automations",
7859
+ "Custom warm hooks and pools",
7859
7860
  "Custom API rates",
7860
7861
  "Custom rate limits",
7861
- "Custom warm hooks and pools",
7862
7862
  "Shared Slack support channel",
7863
7863
  "SOC 2"
7864
7864
  ]
@@ -8357,7 +8357,9 @@ gh pr review 123 --request-changes --body "Changes needed"
8357
8357
  GitHub does NOT have a public API for uploading images to PRs/issues. When you need to include images:
8358
8358
  - Do NOT use placeholder image URLs
8359
8359
  - Do NOT commit screenshots as files to the repository
8360
- - Use the \`replicas-agent\` skill to share the image
8360
+ - Run \`replicas media upload <image> --share\` and put the printed \`Forge embed\` Markdown in the PR body or comment
8361
+ - For video, embed a shared poster image linked to the recording's **View in Replicas** URL; GitHub does not reliably render externally hosted video inline
8362
+ - Treat the public URL as an opt-in bearer capability and revoke it with \`replicas media revoke <media-id>\` when appropriate
8361
8363
  - If you were triggered from Slack, also upload the image to the Slack thread so the user can see it directly
8362
8364
  `;
8363
8365
  var GITHUB_ABILITY = {
@@ -8417,6 +8419,23 @@ curl -s -H "Authorization: Bearer $TOKEN" "https://gitlab.com/api/v4/projects/<u
8417
8419
  \`\`\`
8418
8420
 
8419
8421
  Use the repo's own host in the API base URL for self-managed instances.
8422
+
8423
+ ## Inline media in merge requests
8424
+
8425
+ GitLab provides a supported project Markdown uploads API. Upload media to Replicas first, then upload the same bytes natively so repository permissions protect the asset:
8426
+
8427
+ \`\`\`bash
8428
+ FILE=/abs/path/to/screenshot.png
8429
+ PROJECT=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("group/project", safe=""))')
8430
+ TOKEN=$(grep -m1 '://oauth2:' ~/.git-credentials | sed -E 's#https://oauth2:([^@]+)@.*#\\1#')
8431
+ UPLOAD=$(curl -sS --request POST \\
8432
+ --header "Authorization: Bearer $TOKEN" \\
8433
+ --form "file=@$FILE" \\
8434
+ "https://gitlab.com/api/v4/projects/$PROJECT/uploads")
8435
+ echo "$UPLOAD" | jq -r .markdown
8436
+ \`\`\`
8437
+
8438
+ Use the repository's own host for self-managed GitLab. Insert the returned \`markdown\` into the merge request description or comment and add the media's **View in Replicas** dashboard link. Images render inline; MP4, MOV, and WebM render as inline video players. Do not create a public Replicas forge share when this native upload succeeds.
8420
8439
  `;
8421
8440
  var GITLAB_ABILITY = {
8422
8441
  label: "GitLab",
@@ -8930,11 +8949,20 @@ For each file, the CLI prints two lines:
8930
8949
 
8931
8950
  Match each "View in Replicas" line to the embed line directly above it \u2014 that's the deep link for that file.
8932
8951
 
8952
+ Pass \`--share\` only when the media must render on an external forge:
8953
+
8954
+ \`\`\`bash
8955
+ replicas media upload screenshot.png --share
8956
+ \`\`\`
8957
+
8958
+ This prints an additional \`Forge embed\` Markdown line backed by an opt-in public bearer URL. Anyone with that unguessable URL can view the asset until it is rotated or revoked. Use \`replicas media share <media-id>\` to rotate it and \`replicas media revoke <media-id>\` to revoke it.
8959
+
8933
8960
  ## The two URLs the CLI gives you \u2014 and where each one is allowed
8934
8961
 
8935
8962
  | URL | What it is | Where it's allowed |
8936
8963
  |---|---|---|
8937
8964
  | \`https://api.tryreplicas.com/v1/media/<id>\` (inside \`![\u2026](\u2026)\`) | Authenticated API redirect to a presigned blob \u2014 requires the Replicas chat session | **Only** in the Replicas chat reply. Nowhere else. |
8965
+ | \`https://api.tryreplicas.com/v1/media/public/<id>/<token>\` | Opt-in public bearer URL printed by \`--share\` | GitHub image embeds only. |
8938
8966
  | \`https://tryreplicas.com/workspaces/<workspace-id>?mode=media&media=<media-id>\` | Public dashboard deep link to the media tab, scrolled to that specific file | Everywhere external \u2014 Slack, GitHub, Linear, PR bodies, comments, emails. |
8939
8967
 
8940
8968
  ### NEVER link the API redirect URL outside the Replicas chat reply
@@ -8979,7 +9007,7 @@ Sharing media externally is a **two-step requirement** for every file:
8979
9007
 
8980
9008
  A link alone is not enough. A native upload alone is not enough. Do both.
8981
9009
 
8982
- The one exception is GitHub, which has no public image upload API \u2014 there, the dashboard link is the only option (see below).
9010
+ GitHub has no native upload API, so use an explicit Replicas public share for screenshots and recording previews. GitLab has a native upload API and should receive the original bytes.
8983
9011
 
8984
9012
  ### Slack \u2014 embed natively via the 3-step external file upload flow
8985
9013
 
@@ -9091,20 +9119,34 @@ The same flow works for issue bodies (use \`issueCreate\` / \`issueUpdate\` with
9091
9119
 
9092
9120
  Never paste the \`api.tryreplicas.com/v1/media/<id>\` URL into a Linear body or comment \u2014 it will render as a broken image to anyone outside your Replicas chat.
9093
9121
 
9094
- ### GitHub \u2014 link the dashboard deep link (no public upload API exists)
9122
+ ### GitHub: inline screenshots and recording previews
9123
+
9124
+ GitHub has **no public API** for uploading attachments to PRs or issues. Its web UI uses an undocumented internal endpoint that API clients must not emulate. For screenshots, explicitly create a forge share and put the printed \`Forge embed\` Markdown directly in the PR body or comment, followed by the dashboard link:
9095
9125
 
9096
- GitHub has **no public API** for uploading images to PRs or issues. Their web UI uses an undocumented internal endpoint that's not available to API clients. So for GitHub specifically, the only correct way to share media is the dashboard deep link, rendered as a markdown hyperlink:
9126
+ \`\`\`bash
9127
+ replicas media upload /tmp/screenshot.png --share
9128
+ \`\`\`
9129
+
9130
+ GitHub does not reliably render arbitrary external videos as inline players. For a recording:
9131
+
9132
+ 1. Upload the MP4 normally so it remains available in Replicas.
9133
+ 2. Generate a poster image from the recording: \`ffmpeg -i /tmp/demo.mp4 -frames:v 1 /tmp/demo-poster.png\`.
9134
+ 3. Upload the poster with \`--share\`.
9135
+ 4. Wrap the poster embed in a link to the recording's **View in Replicas** URL.
9097
9136
 
9098
9137
  \`\`\`markdown
9099
- [View in Replicas](https://tryreplicas.com/workspaces/<workspace-id>?mode=media&media=<media-id>)
9138
+ [![Demo recording](<public-poster-url>)](<recording-dashboard-url>)
9100
9139
  \`\`\`
9101
9140
 
9102
- Do **not**:
9103
- - Commit screenshots as files to the repo as a workaround.
9104
- - Use placeholder image URLs.
9105
- - Embed the \`api.tryreplicas.com/v1/media/<id>\` URL in markdown \u2014 it renders as a broken image for every viewer.
9141
+ Public forge shares are bearer capabilities. For private repositories, create one only when the user asked for inline PR media, and revoke it with \`replicas media revoke <media-id>\` when it should stop resolving.
9142
+
9143
+ Do **not** commit screenshots to the repo, use placeholder URLs, expose the authenticated \`/v1/media/<id>\` URL, or automate GitHub's undocumented browser upload flow.
9144
+
9145
+ ### GitLab: native inline screenshots and videos
9106
9146
 
9107
- This applies to PR descriptions, PR review comments, issue bodies, issue comments, and commit messages.
9147
+ GitLab supports native project Markdown uploads. Always upload the media to Replicas first, then POST the same original file to the repository's \`/projects/:id/uploads\` API using the workspace GitLab credential. Insert the returned \`markdown\` value into the merge request body or comment and include the Replicas dashboard link. Image uploads render inline; MP4, MOV, and WebM uploads render as inline video players.
9148
+
9149
+ Do not use a Replicas public forge share for GitLab when the native project upload succeeds; native uploads preserve project access controls.
9108
9150
 
9109
9151
  ## Recording defaults
9110
9152
 
@@ -9144,6 +9186,7 @@ replicas media upload chart-a.svg chart-b.svg --kind image
9144
9186
 
9145
9187
  - \`--kind <image|video|audio>\` \u2014 override auto-detection
9146
9188
  - \`--session-id <id>\` \u2014 associate the upload with a specific session
9189
+ - \`--share\`: create a revocable public bearer URL for a forge image embed
9147
9190
  `;
9148
9191
  var MEDIA_ABILITY = {
9149
9192
  label: "Media",
@@ -9693,7 +9736,7 @@ var HOOK_EXEC_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
9693
9736
  var REPLICAS_CONFIG_FILENAMES = ["replicas.json", "replicas.yaml", "replicas.yml"];
9694
9737
 
9695
9738
  // ../shared/src/cli-version.ts
9696
- var CLI_VERSION = "0.2.365";
9739
+ var CLI_VERSION = "0.2.367";
9697
9740
 
9698
9741
  // ../shared/src/version.ts
9699
9742
  function compareVersions(v1, v2) {
@@ -14681,6 +14724,15 @@ async function previewRemoveCommand(workspaceId, options) {
14681
14724
  // src/commands/media.ts
14682
14725
  import fs4 from "fs";
14683
14726
  import path5 from "path";
14727
+ function publicMediaUrl(mediaId, token) {
14728
+ return `${MONOLITH_URL4}/v1/media/public/${mediaId}/${token}`;
14729
+ }
14730
+ async function shareMedia(mediaId) {
14731
+ const { token } = await agentFetch(`/v1/engine/media/${mediaId}/share`, {
14732
+ method: "POST"
14733
+ });
14734
+ return publicMediaUrl(mediaId, token);
14735
+ }
14684
14736
  var MONOLITH_URL4 = process.env.MONOLITH_URL || process.env.REPLICAS_MONOLITH_URL || "https://api.tryreplicas.com";
14685
14737
  function mediaInfo(ext) {
14686
14738
  const { kind, mimeType } = classifyCanvasFilename(`.${ext}`);
@@ -14741,6 +14793,17 @@ async function mediaUploadCommand(filePaths, options) {
14741
14793
  if (result.status === "fulfilled") {
14742
14794
  const { fileName, media } = result.value;
14743
14795
  console.log(`![${fileName}](${MONOLITH_URL4}/v1/media/${media.id})`);
14796
+ if (options.share) {
14797
+ try {
14798
+ console.log(`Forge embed: ![${fileName}](${await shareMedia(media.id)})`);
14799
+ } catch (error) {
14800
+ failures.push({
14801
+ filePath: filePaths[i],
14802
+ operation: "share",
14803
+ error: error instanceof Error ? error : new Error(String(error))
14804
+ });
14805
+ }
14806
+ }
14744
14807
  if (config2?.workspace_id) {
14745
14808
  const deepLink = getWorkspaceDashboardUrl(config2.workspace_id, {
14746
14809
  mode: "media",
@@ -14750,14 +14813,21 @@ async function mediaUploadCommand(filePaths, options) {
14750
14813
  }
14751
14814
  } else {
14752
14815
  const error = result.reason instanceof Error ? result.reason : new Error(String(result.reason));
14753
- failures.push({ filePath: filePaths[i], error });
14816
+ failures.push({ filePath: filePaths[i], operation: "upload", error });
14754
14817
  }
14755
14818
  }
14756
14819
  if (failures.length > 0) {
14757
- const detail = failures.map((f) => `${f.filePath}: ${f.error.message}`).join("; ");
14758
- throw new Error(`${failures.length} of ${filePaths.length} upload(s) failed \u2014 ${detail}`);
14820
+ const detail = failures.map((f) => `${f.filePath} ${f.operation}: ${f.error.message}`).join("; ");
14821
+ throw new Error(`${failures.length} media operation(s) failed \u2014 ${detail}`);
14759
14822
  }
14760
14823
  }
14824
+ async function mediaShareCommand(mediaId) {
14825
+ console.log(await shareMedia(mediaId));
14826
+ }
14827
+ async function mediaRevokeCommand(mediaId) {
14828
+ await agentFetch(`/v1/engine/media/${mediaId}/share`, { method: "DELETE" });
14829
+ console.log(`Revoked public share for ${mediaId}`);
14830
+ }
14761
14831
  async function mediaListCommand(options) {
14762
14832
  const qs = new URLSearchParams();
14763
14833
  if (options.kind) qs.set("kind", options.kind);
@@ -22085,7 +22155,7 @@ if (isAgentMode()) {
22085
22155
  }
22086
22156
  if (isAgentMode()) {
22087
22157
  const media = program.command("media").description("Share workspace media (screenshots, videos, audio) inline in chat");
22088
- media.command("upload <files...>").description("Upload one or more screenshot, video, or audio files. Multiple files are uploaded concurrently. Prints a markdown embed per file for the assistant reply.").option("-k, --kind <kind>", "Media kind: image, video, or audio (inferred from extension if omitted)").option("-s, --session-id <id>", "Session ID to associate the asset with").action(async (files, options) => {
22158
+ media.command("upload <files...>").description("Upload one or more screenshot, video, or audio files. Multiple files are uploaded concurrently. Prints a markdown embed per file for the assistant reply.").option("-k, --kind <kind>", "Media kind: image, video, or audio (inferred from extension if omitted)").option("-s, --session-id <id>", "Session ID to associate the asset with").option("--share", "Create an opt-in public forge embed URL for each asset").action(async (files, options) => {
22089
22159
  try {
22090
22160
  await mediaUploadCommand(files, options);
22091
22161
  } catch (error) {
@@ -22097,6 +22167,26 @@ if (isAgentMode()) {
22097
22167
  process.exit(1);
22098
22168
  }
22099
22169
  });
22170
+ media.command("share <mediaId>").description("Create or rotate a public forge embed URL for uploaded media").action(async (mediaId) => {
22171
+ try {
22172
+ await mediaShareCommand(mediaId);
22173
+ } catch (error) {
22174
+ if (error instanceof Error) console.error(chalk24.red(`
22175
+ \u2717 ${error.message}
22176
+ `));
22177
+ process.exit(1);
22178
+ }
22179
+ });
22180
+ media.command("revoke <mediaId>").description("Revoke the public forge embed URL for uploaded media").action(async (mediaId) => {
22181
+ try {
22182
+ await mediaRevokeCommand(mediaId);
22183
+ } catch (error) {
22184
+ if (error instanceof Error) console.error(chalk24.red(`
22185
+ \u2717 ${error.message}
22186
+ `));
22187
+ process.exit(1);
22188
+ }
22189
+ });
22100
22190
  media.command("list").description("List media uploaded for this workspace").option("-k, --kind <kind>", "Filter by kind: image, video, or audio").option("-l, --limit <n>", "Page size (default 50, max 200)").option("-p, --page <n>", "Page number (default 1)").action(async (options) => {
22101
22191
  try {
22102
22192
  await mediaListCommand(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-cli",
3
- "version": "0.2.365",
3
+ "version": "0.2.367",
4
4
  "description": "CLI for managing Replicas workspaces - SSH into cloud dev environments with automatic port forwarding",
5
5
  "main": "dist/index.mjs",
6
6
  "bin": {