@01.works/visual-review 0.14.0 → 0.15.0
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/active-review-mount-BpeCsPqV.js +1941 -0
- package/dist/cli.js +350 -61
- package/dist/{convex-runtime-Bkk8o32N.js → convex-runtime-D1aPBck9.js} +2 -2
- package/dist/{cursor-chat-repository-B2hY4vVl.js → cursor-chat-repository-CK86F1lv.js} +1 -1
- package/dist/{hosted-runtime-BrJ3brgj.js → hosted-runtime-B7YTLu3G.js} +2 -2
- package/dist/index.d.ts +95 -1
- package/dist/index.js +1 -1
- package/dist/install-CY72L_bY.js +2 -0
- package/dist/mcp.js +9 -9
- package/dist/react.js +1 -1
- package/dist/review-repository-CAu78oOD.js +1 -0
- package/package.json +1 -1
- package/dist/active-review-mount-DZ10FauJ.js +0 -1516
- package/dist/install-DAqwiYRg.js +0 -2
- package/dist/review-repository-DJTt7aZR.js +0 -1
package/dist/cli.js
CHANGED
|
@@ -139,6 +139,7 @@ function sanitizePayload(input, caps, forceTruncated = false) {
|
|
|
139
139
|
const region = record(target.region);
|
|
140
140
|
const inheritedTrust = record(root.trust);
|
|
141
141
|
const state = {
|
|
142
|
+
redacted: inheritedTrust.redacted === true,
|
|
142
143
|
truncated: forceTruncated || inheritedTrust.truncated === true
|
|
143
144
|
};
|
|
144
145
|
const source = sanitizeSourceLocation(root.source, caps, state);
|
|
@@ -152,7 +153,7 @@ function sanitizePayload(input, caps, forceTruncated = false) {
|
|
|
152
153
|
const firstElement = elements[0];
|
|
153
154
|
const sanitized = {
|
|
154
155
|
schemaVersion: 3,
|
|
155
|
-
trust: trustMetadata(false),
|
|
156
|
+
trust: trustMetadata(false, false),
|
|
156
157
|
feedback: {
|
|
157
158
|
id: sanitizeText(feedback.id, caps.id, state),
|
|
158
159
|
status: sanitizeText(feedback.status, caps.status, state),
|
|
@@ -199,7 +200,7 @@ function sanitizePayload(input, caps, forceTruncated = false) {
|
|
|
199
200
|
if (!(root.stale === null || typeof root.stale === "boolean")) {
|
|
200
201
|
state.truncated = true;
|
|
201
202
|
}
|
|
202
|
-
sanitized.trust = trustMetadata(state.truncated);
|
|
203
|
+
sanitized.trust = trustMetadata(state.redacted, state.truncated);
|
|
203
204
|
return sanitized;
|
|
204
205
|
}
|
|
205
206
|
function sanitizeTargetElements(input, caps, state) {
|
|
@@ -338,14 +339,64 @@ function sanitizePageUrl(value, limit, state) {
|
|
|
338
339
|
if (parsed.username || parsed.password) {
|
|
339
340
|
parsed.username = "";
|
|
340
341
|
parsed.password = "";
|
|
341
|
-
state.
|
|
342
|
+
state.redacted = true;
|
|
342
343
|
}
|
|
344
|
+
if (redactSensitiveParameters(parsed.searchParams)) state.redacted = true;
|
|
345
|
+
if (parsed.hash && redactSensitiveFragment(parsed)) state.redacted = true;
|
|
343
346
|
return sanitizeText(parsed.toString(), limit, state);
|
|
344
347
|
} catch {
|
|
345
348
|
state.truncated = true;
|
|
346
349
|
return "unavailable";
|
|
347
350
|
}
|
|
348
351
|
}
|
|
352
|
+
var sensitiveUrlParameterParts = /* @__PURE__ */ new Set([
|
|
353
|
+
"auth",
|
|
354
|
+
"authorization",
|
|
355
|
+
"code",
|
|
356
|
+
"credential",
|
|
357
|
+
"invite",
|
|
358
|
+
"key",
|
|
359
|
+
"password",
|
|
360
|
+
"secret",
|
|
361
|
+
"session",
|
|
362
|
+
"sig",
|
|
363
|
+
"signature",
|
|
364
|
+
"token"
|
|
365
|
+
]);
|
|
366
|
+
function isSensitiveUrlParameter(key) {
|
|
367
|
+
const normalized = key.toLowerCase();
|
|
368
|
+
if (normalized === "visual-review") return true;
|
|
369
|
+
const parts = normalized.split(/[-_.]/u);
|
|
370
|
+
return parts.some((part) => sensitiveUrlParameterParts.has(part)) || [...sensitiveUrlParameterParts].some((part) => normalized.endsWith(part));
|
|
371
|
+
}
|
|
372
|
+
function redactSensitiveParameters(parameters) {
|
|
373
|
+
const sensitiveKeys = new Set(
|
|
374
|
+
[...parameters.keys()].filter((key) => isSensitiveUrlParameter(key))
|
|
375
|
+
);
|
|
376
|
+
for (const key of sensitiveKeys) parameters.set(key, "REDACTED");
|
|
377
|
+
return sensitiveKeys.size > 0;
|
|
378
|
+
}
|
|
379
|
+
function redactSensitiveFragment(url) {
|
|
380
|
+
const fragment = url.hash.slice(1);
|
|
381
|
+
const route = /^(?:!\/|\/)/u.test(fragment);
|
|
382
|
+
const queryIndex = fragment.indexOf("?");
|
|
383
|
+
if (route && queryIndex >= 0) {
|
|
384
|
+
const parameters = new URLSearchParams(fragment.slice(queryIndex + 1));
|
|
385
|
+
if (!redactSensitiveParameters(parameters)) return false;
|
|
386
|
+
url.hash = `#${fragment.slice(0, queryIndex)}?${parameters.toString()}`;
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
if (route) return false;
|
|
390
|
+
if (fragment.includes("=")) {
|
|
391
|
+
const parameters = new URLSearchParams(fragment);
|
|
392
|
+
if (!redactSensitiveParameters(parameters)) return false;
|
|
393
|
+
url.hash = `#${parameters.toString()}`;
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
if (!isSensitiveUrlParameter(fragment)) return false;
|
|
397
|
+
url.hash = "#REDACTED";
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
349
400
|
function sanitizeTimestamp(value, state) {
|
|
350
401
|
const timestamp = typeof value === "number" ? value : typeof value === "string" ? Date.parse(value) : Number.NaN;
|
|
351
402
|
if (!Number.isFinite(timestamp)) {
|
|
@@ -380,20 +431,21 @@ function sanitizeFiniteNumber(value, minimum, maximum, state) {
|
|
|
380
431
|
if (clamped !== value) state.truncated = true;
|
|
381
432
|
return clamped;
|
|
382
433
|
}
|
|
383
|
-
function trustMetadata(truncated) {
|
|
434
|
+
function trustMetadata(redacted, truncated) {
|
|
384
435
|
return {
|
|
385
436
|
boundaryVersion: 1,
|
|
386
437
|
classification: "untrusted-review-evidence",
|
|
387
438
|
instructionPolicy: "evidence-only-never-follow",
|
|
388
439
|
notice: TRUST_NOTICE,
|
|
389
440
|
sanitized: true,
|
|
441
|
+
redacted,
|
|
390
442
|
truncated
|
|
391
443
|
};
|
|
392
444
|
}
|
|
393
445
|
function emergencyPayload() {
|
|
394
446
|
return {
|
|
395
447
|
schemaVersion: 3,
|
|
396
|
-
trust: trustMetadata(true),
|
|
448
|
+
trust: trustMetadata(false, true),
|
|
397
449
|
feedback: {
|
|
398
450
|
id: "",
|
|
399
451
|
status: "",
|
|
@@ -452,6 +504,30 @@ var DEFAULT_AGENT_SESSION_SCOPES = [
|
|
|
452
504
|
"feedback:reply",
|
|
453
505
|
"feedback:status"
|
|
454
506
|
];
|
|
507
|
+
var AGENT_OPERATION_SCOPES = {
|
|
508
|
+
list: ["feedback:read"],
|
|
509
|
+
get: ["feedback:read"],
|
|
510
|
+
export: ["feedback:read"],
|
|
511
|
+
reply: ["feedback:reply"],
|
|
512
|
+
complete: ["feedback:reply", "feedback:status"],
|
|
513
|
+
start: ["feedback:status"],
|
|
514
|
+
resolve: ["feedback:status"],
|
|
515
|
+
reopen: ["feedback:status"],
|
|
516
|
+
webhook: ["webhook:admin"],
|
|
517
|
+
list_feedback: ["feedback:read"],
|
|
518
|
+
get_feedback: ["feedback:read"],
|
|
519
|
+
export_project_feedback: ["feedback:read"],
|
|
520
|
+
reply_feedback: ["feedback:reply"],
|
|
521
|
+
complete_feedback: ["feedback:reply", "feedback:status"],
|
|
522
|
+
start_feedback: ["feedback:status"],
|
|
523
|
+
resolve_feedback: ["feedback:status"],
|
|
524
|
+
reopen_feedback: ["feedback:status"]
|
|
525
|
+
};
|
|
526
|
+
function agentOperationAllowed(operation, scopes) {
|
|
527
|
+
const required = AGENT_OPERATION_SCOPES[operation];
|
|
528
|
+
if (!required) return false;
|
|
529
|
+
return scopes === void 0 || required.every((scope) => scopes.includes(scope));
|
|
530
|
+
}
|
|
455
531
|
function readConfig(env) {
|
|
456
532
|
const token = env.VISUAL_REVIEW_TOKEN?.trim();
|
|
457
533
|
if (!token) {
|
|
@@ -623,7 +699,7 @@ var VisualReviewClient = class {
|
|
|
623
699
|
}
|
|
624
700
|
const row = session;
|
|
625
701
|
const allowedScopes = new Set(AGENT_SESSION_SCOPES);
|
|
626
|
-
if (row.projectId !== projectId || !Number.isSafeInteger(row.expiresAt) || row.expiresAt < 0 || !Array.isArray(row.scopes) || row.scopes.length === 0 || row.scopes.some((scope) => typeof scope !== "string" || !allowedScopes.has(scope)) || new Set(row.scopes).size !== row.scopes.length) malformedSessionStatus();
|
|
702
|
+
if (row.projectId !== projectId || typeof row.displayName !== "string" || !row.displayName.trim() || row.displayName.length > 80 || !Number.isSafeInteger(row.expiresAt) || row.expiresAt < 0 || !Array.isArray(row.scopes) || row.scopes.length === 0 || row.scopes.some((scope) => typeof scope !== "string" || !allowedScopes.has(scope)) || new Set(row.scopes).size !== row.scopes.length) malformedSessionStatus();
|
|
627
703
|
return row;
|
|
628
704
|
}
|
|
629
705
|
async #request(path, init) {
|
|
@@ -732,7 +808,7 @@ function assertSecureServiceUrl(value) {
|
|
|
732
808
|
|
|
733
809
|
// ../review-agent/src/lifecycle.ts
|
|
734
810
|
import { randomUUID } from "node:crypto";
|
|
735
|
-
import { execFileSync } from "node:child_process";
|
|
811
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
736
812
|
import {
|
|
737
813
|
appendFileSync,
|
|
738
814
|
chmodSync,
|
|
@@ -775,6 +851,7 @@ async function statusCli(options) {
|
|
|
775
851
|
projectId: options.env.VISUAL_REVIEW_PROJECT_ID?.trim() || null,
|
|
776
852
|
expiresAt: null,
|
|
777
853
|
scopes: null,
|
|
854
|
+
displayName: null,
|
|
778
855
|
configSource
|
|
779
856
|
};
|
|
780
857
|
}
|
|
@@ -786,6 +863,7 @@ async function statusCli(options) {
|
|
|
786
863
|
let connectionReason = "expired";
|
|
787
864
|
let remoteExpiresAt;
|
|
788
865
|
let remoteScopes;
|
|
866
|
+
let remoteDisplayName;
|
|
789
867
|
const now = options.now?.() ?? Date.now();
|
|
790
868
|
if (state.expiresAt === void 0 || state.expiresAt > now) {
|
|
791
869
|
try {
|
|
@@ -798,6 +876,7 @@ async function statusCli(options) {
|
|
|
798
876
|
connectionReason = connected ? "connected" : "expired";
|
|
799
877
|
remoteExpiresAt = session.expiresAt;
|
|
800
878
|
remoteScopes = session.scopes;
|
|
879
|
+
remoteDisplayName = session.displayName;
|
|
801
880
|
} catch (cause) {
|
|
802
881
|
connectionReason = statusConnectionFailureReason(cause);
|
|
803
882
|
}
|
|
@@ -810,6 +889,7 @@ async function statusCli(options) {
|
|
|
810
889
|
projectId: state.config.projectId,
|
|
811
890
|
expiresAt: remoteExpiresAt ?? state.expiresAt ?? null,
|
|
812
891
|
scopes: remoteScopes ?? state.scopes ?? null,
|
|
892
|
+
displayName: remoteDisplayName ?? state.config.displayName ?? null,
|
|
813
893
|
configSource
|
|
814
894
|
};
|
|
815
895
|
}
|
|
@@ -828,15 +908,58 @@ async function configureCli(command, options) {
|
|
|
828
908
|
);
|
|
829
909
|
}
|
|
830
910
|
const serviceUrl = secureServiceUrl(command.serviceUrl ?? DEFAULT_SERVICE_URL);
|
|
831
|
-
const prompt = options.prompt ?? defaultPrompt;
|
|
832
|
-
const email = ownerEmail(
|
|
833
|
-
command.email ?? options.env.VISUAL_REVIEW_OWNER_EMAIL ?? await prompt("Owner \uC774\uBA54\uC77C: ", false)
|
|
834
|
-
);
|
|
835
911
|
const request = requestJson(options.fetch ?? globalThis.fetch);
|
|
836
912
|
const runtime = await request(`${serviceUrl}/v1/agency/runtime`, { method: "GET" });
|
|
837
913
|
if (runtime.provider !== "convex") {
|
|
838
914
|
throw new Error("\uC120\uD0DD\uD55C \uC11C\uBE44\uC2A4\uB294 \uACF5\uAC1C Visual Review CLI\uB97C \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
839
915
|
}
|
|
916
|
+
const repository = resolve(options.cwd, command.repository ?? ".");
|
|
917
|
+
if (!existsSync(repository) || !statSync(repository).isDirectory()) {
|
|
918
|
+
throw new Error(`\uCF54\uB4DC \uC800\uC7A5\uC18C \uACBD\uB85C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: ${repository}`);
|
|
919
|
+
}
|
|
920
|
+
const displayName = normalizeAgentDisplayName(command.displayName ?? "Agent");
|
|
921
|
+
const requestedScopes = requestedAgentScopes(command.webhookAdmin, command.readOnly === true);
|
|
922
|
+
const terminalAuthentication = command.noBrowser || command.list || command.email !== void 0 || Boolean(options.env.VISUAL_REVIEW_OWNER_EMAIL?.trim()) || Boolean(options.env.VISUAL_REVIEW_AUTH_CODE?.trim());
|
|
923
|
+
if (command.list) {
|
|
924
|
+
return { projects: (await authenticateWithEmail(command, options, serviceUrl, request)).projects };
|
|
925
|
+
}
|
|
926
|
+
const authenticated = terminalAuthentication ? await createSessionWithEmail(command, options, serviceUrl, request, requestedScopes, displayName) : await createSessionWithBrowser(command, options, serviceUrl, request, requestedScopes, displayName);
|
|
927
|
+
const { project, session } = authenticated;
|
|
928
|
+
if (typeof session.token !== "string" || session.projectId !== project.id || !Number.isSafeInteger(session.expiresAt) || session.expiresAt <= (options.now?.() ?? Date.now())) {
|
|
929
|
+
throw new Error("\uD504\uB85C\uC81D\uD2B8 CLI session \uC751\uB2F5\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
930
|
+
}
|
|
931
|
+
const scopes = session.scopes === void 0 ? requestedScopes : agentSessionScopes(session.scopes, "\uD504\uB85C\uC81D\uD2B8 CLI session");
|
|
932
|
+
if (scopes.length !== requestedScopes.length || scopes.some((scope) => !requestedScopes.includes(scope))) {
|
|
933
|
+
throw new Error("\uD504\uB85C\uC81D\uD2B8 CLI session scope\uAC00 \uC694\uCCAD\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
934
|
+
}
|
|
935
|
+
if (normalizeAgentDisplayName(session.displayName) !== displayName) {
|
|
936
|
+
throw new Error("\uD504\uB85C\uC81D\uD2B8 CLI session displayName\uC774 \uC694\uCCAD\uACFC \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
937
|
+
}
|
|
938
|
+
const configPath = resolve(options.cwd, command.output ?? resolve(repository, ".visual-review.json"));
|
|
939
|
+
ensureCredentialIgnored(repository, configPath);
|
|
940
|
+
writePrivateFileAtomically(configPath, `${JSON.stringify({
|
|
941
|
+
serviceUrl,
|
|
942
|
+
token: session.token,
|
|
943
|
+
projectId: project.id,
|
|
944
|
+
displayName,
|
|
945
|
+
expiresAt: session.expiresAt,
|
|
946
|
+
scopes
|
|
947
|
+
}, null, 2)}
|
|
948
|
+
`);
|
|
949
|
+
return {
|
|
950
|
+
configured: true,
|
|
951
|
+
project,
|
|
952
|
+
expiresAt: session.expiresAt,
|
|
953
|
+
scopes,
|
|
954
|
+
displayName,
|
|
955
|
+
configPath
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
async function authenticateWithEmail(command, options, serviceUrl, request) {
|
|
959
|
+
const prompt = options.prompt ?? defaultPrompt;
|
|
960
|
+
const email = ownerEmail(
|
|
961
|
+
command.email ?? options.env.VISUAL_REVIEW_OWNER_EMAIL ?? await prompt("Owner \uC774\uBA54\uC77C: ", false)
|
|
962
|
+
);
|
|
840
963
|
const challenge = await request(`${serviceUrl}/v1/agency/auth/code`, {
|
|
841
964
|
method: "POST",
|
|
842
965
|
body: JSON.stringify({ email, intent: "sign-in" })
|
|
@@ -854,49 +977,88 @@ async function configureCli(command, options) {
|
|
|
854
977
|
method: "GET",
|
|
855
978
|
headers: { authorization: `Bearer ${verified.token}` }
|
|
856
979
|
});
|
|
857
|
-
|
|
858
|
-
|
|
980
|
+
return { projects: projectList(catalog.projects), ownerToken: verified.token };
|
|
981
|
+
}
|
|
982
|
+
async function createSessionWithEmail(command, options, serviceUrl, request, scopes, displayName) {
|
|
983
|
+
const { projects, ownerToken } = await authenticateWithEmail(
|
|
984
|
+
command,
|
|
985
|
+
options,
|
|
986
|
+
serviceUrl,
|
|
987
|
+
request
|
|
988
|
+
);
|
|
859
989
|
const project = command.projectId ? projects.find(({ id }) => id === command.projectId) : projects.length === 1 ? projects[0] : void 0;
|
|
860
990
|
if (!project) {
|
|
861
991
|
throw new Error(projects.length === 1 ? `\uD504\uB85C\uC81D\uD2B8 ${command.projectId}\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.` : "\uD504\uB85C\uC81D\uD2B8\uAC00 \uC5EC\uB7EC \uAC1C\uC785\uB2C8\uB2E4. configure --list\uB85C \uD655\uC778\uD558\uACE0 --project <id>\uB97C \uC9C0\uC815\uD558\uC138\uC694.");
|
|
862
992
|
}
|
|
863
|
-
const requestedScopes = requestedAgentScopes(command.webhookAdmin);
|
|
864
993
|
const session = await request(`${serviceUrl}/v1/agency/agent-sessions`, {
|
|
865
994
|
method: "POST",
|
|
866
|
-
headers: { authorization: `Bearer ${
|
|
995
|
+
headers: { authorization: `Bearer ${ownerToken}` },
|
|
996
|
+
body: JSON.stringify({ projectId: project.id, displayName, scopes })
|
|
997
|
+
});
|
|
998
|
+
return { project, session };
|
|
999
|
+
}
|
|
1000
|
+
async function createSessionWithBrowser(command, options, serviceUrl, request, scopes, displayName) {
|
|
1001
|
+
const started = await request(`${serviceUrl}/v1/agency/cli-authorizations`, {
|
|
1002
|
+
method: "POST",
|
|
867
1003
|
body: JSON.stringify({
|
|
868
|
-
|
|
869
|
-
|
|
1004
|
+
scopes,
|
|
1005
|
+
displayName,
|
|
1006
|
+
...command.projectId === void 0 ? {} : { projectId: command.projectId }
|
|
870
1007
|
})
|
|
871
1008
|
});
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
}
|
|
879
|
-
const repository = resolve(options.cwd, command.repository ?? ".");
|
|
880
|
-
if (!existsSync(repository) || !statSync(repository).isDirectory()) {
|
|
881
|
-
throw new Error(`\uCF54\uB4DC \uC800\uC7A5\uC18C \uACBD\uB85C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: ${repository}`);
|
|
882
|
-
}
|
|
883
|
-
const configPath = resolve(options.cwd, command.output ?? resolve(repository, ".visual-review.json"));
|
|
884
|
-
ensureCredentialIgnored(repository, configPath);
|
|
885
|
-
writePrivateFileAtomically(configPath, `${JSON.stringify({
|
|
1009
|
+
const requestId = authorizationId(started.requestId);
|
|
1010
|
+
const requestToken = authorizationToken(started.requestToken);
|
|
1011
|
+
const verificationCode2 = browserVerificationCode(started.verificationCode);
|
|
1012
|
+
const expiresAt = authorizationExpiry(started.expiresAt, options.now?.() ?? Date.now());
|
|
1013
|
+
const authorizationUrl = browserAuthorizationUrl(
|
|
1014
|
+
started.verificationUri,
|
|
886
1015
|
serviceUrl,
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1016
|
+
requestId
|
|
1017
|
+
);
|
|
1018
|
+
options.warning?.(`\uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C Visual Review \uB85C\uADF8\uC778\uC744 \uC2B9\uC778\uD558\uC138\uC694: ${authorizationUrl}`);
|
|
1019
|
+
options.warning?.(`\uD655\uC778 \uCF54\uB4DC: ${verificationCode2}`);
|
|
1020
|
+
let opened = false;
|
|
1021
|
+
try {
|
|
1022
|
+
opened = await (options.openBrowser ?? defaultOpenBrowser)(authorizationUrl);
|
|
1023
|
+
} catch {
|
|
1024
|
+
opened = false;
|
|
1025
|
+
}
|
|
1026
|
+
if (!opened) options.warning?.("\uBE0C\uB77C\uC6B0\uC800\uB97C \uC790\uB3D9\uC73C\uB85C \uC5F4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC704 URL\uC744 \uC9C1\uC811 \uC5EC\uC138\uC694.");
|
|
1027
|
+
const wait = options.wait ?? ((milliseconds) => new Promise((resolveWait) => {
|
|
1028
|
+
setTimeout(resolveWait, milliseconds);
|
|
1029
|
+
}));
|
|
1030
|
+
const maximumAttempts = Math.max(1, Math.ceil((expiresAt - (options.now?.() ?? Date.now())) / 2e3));
|
|
1031
|
+
for (let attempt = 0; attempt < maximumAttempts; attempt += 1) {
|
|
1032
|
+
const exchanged = await request(
|
|
1033
|
+
`${serviceUrl}/v1/agency/cli-authorizations/${encodeURIComponent(requestId)}/token`,
|
|
1034
|
+
{
|
|
1035
|
+
method: "POST",
|
|
1036
|
+
body: JSON.stringify({ requestToken })
|
|
1037
|
+
}
|
|
1038
|
+
);
|
|
1039
|
+
if (exchanged.status !== "pending") {
|
|
1040
|
+
const token = authorizationToken(exchanged.token);
|
|
1041
|
+
const projectId = authorizationId(exchanged.projectId);
|
|
1042
|
+
const projectName = authorizationProjectName(exchanged.projectName);
|
|
1043
|
+
const sessionScopes = agentSessionScopes(exchanged.scopes, "\uD504\uB85C\uC81D\uD2B8 CLI session");
|
|
1044
|
+
const sessionExpiresAt = authorizationExpiry(
|
|
1045
|
+
exchanged.expiresAt,
|
|
1046
|
+
options.now?.() ?? Date.now()
|
|
1047
|
+
);
|
|
1048
|
+
return {
|
|
1049
|
+
project: { id: projectId, name: projectName },
|
|
1050
|
+
session: {
|
|
1051
|
+
token,
|
|
1052
|
+
projectId,
|
|
1053
|
+
displayName: normalizeAgentDisplayName(exchanged.displayName),
|
|
1054
|
+
scopes: sessionScopes,
|
|
1055
|
+
expiresAt: sessionExpiresAt
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
await wait(2e3);
|
|
1060
|
+
}
|
|
1061
|
+
throw new Error("\uBE0C\uB77C\uC6B0\uC800 \uB85C\uADF8\uC778 \uC694\uCCAD\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. configure\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
|
|
900
1062
|
}
|
|
901
1063
|
async function logoutCli(command, options) {
|
|
902
1064
|
const state = readCliConfigState(options.env, options.cwd, {
|
|
@@ -956,10 +1118,15 @@ function readCliConfigState(env, cwd, options) {
|
|
|
956
1118
|
VISUAL_REVIEW_PROJECT_ID: env.VISUAL_REVIEW_PROJECT_ID ?? stringValue(fileConfig.projectId),
|
|
957
1119
|
VISUAL_REVIEW_SERVICE_URL: env.VISUAL_REVIEW_SERVICE_URL ?? stringValue(fileConfig.serviceUrl)
|
|
958
1120
|
});
|
|
1121
|
+
const displayName = fileConfig.displayName === void 0 ? void 0 : normalizeAgentDisplayName(fileConfig.displayName);
|
|
959
1122
|
const expiresAt = usesFileToken && Number.isSafeInteger(fileConfig.expiresAt) ? fileConfig.expiresAt : void 0;
|
|
960
1123
|
const scopes = usesFileToken && fileConfig.scopes !== void 0 ? agentSessionScopes(fileConfig.scopes, configPath) : void 0;
|
|
961
1124
|
return {
|
|
962
|
-
config
|
|
1125
|
+
config: {
|
|
1126
|
+
...config,
|
|
1127
|
+
...displayName === void 0 ? {} : { displayName },
|
|
1128
|
+
...scopes === void 0 ? {} : { scopes }
|
|
1129
|
+
},
|
|
963
1130
|
...expiresAt === void 0 ? {} : { expiresAt },
|
|
964
1131
|
...scopes === void 0 ? {} : { scopes },
|
|
965
1132
|
configPath,
|
|
@@ -967,12 +1134,24 @@ function readCliConfigState(env, cwd, options) {
|
|
|
967
1134
|
usesFileToken
|
|
968
1135
|
};
|
|
969
1136
|
}
|
|
970
|
-
function requestedAgentScopes(webhookAdmin) {
|
|
1137
|
+
function requestedAgentScopes(webhookAdmin, readOnly) {
|
|
1138
|
+
if (readOnly) return ["feedback:read"];
|
|
971
1139
|
return [
|
|
972
1140
|
...DEFAULT_AGENT_SESSION_SCOPES,
|
|
973
1141
|
...webhookAdmin ? ["webhook:admin"] : []
|
|
974
1142
|
];
|
|
975
1143
|
}
|
|
1144
|
+
function normalizeAgentDisplayName(value) {
|
|
1145
|
+
if (typeof value !== "string") throw new Error("agent displayName \uAC12\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
1146
|
+
if (/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/u.test(value)) {
|
|
1147
|
+
throw new Error("agent displayName \uAC12\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
1148
|
+
}
|
|
1149
|
+
const normalized = value.normalize("NFC").trim().replace(/\s+/gu, " ");
|
|
1150
|
+
if (!normalized || normalized.length > 80) {
|
|
1151
|
+
throw new Error("agent displayName\uC740 1\uC790 \uC774\uC0C1 80\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
1152
|
+
}
|
|
1153
|
+
return normalized;
|
|
1154
|
+
}
|
|
976
1155
|
function agentSessionScopes(value, context) {
|
|
977
1156
|
if (!Array.isArray(value) || value.length === 0) {
|
|
978
1157
|
throw new Error(`${context} scopes \uAC12\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.`);
|
|
@@ -985,7 +1164,15 @@ function agentSessionScopes(value, context) {
|
|
|
985
1164
|
}
|
|
986
1165
|
scopes.push(scope);
|
|
987
1166
|
}
|
|
988
|
-
|
|
1167
|
+
const requested = new Set(scopes);
|
|
1168
|
+
const readOnly = requested.size === 1 && requested.has("feedback:read");
|
|
1169
|
+
const defaultAccess = DEFAULT_AGENT_SESSION_SCOPES.every((scope) => requested.has(scope));
|
|
1170
|
+
const defaultProfile = defaultAccess && requested.size === DEFAULT_AGENT_SESSION_SCOPES.length;
|
|
1171
|
+
const webhookProfile = defaultAccess && requested.has("webhook:admin") && requested.size === DEFAULT_AGENT_SESSION_SCOPES.length + 1;
|
|
1172
|
+
if (!readOnly && !defaultProfile && !webhookProfile) {
|
|
1173
|
+
throw new Error(`${context} scopes\uB294 read-only, default \uB610\uB294 default+webhook \uD504\uB85C\uD544\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.`);
|
|
1174
|
+
}
|
|
1175
|
+
return readOnly ? ["feedback:read"] : [...DEFAULT_AGENT_SESSION_SCOPES, ...webhookProfile ? ["webhook:admin"] : []];
|
|
989
1176
|
}
|
|
990
1177
|
function requestJson(fetcher) {
|
|
991
1178
|
return async (url, init) => {
|
|
@@ -1242,6 +1429,76 @@ function verificationCode(value) {
|
|
|
1242
1429
|
if (!/^\d{6}$/u.test(normalized)) throw new Error("6\uC790\uB9AC \uC774\uBA54\uC77C \uC778\uC99D \uCF54\uB4DC\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.");
|
|
1243
1430
|
return normalized;
|
|
1244
1431
|
}
|
|
1432
|
+
function authorizationId(value) {
|
|
1433
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
1434
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u.test(normalized)) {
|
|
1435
|
+
throw new Error("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D \uC751\uB2F5\uC758 ID\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
1436
|
+
}
|
|
1437
|
+
return normalized;
|
|
1438
|
+
}
|
|
1439
|
+
function authorizationToken(value) {
|
|
1440
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
1441
|
+
if (!/^[A-Za-z0-9_-]{43}$/u.test(normalized)) {
|
|
1442
|
+
throw new Error("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D \uC751\uB2F5\uC758 token\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
1443
|
+
}
|
|
1444
|
+
return normalized;
|
|
1445
|
+
}
|
|
1446
|
+
function browserVerificationCode(value) {
|
|
1447
|
+
const normalized = typeof value === "string" ? value.trim().toUpperCase() : "";
|
|
1448
|
+
if (!/^[A-HJ-NP-Z2-9]{4}-[A-HJ-NP-Z2-9]{4}$/u.test(normalized)) {
|
|
1449
|
+
throw new Error("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D \uD655\uC778 \uCF54\uB4DC\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
1450
|
+
}
|
|
1451
|
+
return normalized;
|
|
1452
|
+
}
|
|
1453
|
+
function authorizationExpiry(value, now) {
|
|
1454
|
+
if (!Number.isSafeInteger(value) || value <= now) {
|
|
1455
|
+
throw new Error("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D \uC694\uCCAD\uC758 \uB9CC\uB8CC \uC2DC\uAC04\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
1456
|
+
}
|
|
1457
|
+
return value;
|
|
1458
|
+
}
|
|
1459
|
+
function authorizationProjectName(value) {
|
|
1460
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
1461
|
+
if (!normalized || normalized.length > 200) {
|
|
1462
|
+
throw new Error("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D \uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
1463
|
+
}
|
|
1464
|
+
return normalized;
|
|
1465
|
+
}
|
|
1466
|
+
function browserAuthorizationUrl(value, serviceUrl, requestId) {
|
|
1467
|
+
const fallback = `${serviceUrl}/cli/authorize?request=${encodeURIComponent(requestId)}`;
|
|
1468
|
+
if (value === void 0) return fallback;
|
|
1469
|
+
if (typeof value !== "string" || value.length > 2048) {
|
|
1470
|
+
throw new Error("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D URL\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
1471
|
+
}
|
|
1472
|
+
try {
|
|
1473
|
+
const url = new URL(value);
|
|
1474
|
+
const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
1475
|
+
if (url.username || url.password || url.hash || url.pathname !== "/cli/authorize" || url.searchParams.size !== 1 || url.searchParams.get("request") !== requestId || url.protocol !== "https:" && !(url.protocol === "http:" && loopback)) throw new Error();
|
|
1476
|
+
return url.href;
|
|
1477
|
+
} catch {
|
|
1478
|
+
throw new Error("\uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D URL\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
async function defaultOpenBrowser(url) {
|
|
1482
|
+
const command = process.platform === "darwin" ? { file: "open", args: [url] } : process.platform === "win32" ? { file: "cmd", args: ["/c", "start", "", url] } : { file: "xdg-open", args: [url] };
|
|
1483
|
+
return new Promise((resolveOpen) => {
|
|
1484
|
+
let settled = false;
|
|
1485
|
+
const child = spawn(command.file, command.args, {
|
|
1486
|
+
detached: true,
|
|
1487
|
+
stdio: "ignore",
|
|
1488
|
+
windowsHide: true
|
|
1489
|
+
});
|
|
1490
|
+
const finish = (opened) => {
|
|
1491
|
+
if (settled) return;
|
|
1492
|
+
settled = true;
|
|
1493
|
+
resolveOpen(opened);
|
|
1494
|
+
};
|
|
1495
|
+
child.once("error", () => finish(false));
|
|
1496
|
+
child.once("spawn", () => {
|
|
1497
|
+
child.unref();
|
|
1498
|
+
finish(true);
|
|
1499
|
+
});
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1245
1502
|
function secureServiceUrl(value) {
|
|
1246
1503
|
const normalized = value.trim().replace(/\/+$/u, "");
|
|
1247
1504
|
try {
|
|
@@ -1341,11 +1598,11 @@ function summarize(payload) {
|
|
|
1341
1598
|
var CLI_HELP = `Visual Review CLI
|
|
1342
1599
|
|
|
1343
1600
|
Usage:
|
|
1344
|
-
visual-review configure [--email <email>] [--project <id>] [--list]
|
|
1601
|
+
visual-review configure [--email <email>] [--project <id>] [--list] [--no-browser]
|
|
1345
1602
|
[--service-url <url>] [--repo <path> | --output <path>]
|
|
1346
|
-
[--webhook-admin]
|
|
1603
|
+
[--name <display-name>] [--read-only | --webhook-admin]
|
|
1347
1604
|
visual-review status
|
|
1348
|
-
visual-review list [--status open|resolved|all] [--limit 1..100] [--cursor <cursor>]
|
|
1605
|
+
visual-review list [--status open|in_progress|resolved|all] [--limit 1..100] [--cursor <cursor>]
|
|
1349
1606
|
visual-review get <comment-id>
|
|
1350
1607
|
visual-review export [--format json|markdown] [--output <new-file>]
|
|
1351
1608
|
visual-review reply <comment-id> (--body <text> | --body-file <path>)
|
|
@@ -1358,6 +1615,8 @@ Usage:
|
|
|
1358
1615
|
--expected-thread-revision <revision>]
|
|
1359
1616
|
visual-review reopen <comment-id> [--expected-workflow-revision <revision>
|
|
1360
1617
|
--expected-thread-revision <revision>]
|
|
1618
|
+
visual-review start <comment-id> [--expected-workflow-revision <revision>
|
|
1619
|
+
--expected-thread-revision <revision>]
|
|
1361
1620
|
visual-review webhook get|delete
|
|
1362
1621
|
visual-review webhook set --url <https-url>
|
|
1363
1622
|
(--secret <32..256 chars> | --secret-file <path>) [--inactive]
|
|
@@ -1376,16 +1635,18 @@ Configuration:
|
|
|
1376
1635
|
Run visual-review help <command> or visual-review <command> --help for details.
|
|
1377
1636
|
`;
|
|
1378
1637
|
var CLI_HELP_TOPICS = {
|
|
1379
|
-
configure: `Usage: visual-review configure [--email <email>] [--project <id>] [--list]
|
|
1380
|
-
[--service-url <https-url>] [--repo <path> | --output <path>] [--
|
|
1638
|
+
configure: `Usage: visual-review configure [--email <email>] [--project <id>] [--list] [--no-browser]
|
|
1639
|
+
[--service-url <https-url>] [--repo <path> | --output <path>] [--name <display-name>]
|
|
1640
|
+
[--read-only | --webhook-admin]
|
|
1381
1641
|
|
|
1382
|
-
|
|
1642
|
+
Opens browser approval by default and writes one named project-scoped credential.
|
|
1643
|
+
--read-only requests only feedback:read. --no-browser (or --email) uses a hidden terminal email code.
|
|
1383
1644
|
`,
|
|
1384
1645
|
status: `Usage: visual-review status
|
|
1385
1646
|
|
|
1386
1647
|
Returns configuration, connectionReason, expiry, and scopes as JSON.
|
|
1387
1648
|
`,
|
|
1388
|
-
list: `Usage: visual-review list [--status open|resolved|all] [--limit 1..100] [--cursor <cursor>]
|
|
1649
|
+
list: `Usage: visual-review list [--status open|in_progress|resolved|all] [--limit 1..100] [--cursor <cursor>]
|
|
1389
1650
|
|
|
1390
1651
|
Lists compact feedback summaries. Pass nextCursor to --cursor when hasMore is true.
|
|
1391
1652
|
`,
|
|
@@ -1413,6 +1674,10 @@ Without revisions, the CLI reads the latest pair first and still uses CAS.
|
|
|
1413
1674
|
reopen: `Usage: visual-review reopen <comment-id> [--expected-workflow-revision <revision> --expected-thread-revision <revision>]
|
|
1414
1675
|
|
|
1415
1676
|
Without revisions, the CLI reads the latest pair first and still uses CAS.
|
|
1677
|
+
`,
|
|
1678
|
+
start: `Usage: visual-review start <comment-id> [--expected-workflow-revision <revision> --expected-thread-revision <revision>]
|
|
1679
|
+
|
|
1680
|
+
Moves requested feedback to in progress. Without revisions, the CLI reads the latest pair first.
|
|
1416
1681
|
`,
|
|
1417
1682
|
webhook: `Usage: visual-review webhook get|delete
|
|
1418
1683
|
visual-review webhook set --url <https-url> (--secret <text> | --secret-file <path>) [--inactive]
|
|
@@ -1425,7 +1690,7 @@ Revokes the remote session before removing the local credential unless --local-o
|
|
|
1425
1690
|
`
|
|
1426
1691
|
};
|
|
1427
1692
|
var CLI_COMMAND_NAMES = Object.keys(CLI_HELP_TOPICS);
|
|
1428
|
-
var CLI_VERSION = "0.
|
|
1693
|
+
var CLI_VERSION = "0.15.0";
|
|
1429
1694
|
var CliUsageError = class extends Error {
|
|
1430
1695
|
};
|
|
1431
1696
|
function parseCliCommand(args) {
|
|
@@ -1452,8 +1717,11 @@ function parseCliCommand(args) {
|
|
|
1452
1717
|
let serviceUrl;
|
|
1453
1718
|
let output;
|
|
1454
1719
|
let repository;
|
|
1720
|
+
let displayName;
|
|
1455
1721
|
let list = false;
|
|
1456
1722
|
let webhookAdmin = false;
|
|
1723
|
+
let readOnly = false;
|
|
1724
|
+
let noBrowser = false;
|
|
1457
1725
|
for (let index = 0; index < rest.length; index += 1) {
|
|
1458
1726
|
const token = rest[index];
|
|
1459
1727
|
if (token === "--list") {
|
|
@@ -1464,9 +1732,17 @@ function parseCliCommand(args) {
|
|
|
1464
1732
|
webhookAdmin = true;
|
|
1465
1733
|
continue;
|
|
1466
1734
|
}
|
|
1735
|
+
if (token === "--read-only") {
|
|
1736
|
+
readOnly = true;
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1739
|
+
if (token === "--no-browser") {
|
|
1740
|
+
noBrowser = true;
|
|
1741
|
+
continue;
|
|
1742
|
+
}
|
|
1467
1743
|
const [flagValue, inlineValue] = token.split("=", 2);
|
|
1468
1744
|
const flag = flagValue;
|
|
1469
|
-
if (!["--email", "--project", "--service-url", "--output", "--repo"].includes(flag)) {
|
|
1745
|
+
if (!["--email", "--project", "--service-url", "--output", "--repo", "--name"].includes(flag)) {
|
|
1470
1746
|
throw new CliUsageError(`\uC54C \uC218 \uC5C6\uB294 configure \uC635\uC158: ${token}`);
|
|
1471
1747
|
}
|
|
1472
1748
|
const value = inlineValue ?? rest[++index];
|
|
@@ -1476,17 +1752,22 @@ function parseCliCommand(args) {
|
|
|
1476
1752
|
if (flag === "--service-url") serviceUrl = value;
|
|
1477
1753
|
if (flag === "--output") output = value;
|
|
1478
1754
|
if (flag === "--repo") repository = value;
|
|
1755
|
+
if (flag === "--name") displayName = value;
|
|
1479
1756
|
}
|
|
1480
1757
|
if (output && repository) throw new CliUsageError("--output\uACFC --repo\uB294 \uD568\uAED8 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
1758
|
+
if (readOnly && webhookAdmin) throw new CliUsageError("--read-only\uC640 --webhook-admin\uC740 \uD568\uAED8 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
1481
1759
|
return {
|
|
1482
1760
|
name,
|
|
1483
1761
|
list,
|
|
1484
1762
|
webhookAdmin,
|
|
1763
|
+
readOnly,
|
|
1764
|
+
noBrowser,
|
|
1485
1765
|
...email ? { email } : {},
|
|
1486
1766
|
...projectId ? { projectId } : {},
|
|
1487
1767
|
...serviceUrl ? { serviceUrl } : {},
|
|
1488
1768
|
...output ? { output } : {},
|
|
1489
|
-
...repository ? { repository } : {}
|
|
1769
|
+
...repository ? { repository } : {},
|
|
1770
|
+
...displayName ? { displayName } : {}
|
|
1490
1771
|
};
|
|
1491
1772
|
}
|
|
1492
1773
|
if (name === "status") {
|
|
@@ -1507,8 +1788,8 @@ function parseCliCommand(args) {
|
|
|
1507
1788
|
const [flag, inlineValue] = token.split("=", 2);
|
|
1508
1789
|
if (flag === "--status") {
|
|
1509
1790
|
const value = inlineValue ?? rest[++index];
|
|
1510
|
-
if (value !== "open" && value !== "resolved" && value !== "all") {
|
|
1511
|
-
throw new CliUsageError("--status\uB294 'open', 'resolved', 'all' \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
1791
|
+
if (value !== "open" && value !== "in_progress" && value !== "resolved" && value !== "all") {
|
|
1792
|
+
throw new CliUsageError("--status\uB294 'open', 'in_progress', 'resolved', 'all' \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.");
|
|
1512
1793
|
}
|
|
1513
1794
|
status = value;
|
|
1514
1795
|
continue;
|
|
@@ -1698,7 +1979,7 @@ function parseCliCommand(args) {
|
|
|
1698
1979
|
...secretFile === void 0 ? {} : { secretFile }
|
|
1699
1980
|
};
|
|
1700
1981
|
}
|
|
1701
|
-
if (name === "resolve" || name === "reopen") {
|
|
1982
|
+
if (name === "start" || name === "resolve" || name === "reopen") {
|
|
1702
1983
|
const commentId = rest[0]?.trim();
|
|
1703
1984
|
if (!commentId) throw new CliUsageError(`${name} \uBA85\uB839\uC5D0\uB294 comment-id\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.`);
|
|
1704
1985
|
let expectedWorkflowRevision;
|
|
@@ -1822,7 +2103,7 @@ ${formatAgentFeedbackMarkdown(feedback)}`).join("\n\n---\n\n")
|
|
|
1822
2103
|
return client.setStatus(
|
|
1823
2104
|
projectId,
|
|
1824
2105
|
command.commentId,
|
|
1825
|
-
command.name === "resolve" ? "resolved" : "open",
|
|
2106
|
+
command.name === "resolve" ? "resolved" : command.name === "start" ? "in_progress" : "open",
|
|
1826
2107
|
precondition
|
|
1827
2108
|
);
|
|
1828
2109
|
}
|
|
@@ -1878,6 +2159,8 @@ async function runCli(args, options = {}) {
|
|
|
1878
2159
|
fetch: options.fetch,
|
|
1879
2160
|
now: options.now,
|
|
1880
2161
|
prompt: options.prompt,
|
|
2162
|
+
openBrowser: options.openBrowser,
|
|
2163
|
+
wait: options.wait,
|
|
1881
2164
|
warning: (text) => stderr(`${text}
|
|
1882
2165
|
`)
|
|
1883
2166
|
};
|
|
@@ -1900,6 +2183,7 @@ async function runCli(args, options = {}) {
|
|
|
1900
2183
|
now: lifecycleOptions.now,
|
|
1901
2184
|
warning: lifecycleOptions.warning
|
|
1902
2185
|
});
|
|
2186
|
+
assertCommandScope(command, config.scopes);
|
|
1903
2187
|
sensitiveValues.add(config.token);
|
|
1904
2188
|
const result = await executeCliCommand(
|
|
1905
2189
|
new VisualReviewClient({
|
|
@@ -1932,6 +2216,11 @@ async function runCli(args, options = {}) {
|
|
|
1932
2216
|
return cause instanceof CliUsageError ? 2 : 1;
|
|
1933
2217
|
}
|
|
1934
2218
|
}
|
|
2219
|
+
function assertCommandScope(command, scopes) {
|
|
2220
|
+
if (!agentOperationAllowed(command.name, scopes)) {
|
|
2221
|
+
throw new CliUsageError("\uC774 CLI session\uC758 \uC2B9\uC778\uB41C scope\uB85C\uB294 \uC774 \uBA85\uB839\uC744 \uC0AC\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
1935
2224
|
function cliErrorEnvelope(cause, sensitiveValues) {
|
|
1936
2225
|
if (cause instanceof CliUsageError) {
|
|
1937
2226
|
return {
|