@tryarcanist/cli 0.1.99 → 0.1.101
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/README.md +10 -10
- package/dist/index.js +49 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ arcanist auth login
|
|
|
34
34
|
|
|
35
35
|
Token config is stored at `~/.arcanist/config.json`. Multiple active tokens per user are supported; revoke tokens independently when a device or workflow no longer needs access.
|
|
36
36
|
|
|
37
|
-
Use a read-scoped token for inspection and debugging
|
|
37
|
+
Use a read-scoped token for inspection and debugging; use a write-scoped token only to create sessions, send prompts, or stop runs.
|
|
38
38
|
|
|
39
39
|
You can also pipe the token via stdin:
|
|
40
40
|
|
|
@@ -55,7 +55,7 @@ export ARCANIST_TOKEN=arc_...
|
|
|
55
55
|
export ARCANIST_API_URL=https://app.tryarcanist.com
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
`--token` and `--api-url` are available for one-off invocations. Prefer `ARCANIST_TOKEN` over `--token` for persistent use
|
|
58
|
+
`--token` and `--api-url` are available for one-off invocations. Prefer `ARCANIST_TOKEN` over `--token` for persistent use; CLI flags can appear in shell history and process lists.
|
|
59
59
|
|
|
60
60
|
Non-local API URLs must use HTTPS and must not embed credentials.
|
|
61
61
|
|
|
@@ -128,17 +128,17 @@ arcanist sessions create your-org/your-repo "verify the deployed change" --cold
|
|
|
128
128
|
arcanist sessions create your-org/your-repo "retry-safe create" --idempotency-key 1f0e6f1a-...
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
|
|
131
|
+
`--backend` picks the agent runtime backend: `codex` (default) or `claude_code`. `--model` must be valid for the chosen backend — codex runs OpenAI models (`gpt-5.5` default, `gpt-5.4`), `claude_code` runs Anthropic models (`claude-opus-4-8` default, `claude-sonnet-4-6`) — and the CLI rejects a mismatch before any network call. `claude_code` sessions require an Anthropic API key configured in Settings.
|
|
132
132
|
|
|
133
|
-
|
|
133
|
+
`--wait` blocks until the created prompt finishes and exits non-zero if the prompt finishes with `status: failed`, making it suitable for cron or other schedulers that alert on command failure. `--poll-interval <ms>` tunes the completion check frequency.
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
Repeatable `--uploaded-file <path>` flags attach local UTF-8 text files to the prompt. Uploaded file names come from the local basename; directory components are not sent.
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
`--cold` forces a fresh sandbox and skips the warm pool — useful for verifying freshly deployed code when a pre-warmed sandbox may predate the deploy.
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
`--idempotency-key <uuid>` is for manually retrying a create request that may have reached the server. The CLI derives separate session and prompt idempotency keys from the provided value.
|
|
140
140
|
|
|
141
|
-
JSON mode returns `{sessionId, sessionUrl?, repoUrl, model?, agentRuntimeBackend?, reasoningEffort?, promptId?}`. `sessionUrl` is emitted only when the server returns it; `agentRuntimeBackend`
|
|
141
|
+
JSON mode returns `{sessionId, sessionUrl?, repoUrl, model?, agentRuntimeBackend?, reasoningEffort?, promptId?}`. `sessionUrl` is emitted only when the server returns it; `agentRuntimeBackend` only when `--backend` is passed.
|
|
142
142
|
|
|
143
143
|
### `arcanist sessions send <session-id> [prompt]`
|
|
144
144
|
|
|
@@ -155,7 +155,7 @@ JSON mode returns `{sessionId, promptId?}`.
|
|
|
155
155
|
|
|
156
156
|
### `arcanist sessions stop <session-id>`
|
|
157
157
|
|
|
158
|
-
Stops the active run for a session.
|
|
158
|
+
Stops the active run for a session. Idempotent: if no sandbox is active, the server returns `already_stopped`.
|
|
159
159
|
|
|
160
160
|
```bash
|
|
161
161
|
arcanist sessions stop abc123
|
|
@@ -190,7 +190,7 @@ arcanist sessions search "mcp debugging" --repo your-org/your-repo --json
|
|
|
190
190
|
arcanist sessions search "repo access" --status idle --scope business --limit 20 --cursor <cursor>
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
|
|
193
|
+
Uses the same metadata-only index and filters as `sessions list`: `--status`, `--scope`, `--repo`, `--limit`, and `--cursor`.
|
|
194
194
|
|
|
195
195
|
### `arcanist sessions events <session-id>`
|
|
196
196
|
|
package/dist/index.js
CHANGED
|
@@ -862,6 +862,22 @@ function noChangeOutcomeCopy(reason) {
|
|
|
862
862
|
}
|
|
863
863
|
}
|
|
864
864
|
|
|
865
|
+
// ../../shared/session/transient-disconnect.ts
|
|
866
|
+
var TRANSIENT_DISCONNECT_VISIBILITY_MS = 15e3;
|
|
867
|
+
function nextDisconnectMask(mask, previous, current, now) {
|
|
868
|
+
if (!current || current.sandboxSubstate !== "reconnecting") return null;
|
|
869
|
+
if (mask) return mask;
|
|
870
|
+
if (!previous || previous.sandboxSubstate === "reconnecting") return null;
|
|
871
|
+
return { since: now, heldPhase: previous.phase, heldSubstate: previous.sandboxSubstate ?? "none" };
|
|
872
|
+
}
|
|
873
|
+
function isDisconnectMaskActive(mask, now) {
|
|
874
|
+
return mask !== null && now - mask.since < TRANSIENT_DISCONNECT_VISIBILITY_MS;
|
|
875
|
+
}
|
|
876
|
+
function applyDisconnectMask(view, mask, now) {
|
|
877
|
+
if (!isDisconnectMaskActive(mask, now)) return view;
|
|
878
|
+
return { ...view, phase: mask.heldPhase, sandboxSubstate: mask.heldSubstate };
|
|
879
|
+
}
|
|
880
|
+
|
|
865
881
|
// ../../shared/transcript/malformed-search.ts
|
|
866
882
|
var MALFORMED_SEARCH_BLOCKED_TRANSCRIPT_PREFIX = "Arcanist blocked this malformed search command before execution.";
|
|
867
883
|
var BASH_UNMATCHED_QUOTE_EOF_RE = /\/bin\/bash:\s+-c:\s+line\s+\d+:\s+unexpected EOF while looking for matching [`'"][`'"]?/i;
|
|
@@ -1244,6 +1260,24 @@ function projectRetryStatus(data, index) {
|
|
|
1244
1260
|
...typeof data?.retryAfterMs === "number" ? { retryAfterMs: data.retryAfterMs } : {}
|
|
1245
1261
|
};
|
|
1246
1262
|
}
|
|
1263
|
+
function projectPromptRetrying(data, index) {
|
|
1264
|
+
const attempt = typeof data?.attempt === "number" ? data.attempt : Number(data?.attempt ?? 0);
|
|
1265
|
+
const cap = typeof data?.cap === "number" ? data.cap : void 0;
|
|
1266
|
+
const retryPromptId = typeof data?.retryPromptId === "string" ? data.retryPromptId : resolvePromptId(data);
|
|
1267
|
+
return {
|
|
1268
|
+
type: "retry_status",
|
|
1269
|
+
// Key off retryPromptId (always present in the emitted payload, unique per
|
|
1270
|
+
// retry) rather than the index, so the id is stable across replays. The
|
|
1271
|
+
// event's `timestamp` lives at the top level, not inside `data`.
|
|
1272
|
+
id: `pr-retry-${retryPromptId ?? index}`,
|
|
1273
|
+
...retryPromptId ? { promptId: retryPromptId } : {},
|
|
1274
|
+
attempt,
|
|
1275
|
+
message: "Sandbox dropped mid-task; retrying on a fresh sandbox",
|
|
1276
|
+
scope: "sandbox_disconnect",
|
|
1277
|
+
...cap !== void 0 ? { maxAttempts: cap } : {},
|
|
1278
|
+
...typeof data?.reason === "string" ? { reason: data.reason } : {}
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1247
1281
|
function projectBranchChanged(data, index) {
|
|
1248
1282
|
return {
|
|
1249
1283
|
type: "branch_changed",
|
|
@@ -1515,6 +1549,9 @@ function flattenSessionEvents(raw) {
|
|
|
1515
1549
|
case "retry_status":
|
|
1516
1550
|
pushEvent(state, projectRetryStatus(data, state.merged.length));
|
|
1517
1551
|
break;
|
|
1552
|
+
case "prompt_retrying":
|
|
1553
|
+
pushEvent(state, projectPromptRetrying(data, state.merged.length));
|
|
1554
|
+
break;
|
|
1518
1555
|
case "branch_changed":
|
|
1519
1556
|
pushEvent(state, projectBranchChanged(data, state.merged.length));
|
|
1520
1557
|
break;
|
|
@@ -2139,6 +2176,12 @@ function formatStatusLine(status) {
|
|
|
2139
2176
|
if (status.spawnDurationMs != null) details.push(`spawn ${(status.spawnDurationMs / 1e3).toFixed(1)}s`);
|
|
2140
2177
|
return details.length > 0 ? `[status] ${phaseLabel} | ${details.join(" | ")}` : `[status] ${phaseLabel}`;
|
|
2141
2178
|
}
|
|
2179
|
+
function advanceStatusDisconnectMask(status, mask, lastView, now) {
|
|
2180
|
+
const view = status.phase ? { phase: status.phase, sandboxSubstate: status.sandboxSubstate } : null;
|
|
2181
|
+
const nextMask = nextDisconnectMask(mask, lastView, view, now);
|
|
2182
|
+
const displayStatus = view ? { ...status, ...applyDisconnectMask(view, nextMask, now) } : status;
|
|
2183
|
+
return { displayStatus, mask: nextMask, view };
|
|
2184
|
+
}
|
|
2142
2185
|
async function printNoChangeOutcome(config, sessionId) {
|
|
2143
2186
|
try {
|
|
2144
2187
|
const data = await apiFetch(
|
|
@@ -2197,6 +2240,8 @@ async function watchCommand(sessionId, options, command) {
|
|
|
2197
2240
|
let afterSequence = initialAfterSequence;
|
|
2198
2241
|
let lastStatusLine = null;
|
|
2199
2242
|
let textOpen = false;
|
|
2243
|
+
let disconnectMask = null;
|
|
2244
|
+
let lastStatusView = null;
|
|
2200
2245
|
if (!json) console.log(`Watching session ${sessionId}...`);
|
|
2201
2246
|
try {
|
|
2202
2247
|
while (true) {
|
|
@@ -2208,7 +2253,10 @@ async function watchCommand(sessionId, options, command) {
|
|
|
2208
2253
|
const parsed = parseSsePayload(payload);
|
|
2209
2254
|
const receivedFullPage = parsed.events.length >= pageSize;
|
|
2210
2255
|
if (!json && parsed.status) {
|
|
2211
|
-
const
|
|
2256
|
+
const advanced = advanceStatusDisconnectMask(parsed.status, disconnectMask, lastStatusView, Date.now());
|
|
2257
|
+
disconnectMask = advanced.mask;
|
|
2258
|
+
lastStatusView = advanced.view;
|
|
2259
|
+
const nextStatusLine = formatStatusLine(advanced.displayStatus);
|
|
2212
2260
|
if (nextStatusLine !== lastStatusLine) {
|
|
2213
2261
|
if (textOpen) {
|
|
2214
2262
|
process.stdout.write("\n");
|