replicas-cli 0.2.417 → 0.2.419
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 +70 -29
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -23024,37 +23024,86 @@ This guide covers how to work with GitLab-hosted repositories from within your R
|
|
|
23024
23024
|
Git credentials for the workspace's GitLab hosts are pre-configured in \`~/.git-credentials\` and refreshed automatically. Plain \`git fetch\` / \`git pull\` / \`git push\` over HTTPS work with no additional setup.
|
|
23025
23025
|
|
|
23026
23026
|
- Never ask the user for a GitLab token or PAT \u2014 credentials are already wired. If a push fails with an authentication error, report it to the user instead of working around it.
|
|
23027
|
+
- A workspace can hold credentials for several GitLab hosts, and \`origin\` is whatever the repo says it is. Always select the token by the host you are about to call, as the snippets below do. Grabbing the first entry in \`~/.git-credentials\` can send one host's token to another.
|
|
23027
23028
|
- There is no \`glab\` CLI in the workspace, and \`gh\` only works for GitHub remotes. Check a repo's host with \`git remote get-url origin\` before choosing the GitHub or GitLab workflow.
|
|
23028
23029
|
|
|
23030
|
+
### Reading the credential for a repo
|
|
23031
|
+
|
|
23032
|
+
A workspace can hold tokens for several GitLab hosts, so always ask git for the
|
|
23033
|
+
credential belonging to the host you are about to call. Never grep
|
|
23034
|
+
\`~/.git-credentials\` yourself: taking the first line sends one instance's token to
|
|
23035
|
+
another, and interpolating a host into a grep pattern makes it a regular
|
|
23036
|
+
expression. \`git credential fill\` matches the host exactly and returns nothing
|
|
23037
|
+
when there is no credential for it:
|
|
23038
|
+
|
|
23039
|
+
\`\`\`bash
|
|
23040
|
+
HOST=$(git remote get-url origin | sed -E 's#^[a-z]+://##; s#^[^@/]*@##; s#/.*##; s#:[^0-9].*##')
|
|
23041
|
+
TOKEN=$(printf 'protocol=https\\nhost=%s\\n\\n' "$HOST" | GIT_TERMINAL_PROMPT=0 git credential fill | sed -n 's/^password=//p')
|
|
23042
|
+
[ -n "$TOKEN" ] || { echo "no credential for $HOST"; exit 1; }
|
|
23043
|
+
\`\`\`
|
|
23044
|
+
|
|
23045
|
+
Every recipe below repeats these two lines so each block runs on its own, and
|
|
23046
|
+
every one calls \`https://$HOST\`, so the token only ever reaches the host it
|
|
23047
|
+
belongs to. Run them inside the repo whose API you are calling. An empty
|
|
23048
|
+
\`$TOKEN\` means the workspace has no credential for that host. Report that rather
|
|
23049
|
+
than reaching for another one.
|
|
23050
|
+
|
|
23029
23051
|
## Merge Requests
|
|
23030
23052
|
|
|
23031
23053
|
Create a merge request directly from a push using push options:
|
|
23032
23054
|
|
|
23033
23055
|
\`\`\`bash
|
|
23034
|
-
git push -o merge_request.create -o merge_request.target=<default-branch> origin HEAD
|
|
23056
|
+
git push -o merge_request.create -o merge_request.target=<default-branch> -o merge_request.title="Title" origin HEAD
|
|
23035
23057
|
\`\`\`
|
|
23036
23058
|
|
|
23037
23059
|
Useful options:
|
|
23038
23060
|
|
|
23039
23061
|
\`\`\`bash
|
|
23040
|
-
-o merge_request.title="Title"
|
|
23041
|
-
-o merge_request.description="Description"
|
|
23042
23062
|
-o merge_request.draft # open as draft
|
|
23043
23063
|
-o merge_request.remove_source_branch # delete branch on merge
|
|
23044
23064
|
\`\`\`
|
|
23045
23065
|
|
|
23046
23066
|
GitLab prints the MR URL in the push output \u2014 include it in your reply to the user.
|
|
23047
23067
|
|
|
23068
|
+
### Setting the description
|
|
23069
|
+
|
|
23070
|
+
Do **not** pass the description as a push option. Push option values are shell
|
|
23071
|
+
arguments on one line, so a real description \u2014 headings, lists, the Replicas
|
|
23072
|
+
footer HTML \u2014 gets mangled or truncated. Write the body to a file and PUT it as
|
|
23073
|
+
JSON instead, which preserves it exactly:
|
|
23074
|
+
|
|
23075
|
+
\`\`\`bash
|
|
23076
|
+
HOST=$(git remote get-url origin | sed -E 's#^[a-z]+://##; s#^[^@/]*@##; s#/.*##; s#:[^0-9].*##')
|
|
23077
|
+
TOKEN=$(printf 'protocol=https\\nhost=%s\\n\\n' "$HOST" | GIT_TERMINAL_PROMPT=0 git credential fill | sed -n 's/^password=//p')
|
|
23078
|
+
PROJECT=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("group/project", safe=""))')
|
|
23079
|
+
cat > /tmp/mr-body.md <<'EOF'
|
|
23080
|
+
## Summary
|
|
23081
|
+
...your full description, ending with the Replicas footer...
|
|
23082
|
+
EOF
|
|
23083
|
+
jq -Rs '{description: .}' /tmp/mr-body.md > /tmp/mr-body.json
|
|
23084
|
+
curl -sS --request PUT \\
|
|
23085
|
+
--header "Authorization: Bearer $TOKEN" \\
|
|
23086
|
+
--header "Content-Type: application/json" \\
|
|
23087
|
+
--data @/tmp/mr-body.json \\
|
|
23088
|
+
"https://$HOST/api/v4/projects/$PROJECT/merge_requests/<iid>"
|
|
23089
|
+
\`\`\`
|
|
23090
|
+
|
|
23091
|
+
Use the same command to edit a description later. Editing replaces the whole
|
|
23092
|
+
body, so re-include the Replicas footer every time \u2014 it is how merge requests
|
|
23093
|
+
opened through Replicas are counted.
|
|
23094
|
+
|
|
23048
23095
|
## GitLab API (advanced)
|
|
23049
23096
|
|
|
23050
23097
|
For operations with no git equivalent (commenting on MRs, reading pipelines), call the REST API with the workspace credential:
|
|
23051
23098
|
|
|
23052
23099
|
\`\`\`bash
|
|
23053
|
-
|
|
23054
|
-
|
|
23100
|
+
HOST=$(git remote get-url origin | sed -E 's#^[a-z]+://##; s#^[^@/]*@##; s#/.*##; s#:[^0-9].*##')
|
|
23101
|
+
TOKEN=$(printf 'protocol=https\\nhost=%s\\n\\n' "$HOST" | GIT_TERMINAL_PROMPT=0 git credential fill | sed -n 's/^password=//p')
|
|
23102
|
+
curl -s -H "Authorization: Bearer $TOKEN" "https://$HOST/api/v4/projects/<url-encoded-path>/merge_requests"
|
|
23055
23103
|
\`\`\`
|
|
23056
23104
|
|
|
23057
|
-
|
|
23105
|
+
This works for gitlab.com and self-managed instances alike, because the base URL
|
|
23106
|
+
and the token both come from the repo's own host.
|
|
23058
23107
|
|
|
23059
23108
|
## Inline media in merge requests
|
|
23060
23109
|
|
|
@@ -23062,16 +23111,17 @@ GitLab provides a supported project Markdown uploads API. Upload media to Replic
|
|
|
23062
23111
|
|
|
23063
23112
|
\`\`\`bash
|
|
23064
23113
|
FILE=/abs/path/to/screenshot.png
|
|
23114
|
+
HOST=$(git remote get-url origin | sed -E 's#^[a-z]+://##; s#^[^@/]*@##; s#/.*##; s#:[^0-9].*##')
|
|
23065
23115
|
PROJECT=$(python3 -c 'import urllib.parse; print(urllib.parse.quote("group/project", safe=""))')
|
|
23066
|
-
TOKEN=$(
|
|
23116
|
+
TOKEN=$(printf 'protocol=https\\nhost=%s\\n\\n' "$HOST" | GIT_TERMINAL_PROMPT=0 git credential fill | sed -n 's/^password=//p')
|
|
23067
23117
|
UPLOAD=$(curl -sS --request POST \\
|
|
23068
23118
|
--header "Authorization: Bearer $TOKEN" \\
|
|
23069
23119
|
--form "file=@$FILE" \\
|
|
23070
|
-
"https
|
|
23120
|
+
"https://$HOST/api/v4/projects/$PROJECT/uploads")
|
|
23071
23121
|
echo "$UPLOAD" | jq -r .markdown
|
|
23072
23122
|
\`\`\`
|
|
23073
23123
|
|
|
23074
|
-
|
|
23124
|
+
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.
|
|
23075
23125
|
`;
|
|
23076
23126
|
var GITLAB_ABILITY = {
|
|
23077
23127
|
label: "GitLab",
|
|
@@ -24428,7 +24478,7 @@ function formatTurnElapsed(ms) {
|
|
|
24428
24478
|
}
|
|
24429
24479
|
|
|
24430
24480
|
// ../shared/src/cli-version.ts
|
|
24431
|
-
var CLI_VERSION = "0.2.
|
|
24481
|
+
var CLI_VERSION = "0.2.419";
|
|
24432
24482
|
|
|
24433
24483
|
// ../shared/src/version.ts
|
|
24434
24484
|
function compareVersions(v1, v2) {
|
|
@@ -25573,14 +25623,21 @@ function parseMcpToolName(name) {
|
|
|
25573
25623
|
}
|
|
25574
25624
|
|
|
25575
25625
|
// ../shared/src/display-message/parsers/claude-parser.ts
|
|
25626
|
+
var ABORTED_TERMINAL_REASONS = /* @__PURE__ */ new Set(["aborted_streaming", "aborted_tools"]);
|
|
25576
25627
|
function coerceClaudeResultPayload(payload) {
|
|
25577
25628
|
return {
|
|
25578
25629
|
is_error: typeof payload.is_error === "boolean" ? payload.is_error : void 0,
|
|
25579
25630
|
subtype: typeof payload.subtype === "string" ? payload.subtype : void 0,
|
|
25580
|
-
errors: Array.isArray(payload.errors) ? payload.errors.filter((e) => typeof e === "string") : void 0
|
|
25631
|
+
errors: Array.isArray(payload.errors) ? payload.errors.filter((e) => typeof e === "string") : void 0,
|
|
25632
|
+
terminal_reason: typeof payload.terminal_reason === "string" ? payload.terminal_reason : void 0
|
|
25581
25633
|
};
|
|
25582
25634
|
}
|
|
25635
|
+
function stripAgentDiagnosticErrors(errors) {
|
|
25636
|
+
return errors.filter((error51) => !error51.startsWith("[ede_diagnostic]"));
|
|
25637
|
+
}
|
|
25583
25638
|
function isClaudeResultError(payload) {
|
|
25639
|
+
if (payload.terminal_reason && ABORTED_TERMINAL_REASONS.has(payload.terminal_reason)) return false;
|
|
25640
|
+
if (payload.errors?.length && stripAgentDiagnosticErrors(payload.errors).length === 0) return false;
|
|
25584
25641
|
return Boolean(payload.is_error) || payload.subtype !== "success";
|
|
25585
25642
|
}
|
|
25586
25643
|
function upsertDisplayMessage(messages, message) {
|
|
@@ -25615,7 +25672,6 @@ function parseClaudeEvents(events, parentToolUseId) {
|
|
|
25615
25672
|
return message?.type === "user" && areUserMessagesWithinMatchWindow(message, { content, timestamp });
|
|
25616
25673
|
});
|
|
25617
25674
|
};
|
|
25618
|
-
let turnWasInterrupted = false;
|
|
25619
25675
|
const taskAccumulator = new TaskAccumulator();
|
|
25620
25676
|
const taskSnapshot = () => taskAccumulator.getTasks().map((task) => ({
|
|
25621
25677
|
text: task.subject,
|
|
@@ -25694,7 +25750,6 @@ function parseClaudeEvents(events, parentToolUseId) {
|
|
|
25694
25750
|
if (LOCAL_COMMAND_ECHO_REGEX.test(textContent.trim())) {
|
|
25695
25751
|
return;
|
|
25696
25752
|
}
|
|
25697
|
-
turnWasInterrupted = INTERRUPTED_MESSAGE_REGEX.test(textContent.trim());
|
|
25698
25753
|
const images = content.filter((c) => c.type === "image" && c.source).map((c) => {
|
|
25699
25754
|
const source = c.source;
|
|
25700
25755
|
return {
|
|
@@ -25943,26 +25998,12 @@ function parseClaudeEvents(events, parentToolUseId) {
|
|
|
25943
25998
|
}
|
|
25944
25999
|
if (event.type === "claude-result") {
|
|
25945
26000
|
const payload = coerceClaudeResultPayload(event.payload);
|
|
25946
|
-
const errorList = payload.errors || [];
|
|
25947
|
-
if (turnWasInterrupted) {
|
|
25948
|
-
turnWasInterrupted = false;
|
|
25949
|
-
const genuineErrors = errorList.filter((e) => !e.includes("[ede_diagnostic]"));
|
|
25950
|
-
if (genuineErrors.length > 0) {
|
|
25951
|
-
messages.push({
|
|
25952
|
-
id: `error-${event.timestamp}`,
|
|
25953
|
-
type: "error",
|
|
25954
|
-
message: genuineErrors.join("\n"),
|
|
25955
|
-
timestamp: event.timestamp
|
|
25956
|
-
});
|
|
25957
|
-
}
|
|
25958
|
-
return;
|
|
25959
|
-
}
|
|
25960
26001
|
if (isClaudeResultError(payload)) {
|
|
25961
|
-
const
|
|
26002
|
+
const genuineErrors = stripAgentDiagnosticErrors(payload.errors ?? []);
|
|
25962
26003
|
messages.push({
|
|
25963
26004
|
id: `error-${event.timestamp}`,
|
|
25964
26005
|
type: "error",
|
|
25965
|
-
message:
|
|
26006
|
+
message: genuineErrors.length > 0 ? genuineErrors.join("\n") : "Claude session encountered an unexpected error.",
|
|
25966
26007
|
timestamp: event.timestamp
|
|
25967
26008
|
});
|
|
25968
26009
|
}
|