@tryarcanist/cli 0.1.133 → 0.1.134
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.js +197 -41
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2666,6 +2666,155 @@ async function createCommand(repoUrl, promptArg, options, command) {
|
|
|
2666
2666
|
if (sessionData.sessionUrl) console.log(`URL: ${sessionData.sessionUrl}`);
|
|
2667
2667
|
}
|
|
2668
2668
|
|
|
2669
|
+
// src/commands/egress.ts
|
|
2670
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
2671
|
+
|
|
2672
|
+
// ../../shared/types/business-egress-policy.ts
|
|
2673
|
+
var MAX_BUSINESS_EGRESS_DOMAINS = 100;
|
|
2674
|
+
var MAX_DOMAIN_LENGTH = 253;
|
|
2675
|
+
function normalizeEgressDomains(entries, key = "egressAllowlist") {
|
|
2676
|
+
const unique = [...new Set(entries.map((entry) => entry.trim().toLowerCase()).filter(Boolean))].sort();
|
|
2677
|
+
if (unique.length > MAX_BUSINESS_EGRESS_DOMAINS) {
|
|
2678
|
+
throw new Error(`${key} must contain at most ${MAX_BUSINESS_EGRESS_DOMAINS} domain names`);
|
|
2679
|
+
}
|
|
2680
|
+
for (const entry of unique) {
|
|
2681
|
+
assertValidEgressDomain(entry, key);
|
|
2682
|
+
}
|
|
2683
|
+
return unique;
|
|
2684
|
+
}
|
|
2685
|
+
function assertValidEgressDomain(value, key = "egressAllowlist") {
|
|
2686
|
+
if (value.includes("*") || value.startsWith(".") || value.endsWith(".") || value.includes("/") || value.includes(":")) {
|
|
2687
|
+
throw new Error(`${key} entries must be exact domain names`);
|
|
2688
|
+
}
|
|
2689
|
+
if (value.length > MAX_DOMAIN_LENGTH) {
|
|
2690
|
+
throw new Error(`${key} entries must be exact domain names`);
|
|
2691
|
+
}
|
|
2692
|
+
const labels = value.split(".");
|
|
2693
|
+
if (labels.length === 4 && labels.every((label) => /^\d+$/.test(label) && Number(label) >= 0 && Number(label) <= 255)) {
|
|
2694
|
+
throw new Error(`${key} entries must be exact domain names`);
|
|
2695
|
+
}
|
|
2696
|
+
if (labels.length < 2 || labels.some(
|
|
2697
|
+
(label) => label.length < 1 || label.length > 63 || !/^[a-z0-9-]+$/.test(label) || label.startsWith("-") || label.endsWith("-")
|
|
2698
|
+
)) {
|
|
2699
|
+
throw new Error(`${key} entries must be exact domain names`);
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// ../../shared/egress-allowlist/parser.ts
|
|
2704
|
+
var EGRESS_ALLOWLIST_SOURCE_PATH = ".arcanist/egress-allowlist.txt";
|
|
2705
|
+
function parseEgressAllowlistSourceFile(content, key = EGRESS_ALLOWLIST_SOURCE_PATH) {
|
|
2706
|
+
const domains = content.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
2707
|
+
return normalizeEgressDomains(domains, key);
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
// src/commands/egress.ts
|
|
2711
|
+
function parseRepoArg(value) {
|
|
2712
|
+
const trimmed = value.trim().replace(/^https:\/\/github\.com\//, "").replace(/\.git$/, "");
|
|
2713
|
+
const [owner, repo, extra] = trimmed.split("/");
|
|
2714
|
+
if (!owner || !repo || extra) throw new CliError("user", "repo must be owner/name or a GitHub URL");
|
|
2715
|
+
return { owner, repo };
|
|
2716
|
+
}
|
|
2717
|
+
function encodePathSegment(value) {
|
|
2718
|
+
return encodeURIComponent(value);
|
|
2719
|
+
}
|
|
2720
|
+
async function resolveBusinessId(config, options) {
|
|
2721
|
+
if (options.business && options.business.trim()) return options.business.trim();
|
|
2722
|
+
const whoami = await apiFetch(config, "/api/auth/whoami");
|
|
2723
|
+
if (whoami.businessId && whoami.businessId.trim()) return whoami.businessId;
|
|
2724
|
+
throw new CliError("user", "--business is required when the authenticated token has no business context.");
|
|
2725
|
+
}
|
|
2726
|
+
async function egressSourceSetCommand(sourceRepoArg, options = {}, command) {
|
|
2727
|
+
const runtime = getRuntimeOptions(command, options);
|
|
2728
|
+
const config = requireConfig(runtime);
|
|
2729
|
+
const businessId = await resolveBusinessId(config, options);
|
|
2730
|
+
const source = parseRepoArg(sourceRepoArg);
|
|
2731
|
+
const payload = await apiFetch(
|
|
2732
|
+
config,
|
|
2733
|
+
`/api/businesses/${encodePathSegment(businessId)}/egress-allowlist/source`,
|
|
2734
|
+
{
|
|
2735
|
+
method: "PUT",
|
|
2736
|
+
body: JSON.stringify({ sourceRepoOwner: source.owner, sourceRepoName: source.repo })
|
|
2737
|
+
}
|
|
2738
|
+
);
|
|
2739
|
+
if (isJson(command, options)) {
|
|
2740
|
+
writeJson(payload);
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
console.log(`Set egress allowlist source to ${source.owner}/${source.repo}:${EGRESS_ALLOWLIST_SOURCE_PATH}.`);
|
|
2744
|
+
}
|
|
2745
|
+
async function egressSourceGetCommand(options = {}, command) {
|
|
2746
|
+
const runtime = getRuntimeOptions(command, options);
|
|
2747
|
+
const config = requireConfig(runtime);
|
|
2748
|
+
const businessId = await resolveBusinessId(config, options);
|
|
2749
|
+
const payload = await apiFetch(
|
|
2750
|
+
config,
|
|
2751
|
+
`/api/businesses/${encodePathSegment(businessId)}/egress-allowlist/source`
|
|
2752
|
+
);
|
|
2753
|
+
if (isJson(command, options)) {
|
|
2754
|
+
writeJson(payload);
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
if (!payload.source) {
|
|
2758
|
+
console.log("No egress allowlist source configured.");
|
|
2759
|
+
return;
|
|
2760
|
+
}
|
|
2761
|
+
console.log(
|
|
2762
|
+
`${payload.source.sourceRepoOwner}/${payload.source.sourceRepoName}:${payload.path} (${payload.defaultBranch ?? "default branch"})`
|
|
2763
|
+
);
|
|
2764
|
+
console.log(`${payload.domains.length} ${payload.domains.length === 1 ? "domain" : "domains"}.`);
|
|
2765
|
+
}
|
|
2766
|
+
async function egressAddCommand(domains, options = {}, command) {
|
|
2767
|
+
const runtime = getRuntimeOptions(command, options);
|
|
2768
|
+
const config = requireConfig(runtime);
|
|
2769
|
+
const businessId = await resolveBusinessId(config, options);
|
|
2770
|
+
const normalized = normalizeEgressDomains(domains, "domains");
|
|
2771
|
+
const payload = await apiFetch(
|
|
2772
|
+
config,
|
|
2773
|
+
`/api/businesses/${encodePathSegment(businessId)}/egress-allowlist/pull-request`,
|
|
2774
|
+
{
|
|
2775
|
+
method: "POST",
|
|
2776
|
+
body: JSON.stringify({ domains: normalized, ...options.reason ? { reason: options.reason } : {} })
|
|
2777
|
+
}
|
|
2778
|
+
);
|
|
2779
|
+
if (isJson(command, options)) {
|
|
2780
|
+
writeJson(payload);
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
if (payload.status === "unchanged") {
|
|
2784
|
+
console.log("All requested domains are already present in the source file.");
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
console.log(`Egress allowlist PR ${payload.status}: ${payload.prUrl}`);
|
|
2788
|
+
console.log(`Added: ${payload.addedDomains.join(", ")}`);
|
|
2789
|
+
}
|
|
2790
|
+
async function egressSyncCommand(options = {}, command) {
|
|
2791
|
+
const runtime = getRuntimeOptions(command, options);
|
|
2792
|
+
const config = requireConfig(runtime);
|
|
2793
|
+
const businessId = await resolveBusinessId(config, options);
|
|
2794
|
+
const payload = await apiFetch(
|
|
2795
|
+
config,
|
|
2796
|
+
`/api/businesses/${encodePathSegment(businessId)}/egress-allowlist/sync`,
|
|
2797
|
+
{ method: "POST" }
|
|
2798
|
+
);
|
|
2799
|
+
if (isJson(command, options)) {
|
|
2800
|
+
writeJson(payload);
|
|
2801
|
+
return;
|
|
2802
|
+
}
|
|
2803
|
+
console.log(
|
|
2804
|
+
`Applied ${payload.egressAllowlist.length} egress allowlist domains from ${payload.source.sourceRepoOwner}/${payload.source.sourceRepoName}:${payload.path}.`
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
async function egressValidateCommand(path = EGRESS_ALLOWLIST_SOURCE_PATH, options = {}, command) {
|
|
2808
|
+
const content = readFileSync2(path, "utf8");
|
|
2809
|
+
const domains = parseEgressAllowlistSourceFile(content, path);
|
|
2810
|
+
if (isJson(command, options)) {
|
|
2811
|
+
writeJson({ ok: true, path, domains });
|
|
2812
|
+
return;
|
|
2813
|
+
}
|
|
2814
|
+
console.log(`Valid egress allowlist: ${path}`);
|
|
2815
|
+
console.log(`${domains.length} ${domains.length === 1 ? "domain" : "domains"}.`);
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2669
2818
|
// src/commands/login.ts
|
|
2670
2819
|
async function loginCommand(options, command) {
|
|
2671
2820
|
const runtime = getRuntimeOptions(command, options);
|
|
@@ -2735,7 +2884,7 @@ async function messageCommand(sessionId, promptArg, options = {}, command) {
|
|
|
2735
2884
|
|
|
2736
2885
|
// src/commands/sandbox.ts
|
|
2737
2886
|
import { execFileSync } from "child_process";
|
|
2738
|
-
import { existsSync, mkdirSync as mkdirSync2, readFileSync as
|
|
2887
|
+
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
2739
2888
|
import { dirname } from "path";
|
|
2740
2889
|
|
|
2741
2890
|
// ../../shared/sandbox-layer/parser.ts
|
|
@@ -3296,7 +3445,7 @@ function assertCleanAndPushed() {
|
|
|
3296
3445
|
function assertManifestExists(path) {
|
|
3297
3446
|
if (!existsSync(path)) throw new CliError("user", `Missing sandbox manifest: ${path}`);
|
|
3298
3447
|
}
|
|
3299
|
-
function
|
|
3448
|
+
function parseRepoArg2(value, fallback) {
|
|
3300
3449
|
if (!value) return fallback();
|
|
3301
3450
|
const parsed = parseGithubRepoFullName(value);
|
|
3302
3451
|
if (!parsed) throw new CliError("user", "repo must be owner/name or a GitHub URL");
|
|
@@ -3305,10 +3454,10 @@ function parseRepoArg(value, fallback) {
|
|
|
3305
3454
|
function repoPath(repo) {
|
|
3306
3455
|
return `${repo.owner}/${repo.repo}`;
|
|
3307
3456
|
}
|
|
3308
|
-
function
|
|
3457
|
+
function encodePathSegment2(value) {
|
|
3309
3458
|
return encodeURIComponent(value);
|
|
3310
3459
|
}
|
|
3311
|
-
async function
|
|
3460
|
+
async function resolveBusinessId2(config, options) {
|
|
3312
3461
|
if (options.business && options.business.trim()) return options.business.trim();
|
|
3313
3462
|
const whoami = await apiFetch(config, "/api/auth/whoami");
|
|
3314
3463
|
if (whoami.businessId && whoami.businessId.trim()) return whoami.businessId;
|
|
@@ -3447,7 +3596,7 @@ function buildLogsPath(businessId, buildId, options = {}) {
|
|
|
3447
3596
|
if (options.afterSequence != null) params.set("afterSequence", String(options.afterSequence));
|
|
3448
3597
|
if (options.limit != null) params.set("limit", String(options.limit));
|
|
3449
3598
|
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
3450
|
-
return `/api/businesses/${
|
|
3599
|
+
return `/api/businesses/${encodePathSegment2(businessId)}/sandbox-layer/build-requests/${encodePathSegment2(
|
|
3451
3600
|
buildId
|
|
3452
3601
|
)}/logs${suffix}`;
|
|
3453
3602
|
}
|
|
@@ -3464,7 +3613,7 @@ async function waitForBuild(config, businessId, buildId, options) {
|
|
|
3464
3613
|
}
|
|
3465
3614
|
const status = await apiFetch(
|
|
3466
3615
|
config,
|
|
3467
|
-
`/api/businesses/${
|
|
3616
|
+
`/api/businesses/${encodePathSegment2(businessId)}/sandbox-layer/build-requests/${encodePathSegment2(buildId)}`
|
|
3468
3617
|
);
|
|
3469
3618
|
if (TERMINAL_BUILD_STATUSES.has(status.buildRequest.status)) {
|
|
3470
3619
|
return status.buildRequest;
|
|
@@ -3553,12 +3702,12 @@ async function sandboxInitCommand(options = {}, command) {
|
|
|
3553
3702
|
async function sandboxValidateCommand(options = {}, command) {
|
|
3554
3703
|
const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
|
|
3555
3704
|
assertManifestExists(manifestPath);
|
|
3556
|
-
const manifestText =
|
|
3705
|
+
const manifestText = readFileSync3(manifestPath, "utf8");
|
|
3557
3706
|
let layerPath;
|
|
3558
3707
|
try {
|
|
3559
3708
|
layerPath = parseSandboxLayerManifest(manifestPath, manifestText).layer.dockerfile;
|
|
3560
3709
|
assertManifestExists(layerPath);
|
|
3561
|
-
const layerText =
|
|
3710
|
+
const layerText = readFileSync3(layerPath, "utf8");
|
|
3562
3711
|
const parsed = await parseSandboxLayerSource({ manifestPath, manifestText, layerPath, layerText });
|
|
3563
3712
|
const payload = formatValidationPayload({ manifestPath, layerPath, parsed });
|
|
3564
3713
|
if (isJson(command, options)) {
|
|
@@ -3582,13 +3731,13 @@ async function sandboxBuildCommand(sourceRepoArg, options = {}, command) {
|
|
|
3582
3731
|
const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
|
|
3583
3732
|
assertManifestExists(manifestPath);
|
|
3584
3733
|
if (!options.ref) assertCleanAndPushed();
|
|
3585
|
-
const sourceRepo =
|
|
3586
|
-
const targetRepo = options.targetRepo ?
|
|
3587
|
-
const businessId = await
|
|
3734
|
+
const sourceRepo = parseRepoArg2(sourceRepoArg, currentRepo);
|
|
3735
|
+
const targetRepo = options.targetRepo ? parseRepoArg2(options.targetRepo, currentRepo) : null;
|
|
3736
|
+
const businessId = await resolveBusinessId2(config, options);
|
|
3588
3737
|
const ref = options.ref ?? currentRef();
|
|
3589
3738
|
const payload = await apiFetch(
|
|
3590
3739
|
config,
|
|
3591
|
-
`/api/businesses/${
|
|
3740
|
+
`/api/businesses/${encodePathSegment2(businessId)}/repos/${encodePathSegment2(sourceRepo.owner)}/${encodePathSegment2(
|
|
3592
3741
|
sourceRepo.repo
|
|
3593
3742
|
)}/sandbox-layer/build-requests`,
|
|
3594
3743
|
{
|
|
@@ -3618,11 +3767,11 @@ async function sandboxBuildCommand(sourceRepoArg, options = {}, command) {
|
|
|
3618
3767
|
async function sandboxStatusCommand(repoArg, options = {}, command) {
|
|
3619
3768
|
const runtime = getRuntimeOptions(command, options);
|
|
3620
3769
|
const config = requireConfig(runtime);
|
|
3621
|
-
const businessId = await
|
|
3622
|
-
const repo =
|
|
3770
|
+
const businessId = await resolveBusinessId2(config, options);
|
|
3771
|
+
const repo = parseRepoArg2(repoArg, currentRepo);
|
|
3623
3772
|
const payload = await apiFetch(
|
|
3624
3773
|
config,
|
|
3625
|
-
`/api/businesses/${
|
|
3774
|
+
`/api/businesses/${encodePathSegment2(businessId)}/repos/${encodePathSegment2(repo.owner)}/${encodePathSegment2(
|
|
3626
3775
|
repo.repo
|
|
3627
3776
|
)}/sandbox-layer/resolution`
|
|
3628
3777
|
);
|
|
@@ -3635,10 +3784,10 @@ async function sandboxStatusCommand(repoArg, options = {}, command) {
|
|
|
3635
3784
|
async function sandboxHistoryCommand(sourceRepoArg, options = {}, command) {
|
|
3636
3785
|
const runtime = getRuntimeOptions(command, options);
|
|
3637
3786
|
const config = requireConfig(runtime);
|
|
3638
|
-
const businessId = await
|
|
3639
|
-
const sourceRepo =
|
|
3787
|
+
const businessId = await resolveBusinessId2(config, options);
|
|
3788
|
+
const sourceRepo = parseRepoArg2(sourceRepoArg, currentRepo);
|
|
3640
3789
|
const params = new URLSearchParams({ sourceRepo: repoPath(sourceRepo) });
|
|
3641
|
-
if (options.targetRepo) params.set("targetRepo", repoPath(
|
|
3790
|
+
if (options.targetRepo) params.set("targetRepo", repoPath(parseRepoArg2(options.targetRepo, currentRepo)));
|
|
3642
3791
|
if (options.status) params.set("status", options.status);
|
|
3643
3792
|
if (options.limit) {
|
|
3644
3793
|
const limit = Number(options.limit);
|
|
@@ -3649,7 +3798,7 @@ async function sandboxHistoryCommand(sourceRepoArg, options = {}, command) {
|
|
|
3649
3798
|
}
|
|
3650
3799
|
const payload = await apiFetch(
|
|
3651
3800
|
config,
|
|
3652
|
-
`/api/businesses/${
|
|
3801
|
+
`/api/businesses/${encodePathSegment2(businessId)}/sandbox-layer/build-requests?${params.toString()}`
|
|
3653
3802
|
);
|
|
3654
3803
|
if (isJson(command, options)) {
|
|
3655
3804
|
writeJson(payload);
|
|
@@ -3666,7 +3815,7 @@ async function sandboxRebuildStaleCommand(options = {}, command) {
|
|
|
3666
3815
|
if (options.all && options.business) {
|
|
3667
3816
|
throw new CliError("user", "Use either --all or --business, not both.");
|
|
3668
3817
|
}
|
|
3669
|
-
const businessId = options.all ? null : await
|
|
3818
|
+
const businessId = options.all ? null : await resolveBusinessId2(config, options);
|
|
3670
3819
|
let payload = await apiFetch(config, "/api/admin/sandbox-layer/rebuild-campaigns", {
|
|
3671
3820
|
method: "POST",
|
|
3672
3821
|
body: JSON.stringify({
|
|
@@ -3694,7 +3843,7 @@ async function followRebuildCampaign(config, campaignId) {
|
|
|
3694
3843
|
for (; ; ) {
|
|
3695
3844
|
const payload = await apiFetch(
|
|
3696
3845
|
config,
|
|
3697
|
-
`/api/admin/sandbox-layer/rebuild-campaigns/${
|
|
3846
|
+
`/api/admin/sandbox-layer/rebuild-campaigns/${encodePathSegment2(campaignId)}`
|
|
3698
3847
|
);
|
|
3699
3848
|
if (isCampaignTerminal(payload.campaign.status)) return payload;
|
|
3700
3849
|
await sleep2(DEFAULT_POLL_INTERVAL_MS);
|
|
@@ -3723,7 +3872,7 @@ function printRebuildCampaign(payload) {
|
|
|
3723
3872
|
async function sandboxLogsCommand(buildId, options = {}, command) {
|
|
3724
3873
|
const runtime = getRuntimeOptions(command, options);
|
|
3725
3874
|
const config = requireConfig(runtime);
|
|
3726
|
-
const businessId = await
|
|
3875
|
+
const businessId = await resolveBusinessId2(config, options);
|
|
3727
3876
|
let afterSequence = options.afterSequence == null ? void 0 : Number(options.afterSequence);
|
|
3728
3877
|
if (afterSequence != null && (!Number.isInteger(afterSequence) || afterSequence < 0)) {
|
|
3729
3878
|
throw new CliError("user", "--after-sequence must be a non-negative integer.");
|
|
@@ -3747,12 +3896,12 @@ async function sandboxLogsCommand(buildId, options = {}, command) {
|
|
|
3747
3896
|
async function sandboxAssignDefaultCommand(sourceRepoArg, options = {}, command) {
|
|
3748
3897
|
const runtime = getRuntimeOptions(command, options);
|
|
3749
3898
|
const config = requireConfig(runtime);
|
|
3750
|
-
const businessId = await
|
|
3751
|
-
const sourceRepo =
|
|
3899
|
+
const businessId = await resolveBusinessId2(config, options);
|
|
3900
|
+
const sourceRepo = parseRepoArg2(sourceRepoArg, currentRepo);
|
|
3752
3901
|
const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
|
|
3753
3902
|
const payload = await apiFetch(
|
|
3754
3903
|
config,
|
|
3755
|
-
`/api/businesses/${
|
|
3904
|
+
`/api/businesses/${encodePathSegment2(businessId)}/sandbox-layer/default-source`,
|
|
3756
3905
|
{ method: "PUT", body: JSON.stringify(sourceBody(sourceRepo, manifestPath)) }
|
|
3757
3906
|
);
|
|
3758
3907
|
if (isJson(command, options)) {
|
|
@@ -3767,13 +3916,13 @@ async function sandboxAssignDefaultCommand(sourceRepoArg, options = {}, command)
|
|
|
3767
3916
|
async function sandboxAssignRepoCommand(targetRepoArg, sourceRepoArg, options = {}, command) {
|
|
3768
3917
|
const runtime = getRuntimeOptions(command, options);
|
|
3769
3918
|
const config = requireConfig(runtime);
|
|
3770
|
-
const businessId = await
|
|
3771
|
-
const targetRepo =
|
|
3772
|
-
const sourceRepo =
|
|
3919
|
+
const businessId = await resolveBusinessId2(config, options);
|
|
3920
|
+
const targetRepo = parseRepoArg2(targetRepoArg, currentRepo);
|
|
3921
|
+
const sourceRepo = parseRepoArg2(sourceRepoArg, currentRepo);
|
|
3773
3922
|
const manifestPath = options.manifest ?? DEFAULT_MANIFEST_PATH;
|
|
3774
3923
|
const payload = await apiFetch(
|
|
3775
3924
|
config,
|
|
3776
|
-
`/api/businesses/${
|
|
3925
|
+
`/api/businesses/${encodePathSegment2(businessId)}/repos/${encodePathSegment2(targetRepo.owner)}/${encodePathSegment2(
|
|
3777
3926
|
targetRepo.repo
|
|
3778
3927
|
)}/sandbox-layer/assignment`,
|
|
3779
3928
|
{ method: "PUT", body: JSON.stringify(sourceBody(sourceRepo, manifestPath)) }
|
|
@@ -3792,11 +3941,11 @@ async function sandboxAssignRepoCommand(targetRepoArg, sourceRepoArg, options =
|
|
|
3792
3941
|
async function sandboxUnassignDefaultCommand(options = {}, command) {
|
|
3793
3942
|
const runtime = getRuntimeOptions(command, options);
|
|
3794
3943
|
const config = requireConfig(runtime);
|
|
3795
|
-
const businessId = await
|
|
3944
|
+
const businessId = await resolveBusinessId2(config, options);
|
|
3796
3945
|
if (!options.yes) await confirmOrThrow("Clear the business default sandbox layer source?");
|
|
3797
3946
|
const payload = await apiFetch(
|
|
3798
3947
|
config,
|
|
3799
|
-
`/api/businesses/${
|
|
3948
|
+
`/api/businesses/${encodePathSegment2(businessId)}/sandbox-layer/default-source`,
|
|
3800
3949
|
{ method: "DELETE" }
|
|
3801
3950
|
);
|
|
3802
3951
|
if (isJson(command, options)) {
|
|
@@ -3808,12 +3957,12 @@ async function sandboxUnassignDefaultCommand(options = {}, command) {
|
|
|
3808
3957
|
async function sandboxUnassignRepoCommand(targetRepoArg, options = {}, command) {
|
|
3809
3958
|
const runtime = getRuntimeOptions(command, options);
|
|
3810
3959
|
const config = requireConfig(runtime);
|
|
3811
|
-
const businessId = await
|
|
3812
|
-
const targetRepo =
|
|
3960
|
+
const businessId = await resolveBusinessId2(config, options);
|
|
3961
|
+
const targetRepo = parseRepoArg2(targetRepoArg, currentRepo);
|
|
3813
3962
|
if (!options.yes) await confirmOrThrow(`Clear sandbox layer assignment for ${repoPath(targetRepo)}?`);
|
|
3814
3963
|
const payload = await apiFetch(
|
|
3815
3964
|
config,
|
|
3816
|
-
`/api/businesses/${
|
|
3965
|
+
`/api/businesses/${encodePathSegment2(businessId)}/repos/${encodePathSegment2(targetRepo.owner)}/${encodePathSegment2(
|
|
3817
3966
|
targetRepo.repo
|
|
3818
3967
|
)}/sandbox-layer/assignment`,
|
|
3819
3968
|
{ method: "DELETE" }
|
|
@@ -3997,8 +4146,8 @@ function parseStopBlocked(err) {
|
|
|
3997
4146
|
}
|
|
3998
4147
|
|
|
3999
4148
|
// src/commands/test-creds.ts
|
|
4000
|
-
import { readFileSync as
|
|
4001
|
-
function
|
|
4149
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
4150
|
+
function parseRepoArg3(repo) {
|
|
4002
4151
|
const trimmed = repo.trim();
|
|
4003
4152
|
const slashIndex = trimmed.indexOf("/");
|
|
4004
4153
|
if (slashIndex <= 0 || slashIndex === trimmed.length - 1) {
|
|
@@ -4015,13 +4164,13 @@ function readValue(options) {
|
|
|
4015
4164
|
throw new CliError("user", "Provide exactly one of --value, --value-file, or --value-stdin.");
|
|
4016
4165
|
}
|
|
4017
4166
|
if (options.value !== void 0) return options.value;
|
|
4018
|
-
const raw = options.valueFile ?
|
|
4167
|
+
const raw = options.valueFile ? readFileSync4(options.valueFile, "utf8") : readFileSync4(0, "utf8");
|
|
4019
4168
|
return raw.replace(/\r?\n$/, "");
|
|
4020
4169
|
}
|
|
4021
4170
|
async function listTestCredentialsCommand(repo, options, command) {
|
|
4022
4171
|
const runtime = getRuntimeOptions(command, options);
|
|
4023
4172
|
const config = requireConfig(runtime);
|
|
4024
|
-
const repoArg =
|
|
4173
|
+
const repoArg = parseRepoArg3(repo);
|
|
4025
4174
|
const payload = await apiFetch(config, basePath(options.business, repoArg));
|
|
4026
4175
|
if (isJson(command, options)) {
|
|
4027
4176
|
writeJson(payload);
|
|
@@ -4039,7 +4188,7 @@ async function listTestCredentialsCommand(repo, options, command) {
|
|
|
4039
4188
|
async function setTestCredentialCommand(repo, name, options, command) {
|
|
4040
4189
|
const runtime = getRuntimeOptions(command, options);
|
|
4041
4190
|
const config = requireConfig(runtime);
|
|
4042
|
-
const repoArg =
|
|
4191
|
+
const repoArg = parseRepoArg3(repo);
|
|
4043
4192
|
const value = readValue(options);
|
|
4044
4193
|
const payload = await apiFetch(
|
|
4045
4194
|
config,
|
|
@@ -4057,12 +4206,12 @@ async function deleteTestCredentialCommand(repo, name, options, command) {
|
|
|
4057
4206
|
throw new CliError("user", "`test-creds delete --json` requires --yes.");
|
|
4058
4207
|
}
|
|
4059
4208
|
if (options.yes !== true) {
|
|
4060
|
-
const repoArg2 =
|
|
4209
|
+
const repoArg2 = parseRepoArg3(repo);
|
|
4061
4210
|
await confirmOrThrow(`Delete test credential '${name}' for ${repoArg2.owner}/${repoArg2.name}?`);
|
|
4062
4211
|
}
|
|
4063
4212
|
const runtime = getRuntimeOptions(command, options);
|
|
4064
4213
|
const config = requireConfig(runtime);
|
|
4065
|
-
const repoArg =
|
|
4214
|
+
const repoArg = parseRepoArg3(repo);
|
|
4066
4215
|
const payload = await apiFetch(
|
|
4067
4216
|
config,
|
|
4068
4217
|
`${basePath(options.business, repoArg)}/${encodeURIComponent(name)}`,
|
|
@@ -4349,6 +4498,13 @@ sandboxAssign.command("repo").description("Assign a sandbox layer source to one
|
|
|
4349
4498
|
var sandboxUnassign = sandbox.command("unassign").description("Remove sandbox layer source assignments");
|
|
4350
4499
|
sandboxUnassign.command("default").description("Clear the business default sandbox layer source").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--yes", "Skip confirmation").action((options, command) => sandboxUnassignDefaultCommand(options, command));
|
|
4351
4500
|
sandboxUnassign.command("repo").description("Clear a target repo sandbox layer assignment").argument("<target-repo>", "Target repository owner/name").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--yes", "Skip confirmation").action((targetRepo, options, command) => sandboxUnassignRepoCommand(targetRepo, options, command));
|
|
4501
|
+
var egress = program.command("egress").description("Workspace egress allowlist commands");
|
|
4502
|
+
var egressSource = egress.command("source").description("Manage the workspace egress allowlist source repo");
|
|
4503
|
+
egressSource.command("set").description("Set the source repo for .arcanist/egress-allowlist.txt").argument("<source-repo>", "Source repository owner/name").option("--business <id>", "Business ID; defaults to authenticated user's business").action((sourceRepo, options, command) => egressSourceSetCommand(sourceRepo, options, command));
|
|
4504
|
+
egressSource.command("get").description("Show the configured egress allowlist source").option("--business <id>", "Business ID; defaults to authenticated user's business").action((options, command) => egressSourceGetCommand(options, command));
|
|
4505
|
+
egress.command("add").description("Open or update a PR adding domains to the egress allowlist source file").argument("<domains...>", "Exact domain names to add").option("--business <id>", "Business ID; defaults to authenticated user's business").option("--reason <text>", "Reason to include in the PR body").action((domains, options, command) => egressAddCommand(domains, options, command));
|
|
4506
|
+
egress.command("sync").description("Apply the merged egress allowlist source file to the runtime business policy").option("--business <id>", "Business ID; defaults to authenticated user's business").action((options, command) => egressSyncCommand(options, command));
|
|
4507
|
+
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));
|
|
4352
4508
|
var tokens = program.command("tokens").description("CLI token commands");
|
|
4353
4509
|
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));
|
|
4354
4510
|
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(
|