@tryarcanist/cli 0.1.227 → 0.1.229
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 +25 -0
- package/dist/index.js +141 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -362,6 +362,31 @@ arcanist sessions usage abc123
|
|
|
362
362
|
arcanist sessions usage abc123 --json
|
|
363
363
|
```
|
|
364
364
|
|
|
365
|
+
### `arcanist sessions artifacts <session-id>`
|
|
366
|
+
|
|
367
|
+
Lists the artifacts a session produced (screenshots, videos) as `artifactId`, `kind`, `label`, `filename`, `bytes`, `createdAt`.
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
arcanist sessions artifacts abc123
|
|
371
|
+
arcanist sessions artifacts abc123 --json
|
|
372
|
+
arcanist sessions artifacts abc123 --all -o ./run-evidence
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
`--all` downloads every artifact for the session instead of listing them, and `-o, --output <dir>` picks the destination directory (default: the current directory). An existing file is never overwritten: if the target name is already taken - by another artifact in the same run or by a file already on disk - the artifact ID is prefixed instead.
|
|
376
|
+
|
|
377
|
+
JSON mode returns `{artifacts}` for a listing and `{downloaded}` for `--all`, where each entry is `{artifactId, path}`.
|
|
378
|
+
|
|
379
|
+
### `arcanist sessions artifacts get <session-id> <artifact-id>`
|
|
380
|
+
|
|
381
|
+
Downloads one artifact and prints the path it wrote.
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
arcanist sessions artifacts get abc123 art-1
|
|
385
|
+
arcanist sessions artifacts get abc123 art-1 -o ./run-evidence
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`-o, --output <dir>` picks the destination directory (default: the current directory). An artifact ID that is not in the session exits non-zero. JSON mode returns `{artifactId, path}`.
|
|
389
|
+
|
|
365
390
|
### `arcanist repos list`
|
|
366
391
|
|
|
367
392
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -267,6 +267,9 @@ async function apiFetch(config, path, init) {
|
|
|
267
267
|
async function apiFetchText(config, path, init) {
|
|
268
268
|
return apiRequest(config, path, init, (res) => res.text());
|
|
269
269
|
}
|
|
270
|
+
async function apiFetchBytes(config, path, init) {
|
|
271
|
+
return apiRequest(config, path, init, async (res) => new Uint8Array(await res.arrayBuffer()));
|
|
272
|
+
}
|
|
270
273
|
async function resolveBusinessId(config, options) {
|
|
271
274
|
if (options.business && options.business.trim()) return options.business.trim();
|
|
272
275
|
const whoami = await apiFetch(config, "/api/auth/whoami");
|
|
@@ -534,7 +537,7 @@ async function readHiddenPrompt(prompt) {
|
|
|
534
537
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
535
538
|
throw new CliError("user", "No interactive terminal available. Re-run with --token-stdin or set ARCANIST_TOKEN.");
|
|
536
539
|
}
|
|
537
|
-
return new Promise((
|
|
540
|
+
return new Promise((resolve3, reject) => {
|
|
538
541
|
const stdin = process.stdin;
|
|
539
542
|
const inputChars = [];
|
|
540
543
|
let ansiCarry = "";
|
|
@@ -550,7 +553,7 @@ async function readHiddenPrompt(prompt) {
|
|
|
550
553
|
settled = true;
|
|
551
554
|
cleanup();
|
|
552
555
|
process.stdout.write("\n");
|
|
553
|
-
|
|
556
|
+
resolve3(inputChars.join(""));
|
|
554
557
|
};
|
|
555
558
|
const fail2 = (error) => {
|
|
556
559
|
if (settled) return;
|
|
@@ -678,7 +681,7 @@ var VendorBinaryMissingError = class extends CliError {
|
|
|
678
681
|
}
|
|
679
682
|
};
|
|
680
683
|
function runVendorLoginProcess(spec) {
|
|
681
|
-
return new Promise((
|
|
684
|
+
return new Promise((resolve3, reject) => {
|
|
682
685
|
const child = spawn(spec.binPath, spec.args, {
|
|
683
686
|
stdio: "inherit",
|
|
684
687
|
env: { ...process.env, ...spec.env }
|
|
@@ -692,7 +695,7 @@ function runVendorLoginProcess(spec) {
|
|
|
692
695
|
});
|
|
693
696
|
child.on("close", (code) => {
|
|
694
697
|
if (code === 0) {
|
|
695
|
-
|
|
698
|
+
resolve3();
|
|
696
699
|
return;
|
|
697
700
|
}
|
|
698
701
|
reject(
|
|
@@ -806,7 +809,7 @@ function resolveVendorBin(config, optionPath) {
|
|
|
806
809
|
return optionPath?.trim() || process.env[config.binEnvVar]?.trim() || config.defaultBin;
|
|
807
810
|
}
|
|
808
811
|
function runVendorInstall(config, command) {
|
|
809
|
-
return new Promise((
|
|
812
|
+
return new Promise((resolve3, reject) => {
|
|
810
813
|
const child = spawn2("bash", ["-c", command], { stdio: ["inherit", 2, "inherit"] });
|
|
811
814
|
child.on("error", (err) => {
|
|
812
815
|
reject(
|
|
@@ -817,7 +820,7 @@ function runVendorInstall(config, command) {
|
|
|
817
820
|
});
|
|
818
821
|
child.on("close", (code) => {
|
|
819
822
|
if (code === 0) {
|
|
820
|
-
|
|
823
|
+
resolve3();
|
|
821
824
|
return;
|
|
822
825
|
}
|
|
823
826
|
reject(
|
|
@@ -1025,7 +1028,7 @@ async function agentSubscriptionLogoutCommand(config, options, command) {
|
|
|
1025
1028
|
|
|
1026
1029
|
// ../../shared/utils/timing.ts
|
|
1027
1030
|
function sleep(ms) {
|
|
1028
|
-
return new Promise((
|
|
1031
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1029
1032
|
}
|
|
1030
1033
|
|
|
1031
1034
|
// src/status-poll.ts
|
|
@@ -1153,6 +1156,102 @@ async function anubisCommand(prUrl, options = {}, command) {
|
|
|
1153
1156
|
});
|
|
1154
1157
|
}
|
|
1155
1158
|
|
|
1159
|
+
// src/commands/artifacts.ts
|
|
1160
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1161
|
+
import { basename, join as join4, resolve as resolve2 } from "path";
|
|
1162
|
+
async function fetchArtifacts(config, sessionId) {
|
|
1163
|
+
const payload = await apiFetch(
|
|
1164
|
+
config,
|
|
1165
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/artifacts/list`
|
|
1166
|
+
);
|
|
1167
|
+
return payload.artifacts ?? [];
|
|
1168
|
+
}
|
|
1169
|
+
function artifactFilename(artifact) {
|
|
1170
|
+
const recorded = artifact.metadata?.filename;
|
|
1171
|
+
const safe = recorded ? sanitizeTerminalText(basename(recorded)) : "";
|
|
1172
|
+
return safe && safe !== "." && safe !== ".." ? safe : `${sanitizeTerminalText(artifact.type)}-${artifact.artifactId}`;
|
|
1173
|
+
}
|
|
1174
|
+
async function downloadArtifact(config, sessionId, artifact, outputDir, usedNames) {
|
|
1175
|
+
const filename = artifactFilename(artifact);
|
|
1176
|
+
const taken = usedNames.has(filename) || existsSync3(join4(outputDir, filename));
|
|
1177
|
+
const targetName = taken ? `${artifact.artifactId}-${filename}` : filename;
|
|
1178
|
+
usedNames.add(targetName);
|
|
1179
|
+
const bytes = await apiFetchBytes(
|
|
1180
|
+
config,
|
|
1181
|
+
`/api/sessions/${encodeURIComponent(sessionId)}/artifacts/${encodeURIComponent(artifact.artifactId)}/view?filename=${encodeURIComponent(filename)}`
|
|
1182
|
+
);
|
|
1183
|
+
const targetPath = join4(outputDir, targetName);
|
|
1184
|
+
writeFileSync2(targetPath, bytes);
|
|
1185
|
+
return targetPath;
|
|
1186
|
+
}
|
|
1187
|
+
function resolveOutputDir(output) {
|
|
1188
|
+
const dir = resolve2(output ?? process.cwd());
|
|
1189
|
+
mkdirSync2(dir, { recursive: true });
|
|
1190
|
+
return dir;
|
|
1191
|
+
}
|
|
1192
|
+
async function listArtifactsCommand(sessionId, options, command) {
|
|
1193
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1194
|
+
const config = requireConfig(runtime);
|
|
1195
|
+
const artifacts2 = await fetchArtifacts(config, sessionId);
|
|
1196
|
+
if (options.all) {
|
|
1197
|
+
await downloadAllArtifacts(config, sessionId, artifacts2, options, command);
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
emit(command, options, { artifacts: artifacts2 }, (payload) => {
|
|
1201
|
+
if (payload.artifacts.length === 0) {
|
|
1202
|
+
console.log(`No artifacts for session ${sessionId}.`);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
console.log(["ARTIFACT ID", "KIND", "LABEL", "FILENAME", "BYTES", "CREATED AT"].join(" "));
|
|
1206
|
+
for (const artifact of payload.artifacts) {
|
|
1207
|
+
console.log(
|
|
1208
|
+
[
|
|
1209
|
+
artifact.artifactId,
|
|
1210
|
+
sanitizeTerminalText(artifact.type),
|
|
1211
|
+
sanitizeTerminalText(artifact.metadata?.label ?? "-"),
|
|
1212
|
+
artifactFilename(artifact),
|
|
1213
|
+
artifact.metadata?.bytes ?? "-",
|
|
1214
|
+
new Date(artifact.createdAt).toISOString()
|
|
1215
|
+
].join(" ")
|
|
1216
|
+
);
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
async function downloadAllArtifacts(config, sessionId, artifacts2, options, command) {
|
|
1221
|
+
if (artifacts2.length === 0) {
|
|
1222
|
+
emit(command, options, { downloaded: [] }, () => {
|
|
1223
|
+
console.log(`No artifacts for session ${sessionId}.`);
|
|
1224
|
+
});
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
const outputDir = resolveOutputDir(options.output);
|
|
1228
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
1229
|
+
const downloaded = [];
|
|
1230
|
+
for (const artifact of artifacts2) {
|
|
1231
|
+
const path = await downloadArtifact(config, sessionId, artifact, outputDir, usedNames);
|
|
1232
|
+
downloaded.push({ artifactId: artifact.artifactId, path });
|
|
1233
|
+
}
|
|
1234
|
+
emit(command, options, { downloaded }, (payload) => {
|
|
1235
|
+
for (const entry of payload.downloaded) console.log(entry.path);
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
async function getArtifactCommand(sessionId, artifactId, options, command) {
|
|
1239
|
+
const runtime = getRuntimeOptions(command, options);
|
|
1240
|
+
const config = requireConfig(runtime);
|
|
1241
|
+
const artifacts2 = await fetchArtifacts(config, sessionId);
|
|
1242
|
+
const artifact = artifacts2.find((candidate) => candidate.artifactId === artifactId);
|
|
1243
|
+
if (!artifact) {
|
|
1244
|
+
throw new CliError("user", `Artifact ${artifactId} not found in session ${sessionId}.`, {
|
|
1245
|
+
hint: `Run \`arcanist sessions artifacts ${sessionId}\` to list the artifact IDs for this session.`
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
const outputDir = resolveOutputDir(options.output);
|
|
1249
|
+
const path = await downloadArtifact(config, sessionId, artifact, outputDir, /* @__PURE__ */ new Set());
|
|
1250
|
+
emit(command, options, { artifactId, path }, (payload) => {
|
|
1251
|
+
console.log(payload.path);
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1156
1255
|
// src/commands/auth.ts
|
|
1157
1256
|
async function whoamiCommand(options, command) {
|
|
1158
1257
|
const { config } = resolveBusinessContext(command, options);
|
|
@@ -2147,7 +2246,7 @@ function formatTime(value) {
|
|
|
2147
2246
|
|
|
2148
2247
|
// src/commands/codex.ts
|
|
2149
2248
|
import { readFile as readFile2 } from "fs/promises";
|
|
2150
|
-
import { join as
|
|
2249
|
+
import { join as join5 } from "path";
|
|
2151
2250
|
var CODEX_SUBSCRIPTION_PATH = "/api/settings/codex-subscription";
|
|
2152
2251
|
var CODEX_SUBSCRIPTION_AUTH_JSON_PATH = "/api/settings/codex-subscription/auth-json";
|
|
2153
2252
|
var CODEX_SUBSCRIPTION_ENABLED_PATH = "/api/settings/codex-subscription/enabled";
|
|
@@ -2185,7 +2284,7 @@ async function codexLoginCommand(options, command) {
|
|
|
2185
2284
|
await runCodexDeviceLogin(codexPath, codexHome);
|
|
2186
2285
|
let raw;
|
|
2187
2286
|
try {
|
|
2188
|
-
raw = await readFile2(
|
|
2287
|
+
raw = await readFile2(join5(codexHome, "auth.json"), "utf8");
|
|
2189
2288
|
} catch {
|
|
2190
2289
|
throw new CliError("user", "Codex login completed but no auth.json was written.", {
|
|
2191
2290
|
hint: "Verify `codex login --device-auth` succeeds on its own, then re-run `arcanist codex login`."
|
|
@@ -2259,7 +2358,7 @@ async function codexLogoutCommand(options, command) {
|
|
|
2259
2358
|
|
|
2260
2359
|
// src/uploads.ts
|
|
2261
2360
|
import { readFile as readFile3 } from "fs/promises";
|
|
2262
|
-
import { basename, extname } from "path";
|
|
2361
|
+
import { basename as basename2, extname } from "path";
|
|
2263
2362
|
|
|
2264
2363
|
// ../../shared/constants/uploads.ts
|
|
2265
2364
|
var MAX_UPLOADED_FILES = 5;
|
|
@@ -2355,11 +2454,11 @@ function validateUploadedFilePayload(files, existingNames = []) {
|
|
|
2355
2454
|
async function resolveUploadedFileOptions(files) {
|
|
2356
2455
|
const paths = normalizeUploadedFileOptions(files);
|
|
2357
2456
|
if (paths.length === 0) return void 0;
|
|
2358
|
-
const names = paths.map((path) =>
|
|
2457
|
+
const names = paths.map((path) => basename2(path));
|
|
2359
2458
|
validateUploadedFileNames(names);
|
|
2360
2459
|
const uploadedFiles = await Promise.all(
|
|
2361
2460
|
paths.map(async (path) => {
|
|
2362
|
-
const name =
|
|
2461
|
+
const name = basename2(path);
|
|
2363
2462
|
try {
|
|
2364
2463
|
return { name, content: await readFile3(path, "utf8") };
|
|
2365
2464
|
} catch (err) {
|
|
@@ -4764,7 +4863,7 @@ async function reviewCommand(prUrl, options = {}, command) {
|
|
|
4764
4863
|
}
|
|
4765
4864
|
|
|
4766
4865
|
// src/commands/sandbox.ts
|
|
4767
|
-
import { existsSync as
|
|
4866
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4768
4867
|
import { dirname as dirname2 } from "path";
|
|
4769
4868
|
|
|
4770
4869
|
// ../../shared/sandbox-layer/parser.ts
|
|
@@ -5311,7 +5410,7 @@ function assertCleanAndPushed() {
|
|
|
5311
5410
|
throw new CliError("user", "Current commit is not pushed; push before building the sandbox layer.");
|
|
5312
5411
|
}
|
|
5313
5412
|
function assertManifestExists(path) {
|
|
5314
|
-
if (!
|
|
5413
|
+
if (!existsSync4(path)) throw new CliError("user", `Missing sandbox manifest: ${path}`);
|
|
5315
5414
|
}
|
|
5316
5415
|
function repoPath(repo) {
|
|
5317
5416
|
return `${repo.owner}/${repo.repo}`;
|
|
@@ -5528,11 +5627,11 @@ function sourceBody(sourceRepo, manifestPath) {
|
|
|
5528
5627
|
}
|
|
5529
5628
|
async function sandboxInitCommand(options = {}, command) {
|
|
5530
5629
|
const runtime = getRuntimeOptions(command, options);
|
|
5531
|
-
if (
|
|
5630
|
+
if (existsSync4(DEFAULT_MANIFEST_PATH)) throw new CliError("conflict", `${DEFAULT_MANIFEST_PATH} already exists`);
|
|
5532
5631
|
const layerPath = ".arcanist/sandbox.layer.Dockerfile";
|
|
5533
|
-
if (
|
|
5534
|
-
|
|
5535
|
-
|
|
5632
|
+
if (existsSync4(layerPath)) throw new CliError("conflict", `${layerPath} already exists`);
|
|
5633
|
+
mkdirSync3(dirname2(DEFAULT_MANIFEST_PATH), { recursive: true });
|
|
5634
|
+
writeFileSync3(
|
|
5536
5635
|
DEFAULT_MANIFEST_PATH,
|
|
5537
5636
|
[
|
|
5538
5637
|
"version: 1",
|
|
@@ -5545,7 +5644,7 @@ async function sandboxInitCommand(options = {}, command) {
|
|
|
5545
5644
|
].join("\n"),
|
|
5546
5645
|
"utf8"
|
|
5547
5646
|
);
|
|
5548
|
-
|
|
5647
|
+
writeFileSync3(
|
|
5549
5648
|
layerPath,
|
|
5550
5649
|
[
|
|
5551
5650
|
"# Only RUN and ENV instructions are supported.",
|
|
@@ -6485,6 +6584,29 @@ Examples:
|
|
|
6485
6584
|
arcanist sessions usage <session-id> --json
|
|
6486
6585
|
`
|
|
6487
6586
|
).action((sessionId, options, command) => usageCommand(sessionId, options, command));
|
|
6587
|
+
var artifacts = sessions.command("artifacts").description("List or download session artifacts (screenshots, videos)").argument("<session-id>", "Session ID").option("-o, --output <dir>", "Directory to write downloads into; defaults to the current directory").option("--all", "Download every artifact for the session instead of listing them").addHelpText(
|
|
6588
|
+
"after",
|
|
6589
|
+
`
|
|
6590
|
+
Examples:
|
|
6591
|
+
arcanist sessions artifacts <session-id>
|
|
6592
|
+
arcanist sessions artifacts <session-id> --json
|
|
6593
|
+
arcanist sessions artifacts <session-id> --all -o ./run-evidence
|
|
6594
|
+
|
|
6595
|
+
JSON:
|
|
6596
|
+
JSON mode returns {artifacts} for a listing and {downloaded} for --all, where each downloaded entry is {artifactId, path}.
|
|
6597
|
+
`
|
|
6598
|
+
).action((sessionId, options, command) => listArtifactsCommand(sessionId, options, command));
|
|
6599
|
+
artifacts.command("get").description("Download one session artifact by ID").argument("<session-id>", "Session ID").argument("<artifact-id>", "Artifact ID from `arcanist sessions artifacts`").option("-o, --output <dir>", "Directory to write the download into; defaults to the current directory").addHelpText(
|
|
6600
|
+
"after",
|
|
6601
|
+
`
|
|
6602
|
+
Examples:
|
|
6603
|
+
arcanist sessions artifacts get <session-id> <artifact-id>
|
|
6604
|
+
arcanist sessions artifacts get <session-id> <artifact-id> -o ./run-evidence
|
|
6605
|
+
|
|
6606
|
+
JSON:
|
|
6607
|
+
JSON mode returns {artifactId, path}.
|
|
6608
|
+
`
|
|
6609
|
+
).action((sessionId, artifactId, options, command) => getArtifactCommand(sessionId, artifactId, options, command));
|
|
6488
6610
|
var repos = program.command("repos").description("Repository discovery commands");
|
|
6489
6611
|
repos.command("list").description("List accessible repositories").addHelpText(
|
|
6490
6612
|
"after",
|