@tryarcanist/cli 0.1.168 → 0.1.170
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 +5 -1
- package/dist/index.js +85 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -116,7 +116,7 @@ Exit codes:
|
|
|
116
116
|
130 interrupted
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
Mutation commands send an `Idempotency-Key` header. The CLI does not auto-retry mutation endpoints; retry explicitly with the same `--idempotency-key` when a request may have already reached the server. `--idempotency-key` is available on `sessions create` and `
|
|
119
|
+
Mutation commands send an `Idempotency-Key` header. The CLI does not auto-retry mutation endpoints; retry explicitly with the same `--idempotency-key` when a request may have already reached the server. `--idempotency-key` is available on `sessions create`, `sessions send`, `tokens create`, and `sandbox build`. `tokens create` and `sandbox build` use auto-random keys by default, so only an explicit key makes cross-invocation retries safe.
|
|
120
120
|
|
|
121
121
|
## Commands
|
|
122
122
|
|
|
@@ -360,8 +360,12 @@ Creates a CLI token and prints the plaintext token exactly once.
|
|
|
360
360
|
```bash
|
|
361
361
|
arcanist tokens create --scope read
|
|
362
362
|
arcanist tokens create --scope read --expires-in-days 30 --json
|
|
363
|
+
arcanist tokens create --scope read --idempotency-key 1f0e6f1a-...
|
|
363
364
|
```
|
|
364
365
|
|
|
366
|
+
JSON mode returns `{ok, token, id, scope}`.
|
|
367
|
+
The plaintext token is shown only once.
|
|
368
|
+
Use `--idempotency-key <uuid>` when manually retrying a token create whose first response may have been lost; duplicate keys return `duplicate_request` rather than replaying the raw token.
|
|
365
369
|
Read-scoped tokens cannot create write-scoped tokens; the server enforces that and returns 403.
|
|
366
370
|
|
|
367
371
|
### `arcanist tokens revoke <id>`
|
package/dist/index.js
CHANGED
|
@@ -4191,7 +4191,7 @@ async function waitForBuild(config, businessId, buildId, options) {
|
|
|
4191
4191
|
if (options.follow) {
|
|
4192
4192
|
const logs = await apiFetch(config, buildLogsPath(businessId, buildId, { afterSequence }));
|
|
4193
4193
|
if (logs.logs.length > 0) {
|
|
4194
|
-
if (options.json) writeJson({
|
|
4194
|
+
if (options.json) writeJson({ type: "logs", logs: logs.logs });
|
|
4195
4195
|
else printLogs(logs.logs);
|
|
4196
4196
|
afterSequence = logs.logs[logs.logs.length - 1].sequence;
|
|
4197
4197
|
}
|
|
@@ -4313,29 +4313,46 @@ async function sandboxBuildCommand(sourceRepoArg, options = {}, command) {
|
|
|
4313
4313
|
const targetRepo = options.targetRepo ? parseRepoArg2(options.targetRepo, currentRepo) : null;
|
|
4314
4314
|
const businessId = await resolveBusinessId(config, options);
|
|
4315
4315
|
const ref = options.ref ?? currentRef();
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4316
|
+
let payload;
|
|
4317
|
+
try {
|
|
4318
|
+
payload = await apiFetch(
|
|
4319
|
+
config,
|
|
4320
|
+
`/api/businesses/${encodeURIComponent(businessId)}/repos/${encodeURIComponent(sourceRepo.owner)}/${encodeURIComponent(
|
|
4321
|
+
sourceRepo.repo
|
|
4322
|
+
)}/sandbox-layer/build-requests`,
|
|
4323
|
+
{
|
|
4324
|
+
method: "POST",
|
|
4325
|
+
headers: { "Idempotency-Key": options.idempotencyKey ?? randomIdempotencyKey() },
|
|
4326
|
+
body: JSON.stringify({
|
|
4327
|
+
ref,
|
|
4328
|
+
manifestPath,
|
|
4329
|
+
...targetRepo ? { targetRepo: { owner: targetRepo.owner, name: targetRepo.repo } } : {}
|
|
4330
|
+
})
|
|
4331
|
+
}
|
|
4332
|
+
);
|
|
4333
|
+
} catch (err) {
|
|
4334
|
+
if (err instanceof ApiError && err.status === 409 && err.data?.serverCode === "duplicate_request") {
|
|
4335
|
+
throw new CliError(err.code, err.message, {
|
|
4336
|
+
exitCode: err.exitCode,
|
|
4337
|
+
requestId: err.requestId,
|
|
4338
|
+
data: err.data,
|
|
4339
|
+
hint: "Use the same --idempotency-key only for retrying the original sandbox build request."
|
|
4340
|
+
});
|
|
4328
4341
|
}
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
if (!options.wait && !options.follow) return;
|
|
4342
|
+
throw err;
|
|
4343
|
+
}
|
|
4332
4344
|
const json = isJson(command, options);
|
|
4345
|
+
if (!(json && options.follow)) {
|
|
4346
|
+
emit(command, options, payload, (buildPayload) => printBuildRequest(buildPayload.buildRequest));
|
|
4347
|
+
}
|
|
4348
|
+
if (!options.wait && !options.follow) return;
|
|
4333
4349
|
const finalBuild = await waitForBuild(config, businessId, payload.buildRequest.id, {
|
|
4334
4350
|
follow: options.follow,
|
|
4335
4351
|
pollIntervalMs: parsePollInterval2(options.pollInterval),
|
|
4336
4352
|
json
|
|
4337
4353
|
});
|
|
4338
|
-
if (json)
|
|
4354
|
+
if (json)
|
|
4355
|
+
writeJson(options.follow ? { type: "build", buildRequest: finalBuild } : { ok: true, buildRequest: finalBuild });
|
|
4339
4356
|
else printTerminalBuildSummary(finalBuild);
|
|
4340
4357
|
if (finalBuild.status !== "completed") {
|
|
4341
4358
|
process.exitCode = 1;
|
|
@@ -4447,8 +4464,10 @@ async function sandboxLogsCommand(buildId, options = {}, command) {
|
|
|
4447
4464
|
config,
|
|
4448
4465
|
buildLogsPath(businessId, buildId, { afterSequence, limit })
|
|
4449
4466
|
);
|
|
4450
|
-
if (
|
|
4451
|
-
|
|
4467
|
+
if (payload.logs.length > 0 || !options.follow) {
|
|
4468
|
+
if (isJson(command, options)) writeJson(options.follow ? { type: "logs", logs: payload.logs } : payload);
|
|
4469
|
+
else printLogs(payload.logs);
|
|
4470
|
+
}
|
|
4452
4471
|
if (!options.follow) return;
|
|
4453
4472
|
if (payload.logs.length > 0) afterSequence = payload.logs[payload.logs.length - 1].sequence;
|
|
4454
4473
|
await sleep(parsePollInterval2(options.pollInterval));
|
|
@@ -4494,6 +4513,9 @@ async function sandboxAssignRepoCommand(targetRepoArg, sourceRepoArg, options =
|
|
|
4494
4513
|
});
|
|
4495
4514
|
}
|
|
4496
4515
|
async function sandboxUnassignDefaultCommand(options = {}, command) {
|
|
4516
|
+
if (isJson(command, options) && options.yes !== true) {
|
|
4517
|
+
throw new CliError("user", "`sandbox unassign default --json` requires --yes.");
|
|
4518
|
+
}
|
|
4497
4519
|
const { config } = resolveBusinessContext(command, options);
|
|
4498
4520
|
const businessId = await resolveBusinessId(config, options);
|
|
4499
4521
|
if (!options.yes) await confirmOrThrow("Clear the business default sandbox layer source?");
|
|
@@ -4505,6 +4527,9 @@ async function sandboxUnassignDefaultCommand(options = {}, command) {
|
|
|
4505
4527
|
emit(command, options, payload, () => console.log("Cleared default sandbox layer source."));
|
|
4506
4528
|
}
|
|
4507
4529
|
async function sandboxUnassignRepoCommand(targetRepoArg, options = {}, command) {
|
|
4530
|
+
if (isJson(command, options) && options.yes !== true) {
|
|
4531
|
+
throw new CliError("user", "`sandbox unassign repo --json` requires --yes.");
|
|
4532
|
+
}
|
|
4508
4533
|
const { config } = resolveBusinessContext(command, options);
|
|
4509
4534
|
const businessId = await resolveBusinessId(config, options);
|
|
4510
4535
|
const targetRepo = parseRepoArg2(targetRepoArg, currentRepo);
|
|
@@ -4790,13 +4815,27 @@ async function createTokenCommand(options, command) {
|
|
|
4790
4815
|
if (expiresInDays !== void 0 && (!Number.isInteger(expiresInDays) || expiresInDays <= 0)) {
|
|
4791
4816
|
throw new CliError("user", "expiresInDays must be a positive integer.");
|
|
4792
4817
|
}
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4818
|
+
let payload;
|
|
4819
|
+
try {
|
|
4820
|
+
payload = await apiFetch(config, "/api/cli-tokens", {
|
|
4821
|
+
method: "POST",
|
|
4822
|
+
headers: { "Idempotency-Key": options.idempotencyKey ?? randomIdempotencyKey() },
|
|
4823
|
+
body: JSON.stringify({
|
|
4824
|
+
scope: options.scope ?? "read",
|
|
4825
|
+
...expiresInDays !== void 0 ? { expiresInDays } : {}
|
|
4826
|
+
})
|
|
4827
|
+
});
|
|
4828
|
+
} catch (err) {
|
|
4829
|
+
if (err instanceof ApiError && err.status === 409 && err.data?.serverCode === "duplicate_request") {
|
|
4830
|
+
throw new CliError(err.code, err.message, {
|
|
4831
|
+
exitCode: err.exitCode,
|
|
4832
|
+
requestId: err.requestId,
|
|
4833
|
+
data: err.data,
|
|
4834
|
+
hint: "Use a new --idempotency-key for a new token, or retry the original command only if the first result was lost."
|
|
4835
|
+
});
|
|
4836
|
+
}
|
|
4837
|
+
throw err;
|
|
4838
|
+
}
|
|
4800
4839
|
emit(command, options, payload, (tokenPayload) => {
|
|
4801
4840
|
console.log(`Token: ${String(tokenPayload.token)}`);
|
|
4802
4841
|
console.log(`ID: ${String(tokenPayload.id)}`);
|
|
@@ -5062,10 +5101,27 @@ Examples:
|
|
|
5062
5101
|
var sandbox = program.command("sandbox").description("Sandbox layer commands");
|
|
5063
5102
|
sandbox.command("init").description("Create sandbox layer source files").action((options, command) => sandboxInitCommand(options, command));
|
|
5064
5103
|
sandbox.command("validate").description("Validate local sandbox layer source files").option("--manifest <path>", "Manifest path", ".arcanist/sandbox.yaml").action((options, command) => sandboxValidateCommand(options, command));
|
|
5065
|
-
sandbox.command("build").description("Build a repo-sourced sandbox layer").argument("[source-repo]", "Source repository owner/name; defaults to current git remote").option("--manifest <path>", "Manifest path", ".arcanist/sandbox.yaml").option("--ref <ref>", "Git ref to build").option("--target-repo <repo>", "Target repository owner/name for resource-profile coverage").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--wait", "Wait for the build to finish").option("--follow", "Follow logs while waiting").option("--poll-interval <ms>", "Polling interval in milliseconds").
|
|
5104
|
+
sandbox.command("build").description("Build a repo-sourced sandbox layer").argument("[source-repo]", "Source repository owner/name; defaults to current git remote").option("--manifest <path>", "Manifest path", ".arcanist/sandbox.yaml").option("--ref <ref>", "Git ref to build").option("--target-repo <repo>", "Target repository owner/name for resource-profile coverage").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--wait", "Wait for the build to finish").option("--follow", "Follow logs while waiting").option("--poll-interval <ms>", "Polling interval in milliseconds").option("--idempotency-key <uuid>", "Request idempotency key for safe manual retries").addHelpText(
|
|
5105
|
+
"after",
|
|
5106
|
+
`
|
|
5107
|
+
JSON:
|
|
5108
|
+
Without --wait and --follow, --json prints one object: {ok, buildRequest}
|
|
5109
|
+
With --wait and without --follow, --json prints two objects: initial {ok, buildRequest} then terminal {ok, buildRequest}
|
|
5110
|
+
With --follow, --json emits NDJSON:
|
|
5111
|
+
{type:"logs", logs:[...]}
|
|
5112
|
+
{type:"build", buildRequest:{...}}
|
|
5113
|
+
`
|
|
5114
|
+
).action((sourceRepo, options, command) => sandboxBuildCommand(sourceRepo, options, command));
|
|
5066
5115
|
sandbox.command("status").description("Show sandbox layer status").argument("[repo]", "Repository owner/name; defaults to current git remote").option("--business <id>", "Business ID; defaults to authenticated user's business").action((repo, options, command) => sandboxStatusCommand(repo, options, command));
|
|
5067
5116
|
sandbox.command("history").description("Show sandbox layer build history").argument("[source-repo]", "Source repository owner/name; defaults to current git remote").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--target-repo <repo>", "Target repository owner/name for resource-profile coverage").option("--status <status>", "Filter by build status").option("--limit <n>", "Maximum builds to return").action((sourceRepo, options, command) => sandboxHistoryCommand(sourceRepo, options, command));
|
|
5068
|
-
sandbox.command("logs").description("Print sandbox layer build logs").argument("<build-id>", "Build ID").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--follow", "Follow logs").option("--after-sequence <n>", "Return logs after this sequence").option("--limit <n>", "Maximum log chunks to return").option("--poll-interval <ms>", "Polling interval in milliseconds").
|
|
5117
|
+
sandbox.command("logs").description("Print sandbox layer build logs").argument("<build-id>", "Build ID").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--follow", "Follow logs").option("--after-sequence <n>", "Return logs after this sequence").option("--limit <n>", "Maximum log chunks to return").option("--poll-interval <ms>", "Polling interval in milliseconds").addHelpText(
|
|
5118
|
+
"after",
|
|
5119
|
+
`
|
|
5120
|
+
JSON:
|
|
5121
|
+
Without --follow, --json prints one object: {ok, logs}
|
|
5122
|
+
With --follow, --json emits NDJSON lines for non-empty batches: {type:"logs", logs:[...]}
|
|
5123
|
+
`
|
|
5124
|
+
).action((buildId, options, command) => sandboxLogsCommand(buildId, options, command));
|
|
5069
5125
|
sandbox.command("rebuild-stale").description("Create stale sandbox template rebuild campaigns").option("--business <id>", "Business ID to scan; defaults to authenticated user's business").option("--all", "Scan all businesses").option("--dry-run", "Preview stale rebuilds without queueing provider builds").option("--yes", "Start a rebuild campaign").option("--follow", "Follow campaign progress").action((options, command) => sandboxRebuildStaleCommand(options, command));
|
|
5070
5126
|
var sandboxAssign = sandbox.command("assign").description("Assign sandbox layer sources");
|
|
5071
5127
|
sandboxAssign.command("default").description("Set the business default sandbox layer source").argument("<source-repo>", "Source repository owner/name").option("--manifest <path>", "Manifest path", ".arcanist/sandbox.yaml").option("--business <id>", "Business ID; defaults to authenticated user's business").action((sourceRepo, options, command) => sandboxAssignDefaultCommand(sourceRepo, options, command));
|
|
@@ -5084,7 +5140,7 @@ egress.command("sync").description("Apply the merged egress allowlist source fil
|
|
|
5084
5140
|
egress.command("validate").description("Validate a local egress allowlist source file").argument("[path]", "Path to validate", ".arcanist/egress-allowlist.txt").action((path, options, command) => egressValidateCommand(path, options, command));
|
|
5085
5141
|
var tokens = program.command("tokens").description("CLI token commands");
|
|
5086
5142
|
tokens.command("list").description("List CLI tokens").option("--limit <n>", "Maximum tokens to return").option("--cursor <cursor>", "Pagination cursor").action((options, command) => listTokensCommand(options, command));
|
|
5087
|
-
tokens.command("create").description("Create a CLI token and print it once").option("--scope <scope>", "Token scope: read or write", "read").option("--expires-in-days <days>", "Token expiry in days").addHelpText(
|
|
5143
|
+
tokens.command("create").description("Create a CLI token and print it once").option("--scope <scope>", "Token scope: read or write", "read").option("--expires-in-days <days>", "Token expiry in days").option("--idempotency-key <uuid>", "Request idempotency key for safe manual retries").addHelpText(
|
|
5088
5144
|
"after",
|
|
5089
5145
|
`
|
|
5090
5146
|
Examples:
|