replicas-cli 0.2.366 → 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.
- package/dist/index.mjs +105 -15
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -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
|
-
-
|
|
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 \`\`) | 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
|
-
|
|
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
|
|
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:
|
|
9125
|
+
|
|
9126
|
+
\`\`\`bash
|
|
9127
|
+
replicas media upload /tmp/screenshot.png --share
|
|
9128
|
+
\`\`\`
|
|
9095
9129
|
|
|
9096
|
-
GitHub
|
|
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
|
-
[
|
|
9138
|
+
[](<recording-dashboard-url>)
|
|
9100
9139
|
\`\`\`
|
|
9101
9140
|
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
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
|
-
|
|
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.
|
|
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(``);
|
|
14796
|
+
if (options.share) {
|
|
14797
|
+
try {
|
|
14798
|
+
console.log(`Forge embed: })`);
|
|
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}
|
|
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);
|