letthemquibble 1.2.0 → 2.1.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/README.md +24 -6
- package/dist/quibble.js +1404 -728
- package/package.json +3 -7
package/dist/quibble.js
CHANGED
|
@@ -6303,9 +6303,9 @@ var asciiTabOrNewline = /[\t\n\r]/g;
|
|
|
6303
6303
|
function stripTabAndNewline(value) {
|
|
6304
6304
|
return value.replace(asciiTabOrNewline, "");
|
|
6305
6305
|
}
|
|
6306
|
-
function urlHostnameOk(url2,
|
|
6307
|
-
|
|
6308
|
-
return
|
|
6306
|
+
function urlHostnameOk(url2, hostname5) {
|
|
6307
|
+
hostname5.lastIndex = 0;
|
|
6308
|
+
return hostname5.test(url2.hostname);
|
|
6309
6309
|
}
|
|
6310
6310
|
function urlProtocolOk(url2, protocol) {
|
|
6311
6311
|
protocol.lastIndex = 0;
|
|
@@ -23233,6 +23233,14 @@ var ACT_AS_HEADER = "x-quibble-act-as";
|
|
|
23233
23233
|
var TRUNCATED_HEADER = "x-quibble-truncated";
|
|
23234
23234
|
var IDEMPOTENCY_HEADER = "idempotency-key";
|
|
23235
23235
|
var IDEMPOTENCY_KEY_MAX = 128;
|
|
23236
|
+
var CLI_LOGIN_TTL_SECONDS = 600;
|
|
23237
|
+
var CLI_LOGIN_POLL_SECONDS = 3;
|
|
23238
|
+
var CLI_LOGIN_CODE_ALPHABET = "BCDFGHJKMNPQRSTVWXZ";
|
|
23239
|
+
var CLI_LOGIN_CODE_LENGTH = 8;
|
|
23240
|
+
var CLI_LOGIN_HOST_MAX = 64;
|
|
23241
|
+
var CLI_LOGIN_OS_MAX = 32;
|
|
23242
|
+
var CLI_LOGIN_VERSION_MAX = 32;
|
|
23243
|
+
var CLI_LOGIN_PAGE_PATH = "/cli-login";
|
|
23236
23244
|
|
|
23237
23245
|
// ../core/dist/errors.js
|
|
23238
23246
|
var QuibbleError = class extends Error {
|
|
@@ -23287,6 +23295,35 @@ function describeError(error62) {
|
|
|
23287
23295
|
return error62 instanceof Error ? error62.message : String(error62);
|
|
23288
23296
|
}
|
|
23289
23297
|
|
|
23298
|
+
// ../core/dist/cli-login.js
|
|
23299
|
+
var CLI_LOGIN_CONTROL_RE = /[\u0000-\u001F\u007F-\u009F\p{Cf}]/u;
|
|
23300
|
+
var CONTROL_GLOBAL_RE = /[\u0000-\u001F\u007F-\u009F\p{Cf}]/gu;
|
|
23301
|
+
var CLI_LOGIN_KEY_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
23302
|
+
var CLI_LOGIN_CODE_MESSAGE = "code: not a login code";
|
|
23303
|
+
var HALF = CLI_LOGIN_CODE_LENGTH / 2;
|
|
23304
|
+
var CODE_RE = new RegExp(`^([${CLI_LOGIN_CODE_ALPHABET}]{${HALF}})-?([${CLI_LOGIN_CODE_ALPHABET}]{${HALF}})$`);
|
|
23305
|
+
var UNKNOWN_HOST = "unknown";
|
|
23306
|
+
function parseCliLoginCode(value) {
|
|
23307
|
+
const match = CODE_RE.exec(value.trim().toUpperCase());
|
|
23308
|
+
if (match === null)
|
|
23309
|
+
throw new ValidationError([CLI_LOGIN_CODE_MESSAGE]);
|
|
23310
|
+
return `${match[1] ?? ""}-${match[2] ?? ""}`;
|
|
23311
|
+
}
|
|
23312
|
+
function cliLoginUrl(webUrl, code2) {
|
|
23313
|
+
return `${webUrl.replace(/\/+$/, "")}${CLI_LOGIN_PAGE_PATH}?code=${code2}`;
|
|
23314
|
+
}
|
|
23315
|
+
function cut(value, max) {
|
|
23316
|
+
if (value.length <= max)
|
|
23317
|
+
return value;
|
|
23318
|
+
const head = value.slice(0, max);
|
|
23319
|
+
const last = head.charCodeAt(head.length - 1);
|
|
23320
|
+
return last >= 55296 && last <= 56319 ? head.slice(0, -1) : head;
|
|
23321
|
+
}
|
|
23322
|
+
function cliLoginHost(raw) {
|
|
23323
|
+
const host = cut(raw.replace(CONTROL_GLOBAL_RE, "").trim(), CLI_LOGIN_HOST_MAX).trim();
|
|
23324
|
+
return host === "" ? UNKNOWN_HOST : host;
|
|
23325
|
+
}
|
|
23326
|
+
|
|
23290
23327
|
// ../core/dist/fingerprint.js
|
|
23291
23328
|
function headlineOf(message) {
|
|
23292
23329
|
for (const line of message.split("\n")) {
|
|
@@ -23307,7 +23344,7 @@ var PUNCTUATION_RE = /[^\p{L}\p{N}\s<>]/gu;
|
|
|
23307
23344
|
function isHexId(run2) {
|
|
23308
23345
|
return /[a-f]/.test(run2) && /[0-9]/.test(run2);
|
|
23309
23346
|
}
|
|
23310
|
-
function
|
|
23347
|
+
function cut2(text, max) {
|
|
23311
23348
|
if (text.length <= max)
|
|
23312
23349
|
return text;
|
|
23313
23350
|
const head = text.slice(0, max);
|
|
@@ -23325,7 +23362,7 @@ function fingerprintOf(message) {
|
|
|
23325
23362
|
text = text.replace(NUMBER_RE, "<n>");
|
|
23326
23363
|
text = text.replace(PUNCTUATION_RE, " ");
|
|
23327
23364
|
text = text.replace(/\s+/gu, " ").trim();
|
|
23328
|
-
text =
|
|
23365
|
+
text = cut2(text, FINGERPRINT_MAX).trim();
|
|
23329
23366
|
return text === "" ? EMPTY_FINGERPRINT : text;
|
|
23330
23367
|
}
|
|
23331
23368
|
function groupComplaints(complaints) {
|
|
@@ -23461,6 +23498,43 @@ function freeText(max, typeMessage) {
|
|
|
23461
23498
|
const message = lengthMessage(1, max);
|
|
23462
23499
|
return external_exports.string({ message: typeMessage ?? message }).overwrite((value) => value.replace(/\r\n?/g, "\n")).trim().min(1, { message }).max(max, { message }).refine((value) => !CONTROL_RE.test(value), { message: CONTROL_MESSAGE });
|
|
23463
23500
|
}
|
|
23501
|
+
var SECRET_PATTERNS = [
|
|
23502
|
+
{
|
|
23503
|
+
kind: "an AWS access key",
|
|
23504
|
+
pattern: /(?<![A-Za-z0-9])(?:AKIA|ASIA)[0-9A-Z]{16}(?![A-Za-z0-9])/
|
|
23505
|
+
},
|
|
23506
|
+
{
|
|
23507
|
+
kind: "a GitHub token",
|
|
23508
|
+
pattern: /(?<![A-Za-z0-9])gh[pousr]_[A-Za-z0-9]{36,}(?![A-Za-z0-9])|(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{22,}/
|
|
23509
|
+
},
|
|
23510
|
+
{
|
|
23511
|
+
kind: "an API key",
|
|
23512
|
+
pattern: /(?<![A-Za-z0-9-])sk-(?:ant-|proj-)?(?=[A-Za-z_-]*[0-9])[A-Za-z0-9_-]{32,}/
|
|
23513
|
+
},
|
|
23514
|
+
{ kind: "a Stripe live key", pattern: /(?<![A-Za-z0-9])[rs]k_live_[A-Za-z0-9]{20,}/ },
|
|
23515
|
+
{ kind: "a Slack token", pattern: /(?<![A-Za-z0-9])xox[abposr]-[A-Za-z0-9-]{10,}/ },
|
|
23516
|
+
{
|
|
23517
|
+
kind: "a Quibble project token",
|
|
23518
|
+
pattern: /(?<![A-Za-z0-9])qbp_[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])/
|
|
23519
|
+
},
|
|
23520
|
+
{ kind: "a private key", pattern: /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----/ }
|
|
23521
|
+
];
|
|
23522
|
+
function findSecret(text) {
|
|
23523
|
+
return SECRET_PATTERNS.find(({ pattern }) => pattern.test(text))?.kind;
|
|
23524
|
+
}
|
|
23525
|
+
function secretFree(schema, max) {
|
|
23526
|
+
return schema.superRefine((value, ctx) => {
|
|
23527
|
+
if (value.length > max || CONTROL_RE.test(value))
|
|
23528
|
+
return;
|
|
23529
|
+
const kind = findSecret(value);
|
|
23530
|
+
if (kind !== void 0) {
|
|
23531
|
+
ctx.addIssue({
|
|
23532
|
+
code: "custom",
|
|
23533
|
+
message: `holds what looks like ${kind}: remove the secret and file again`
|
|
23534
|
+
});
|
|
23535
|
+
}
|
|
23536
|
+
});
|
|
23537
|
+
}
|
|
23464
23538
|
var SEVERITY_MESSAGE = `must be one of ${SEVERITIES.join(", ")}`;
|
|
23465
23539
|
var STATUS_MESSAGE = `must be one of ${STATUSES.join(", ")}`;
|
|
23466
23540
|
var LIMIT_MESSAGE = `must be an integer between 1 and ${MAX_LIMIT}`;
|
|
@@ -23613,7 +23687,7 @@ var agentSchema = freeText(AGENT_MAX);
|
|
|
23613
23687
|
var newComplaintShape = {
|
|
23614
23688
|
org: orgSlugSchema,
|
|
23615
23689
|
project: projectNameSchema,
|
|
23616
|
-
message: freeText(MESSAGE_MAX),
|
|
23690
|
+
message: secretFree(freeText(MESSAGE_MAX), MESSAGE_MAX),
|
|
23617
23691
|
severity: severitySchema.optional(),
|
|
23618
23692
|
tags: tagsSchema.optional(),
|
|
23619
23693
|
agent: agentSchema.optional(),
|
|
@@ -23621,7 +23695,7 @@ var newComplaintShape = {
|
|
|
23621
23695
|
reporterName: freeText(REPORTER_MAX).optional(),
|
|
23622
23696
|
cwd: cwdSchema.optional(),
|
|
23623
23697
|
// Free text: it is trimmed and may hold newlines, and nothing parses it.
|
|
23624
|
-
cmd: freeText(CMD_MAX).optional()
|
|
23698
|
+
cmd: secretFree(freeText(CMD_MAX), CMD_MAX).optional()
|
|
23625
23699
|
};
|
|
23626
23700
|
var newComplaintObject = external_exports.strictObject(newComplaintShape);
|
|
23627
23701
|
function withDefaults(input2) {
|
|
@@ -23906,6 +23980,23 @@ function newTokenSchema(now) {
|
|
|
23906
23980
|
...input2.expiresAt === void 0 ? {} : { expiresAt: input2.expiresAt }
|
|
23907
23981
|
}));
|
|
23908
23982
|
}
|
|
23983
|
+
function reportedField(max) {
|
|
23984
|
+
const message = lengthMessage(1, max);
|
|
23985
|
+
return external_exports.string({ message }).trim().min(1, { message }).max(max, { message }).refine((value) => !CLI_LOGIN_CONTROL_RE.test(value), { message: CONTROL_MESSAGE });
|
|
23986
|
+
}
|
|
23987
|
+
var newCliLoginSchema = external_exports.strictObject({
|
|
23988
|
+
host: reportedField(CLI_LOGIN_HOST_MAX),
|
|
23989
|
+
os: reportedField(CLI_LOGIN_OS_MAX),
|
|
23990
|
+
version: reportedField(CLI_LOGIN_VERSION_MAX)
|
|
23991
|
+
}).transform((input2) => ({
|
|
23992
|
+
host: input2.host,
|
|
23993
|
+
os: input2.os,
|
|
23994
|
+
version: input2.version
|
|
23995
|
+
}));
|
|
23996
|
+
var LOGIN_KEY_MESSAGE = "must be 43 base64url characters";
|
|
23997
|
+
var cliLoginPollSchema = external_exports.strictObject({
|
|
23998
|
+
loginKey: external_exports.string({ message: LOGIN_KEY_MESSAGE }).regex(CLI_LOGIN_KEY_RE, { message: LOGIN_KEY_MESSAGE })
|
|
23999
|
+
}).transform((input2) => ({ loginKey: input2.loginKey }));
|
|
23909
24000
|
var UNKNOWN_KEY_MESSAGE = "unknown key";
|
|
23910
24001
|
var emptyBodySchema = external_exports.strictObject({});
|
|
23911
24002
|
function formatIssues(error62) {
|
|
@@ -24756,6 +24847,7 @@ var TOO_MANY_LINE = "Too many attempts. Try again later.";
|
|
|
24756
24847
|
var WRONG_CODE_LINE = "Wrong code.";
|
|
24757
24848
|
var EXPIRED_CODE_LINE = "That code has expired. Request a new one.";
|
|
24758
24849
|
var PASSWORD_POLICY_LINE = "Use at least 12 characters, with an uppercase letter, a lowercase letter, a number and a symbol.";
|
|
24850
|
+
var BROWSER_REFUSED_LINE = "the browser approval could not be used: run quibble login again";
|
|
24759
24851
|
var UNREACHABLE_TYPE = "Unreachable";
|
|
24760
24852
|
var INCORRECT_CREDENTIALS_MESSAGE = "Incorrect username or password.";
|
|
24761
24853
|
var CognitoError = class extends RemoteError {
|
|
@@ -24772,14 +24864,15 @@ var CognitoError = class extends RemoteError {
|
|
|
24772
24864
|
function endpointUrl(region) {
|
|
24773
24865
|
return `https://cognito-idp.${region}.amazonaws.com/`;
|
|
24774
24866
|
}
|
|
24775
|
-
function errorLineOf(type, detail2,
|
|
24867
|
+
function errorLineOf(type, detail2, step) {
|
|
24776
24868
|
switch (type) {
|
|
24777
24869
|
case UNREACHABLE_TYPE:
|
|
24778
24870
|
return UNREACHABLE_LINE;
|
|
24779
24871
|
case "UserNotFoundException":
|
|
24780
|
-
return
|
|
24872
|
+
return step === "sign-in" ? WRONG_CREDENTIALS_LINE : detail2 ?? type;
|
|
24781
24873
|
case "NotAuthorizedException":
|
|
24782
|
-
|
|
24874
|
+
if (step === "browser") return BROWSER_REFUSED_LINE;
|
|
24875
|
+
return step === "sign-in" && detail2 === INCORRECT_CREDENTIALS_MESSAGE ? WRONG_CREDENTIALS_LINE : detail2 ?? type;
|
|
24783
24876
|
case "LimitExceededException":
|
|
24784
24877
|
case "TooManyRequestsException":
|
|
24785
24878
|
case "TooManyFailedAttemptsException":
|
|
@@ -24800,7 +24893,7 @@ function typeOf(value) {
|
|
|
24800
24893
|
const hash2 = value.indexOf("#");
|
|
24801
24894
|
return hash2 === -1 ? value : value.slice(hash2 + 1);
|
|
24802
24895
|
}
|
|
24803
|
-
async function call(endpoint, operation, body,
|
|
24896
|
+
async function call(endpoint, operation, body, step = "other") {
|
|
24804
24897
|
const url2 = endpointUrl(endpoint.region);
|
|
24805
24898
|
const send2 = endpoint.fetch ?? globalThis.fetch;
|
|
24806
24899
|
const site = {
|
|
@@ -24812,7 +24905,7 @@ async function call(endpoint, operation, body, signIn2 = false) {
|
|
|
24812
24905
|
throw new CognitoError(
|
|
24813
24906
|
type,
|
|
24814
24907
|
detail2,
|
|
24815
|
-
errorLineOf(type, detail2,
|
|
24908
|
+
errorLineOf(type, detail2, step),
|
|
24816
24909
|
detail(site, status)
|
|
24817
24910
|
);
|
|
24818
24911
|
};
|
|
@@ -24895,14 +24988,27 @@ function authAnswerOf(body) {
|
|
|
24895
24988
|
}
|
|
24896
24989
|
};
|
|
24897
24990
|
}
|
|
24991
|
+
function flowOf(input2) {
|
|
24992
|
+
switch (input2.flow) {
|
|
24993
|
+
case "USER_PASSWORD_AUTH":
|
|
24994
|
+
return {
|
|
24995
|
+
parameters: { USERNAME: input2.username, PASSWORD: input2.password },
|
|
24996
|
+
step: "sign-in"
|
|
24997
|
+
};
|
|
24998
|
+
case "REFRESH_TOKEN_AUTH":
|
|
24999
|
+
return { parameters: { REFRESH_TOKEN: input2.refreshToken }, step: "other" };
|
|
25000
|
+
case "CUSTOM_AUTH":
|
|
25001
|
+
return { parameters: { USERNAME: input2.username }, step: "browser" };
|
|
25002
|
+
}
|
|
25003
|
+
}
|
|
24898
25004
|
async function initiateAuth(endpoint, input2) {
|
|
24899
|
-
const
|
|
25005
|
+
const { parameters, step } = flowOf(input2);
|
|
24900
25006
|
return authAnswerOf(
|
|
24901
25007
|
await call(
|
|
24902
25008
|
endpoint,
|
|
24903
25009
|
"InitiateAuth",
|
|
24904
25010
|
{ ClientId: input2.clientId, AuthFlow: input2.flow, AuthParameters: parameters },
|
|
24905
|
-
|
|
25011
|
+
step
|
|
24906
25012
|
)
|
|
24907
25013
|
);
|
|
24908
25014
|
}
|
|
@@ -24917,6 +25023,22 @@ async function respondToAuthChallenge(endpoint, input2) {
|
|
|
24917
25023
|
})
|
|
24918
25024
|
);
|
|
24919
25025
|
}
|
|
25026
|
+
async function respondToCustomChallenge(endpoint, input2) {
|
|
25027
|
+
const username = input2.challenge.challengeParameters["USERNAME"] ?? input2.username;
|
|
25028
|
+
return authAnswerOf(
|
|
25029
|
+
await call(
|
|
25030
|
+
endpoint,
|
|
25031
|
+
"RespondToAuthChallenge",
|
|
25032
|
+
{
|
|
25033
|
+
ClientId: input2.clientId,
|
|
25034
|
+
ChallengeName: input2.challenge.challengeName,
|
|
25035
|
+
Session: input2.challenge.session,
|
|
25036
|
+
ChallengeResponses: { USERNAME: username, ANSWER: input2.answer }
|
|
25037
|
+
},
|
|
25038
|
+
"browser"
|
|
25039
|
+
)
|
|
25040
|
+
);
|
|
25041
|
+
}
|
|
24920
25042
|
async function revokeToken(endpoint, input2) {
|
|
24921
25043
|
await call(endpoint, "RevokeToken", { ClientId: input2.clientId, Token: input2.token });
|
|
24922
25044
|
}
|
|
@@ -25350,7 +25472,7 @@ function resolveEndpointSource(options, project, user) {
|
|
|
25350
25472
|
};
|
|
25351
25473
|
}
|
|
25352
25474
|
function resolveContext(options) {
|
|
25353
|
-
const project = findProjectConfig(options.cwd);
|
|
25475
|
+
const project = options.readProjectConfig === false ? void 0 : findProjectConfig(options.cwd);
|
|
25354
25476
|
const user = loadUserConfig(options.env);
|
|
25355
25477
|
const { mode, endpoint, endpointSource, localFix } = resolveEndpointSource(
|
|
25356
25478
|
options,
|
|
@@ -25591,7 +25713,7 @@ function readComplaint(body) {
|
|
|
25591
25713
|
throw new RemoteError(NOT_A_COMPLAINT, { cause: error62 });
|
|
25592
25714
|
}
|
|
25593
25715
|
}
|
|
25594
|
-
var ApiClient = class {
|
|
25716
|
+
var ApiClient = class _ApiClient {
|
|
25595
25717
|
endpoint;
|
|
25596
25718
|
/** The organization of every org-scoped path, when the command resolved one. */
|
|
25597
25719
|
org;
|
|
@@ -25639,6 +25761,19 @@ var ApiClient = class {
|
|
|
25639
25761
|
if (this.org === void 0) throw new ConfigError(NO_ORG_MESSAGE);
|
|
25640
25762
|
return `/v1/orgs/${encodeURIComponent(this.org)}`;
|
|
25641
25763
|
}
|
|
25764
|
+
/**
|
|
25765
|
+
* The same client for one organization, with the same authorizer, so a
|
|
25766
|
+
* command that learns the organization from `GET /v1/me` reads the auth
|
|
25767
|
+
* config and refreshes a session once. It runs no `beforeFirst`.
|
|
25768
|
+
*/
|
|
25769
|
+
forOrg(org) {
|
|
25770
|
+
return new _ApiClient({
|
|
25771
|
+
endpoint: this.endpoint,
|
|
25772
|
+
authorizer: this.#auth,
|
|
25773
|
+
org,
|
|
25774
|
+
...this.#http
|
|
25775
|
+
});
|
|
25776
|
+
}
|
|
25642
25777
|
/** Reads the caller's own identity. */
|
|
25643
25778
|
async me() {
|
|
25644
25779
|
return parseMe(await this.request("GET", "/v1/me"));
|
|
@@ -25666,6 +25801,18 @@ var ApiClient = class {
|
|
|
25666
25801
|
async project(name) {
|
|
25667
25802
|
return readProject(await this.request("GET", this.projectPath(name)), name);
|
|
25668
25803
|
}
|
|
25804
|
+
/**
|
|
25805
|
+
* Creates a project, or restores a deleted one of that name (section 7.5).
|
|
25806
|
+
* The route needs `admin`, and answers `201` with the record.
|
|
25807
|
+
*/
|
|
25808
|
+
async createProject(name, tags) {
|
|
25809
|
+
const body = await this.request(
|
|
25810
|
+
"POST",
|
|
25811
|
+
`${this.orgPath()}/projects`,
|
|
25812
|
+
JSON.stringify({ project: name, ...tags === void 0 ? {} : { tags } })
|
|
25813
|
+
);
|
|
25814
|
+
return readProject(body, name);
|
|
25815
|
+
}
|
|
25669
25816
|
/**
|
|
25670
25817
|
* Replaces the allowed tag list of a project, or clears it with `null`. The
|
|
25671
25818
|
* route retags the stored complaints and answers how many it rewrote.
|
|
@@ -26229,7 +26376,9 @@ function projectOf(entry) {
|
|
|
26229
26376
|
}
|
|
26230
26377
|
function headlineOfEntry(entry) {
|
|
26231
26378
|
const message = entry.body["message"];
|
|
26232
|
-
|
|
26379
|
+
if (typeof message !== "string") return '"?"';
|
|
26380
|
+
const headline = summarize(message);
|
|
26381
|
+
return findSecret(headline) === void 0 ? `"${printable(headline)}"` : `key ${printable(entry.key)}`;
|
|
26233
26382
|
}
|
|
26234
26383
|
function queuedCount(n) {
|
|
26235
26384
|
return `${n} queued ${n === 1 ? "complaint" : "complaints"}`;
|
|
@@ -26300,7 +26449,7 @@ async function flushQueue(options) {
|
|
|
26300
26449
|
result.dropped += 1;
|
|
26301
26450
|
writeLine(
|
|
26302
26451
|
options.stderr,
|
|
26303
|
-
`quibble: dropped a queued complaint for ${entry.org}/${projectOf(entry)},
|
|
26452
|
+
`quibble: dropped a queued complaint for ${entry.org}/${projectOf(entry)}, ${headlineOfEntry(entry)}: ${printable(message)}`
|
|
26304
26453
|
);
|
|
26305
26454
|
}
|
|
26306
26455
|
return result;
|
|
@@ -26552,6 +26701,75 @@ async function runConfig(options, context, io) {
|
|
|
26552
26701
|
import { existsSync as existsSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
26553
26702
|
import { basename as basename2, join as join7, resolve as resolve3 } from "node:path";
|
|
26554
26703
|
|
|
26704
|
+
// src/git.ts
|
|
26705
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
26706
|
+
var GIT_TIMEOUT_MS = 5e3;
|
|
26707
|
+
var ORIGIN_ARGS = ["remote", "get-url", "origin"];
|
|
26708
|
+
function nodeGit(options = {}) {
|
|
26709
|
+
const spawn = options.spawn ?? nodeSpawn;
|
|
26710
|
+
const timeoutMs = options.timeoutMs ?? GIT_TIMEOUT_MS;
|
|
26711
|
+
return {
|
|
26712
|
+
originUrl(cwd) {
|
|
26713
|
+
return new Promise((resolve4) => {
|
|
26714
|
+
let settled = false;
|
|
26715
|
+
let child;
|
|
26716
|
+
let output2 = "";
|
|
26717
|
+
const settle2 = (url2) => {
|
|
26718
|
+
if (settled) return;
|
|
26719
|
+
settled = true;
|
|
26720
|
+
clearTimeout(timer);
|
|
26721
|
+
resolve4(url2);
|
|
26722
|
+
};
|
|
26723
|
+
const timer = setTimeout(() => {
|
|
26724
|
+
settle2(void 0);
|
|
26725
|
+
child?.kill();
|
|
26726
|
+
}, timeoutMs);
|
|
26727
|
+
timer.unref();
|
|
26728
|
+
try {
|
|
26729
|
+
child = spawn("git", [...ORIGIN_ARGS], {
|
|
26730
|
+
cwd,
|
|
26731
|
+
shell: false,
|
|
26732
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
26733
|
+
windowsHide: true
|
|
26734
|
+
});
|
|
26735
|
+
} catch {
|
|
26736
|
+
settle2(void 0);
|
|
26737
|
+
return;
|
|
26738
|
+
}
|
|
26739
|
+
child.stdout?.setEncoding("utf8");
|
|
26740
|
+
child.stdout?.on("data", (chunk) => {
|
|
26741
|
+
output2 += chunk;
|
|
26742
|
+
});
|
|
26743
|
+
child.on("error", () => {
|
|
26744
|
+
settle2(void 0);
|
|
26745
|
+
});
|
|
26746
|
+
child.on("close", (code2) => {
|
|
26747
|
+
const url2 = output2.trim();
|
|
26748
|
+
settle2(code2 === 0 && url2 !== "" ? url2 : void 0);
|
|
26749
|
+
});
|
|
26750
|
+
});
|
|
26751
|
+
}
|
|
26752
|
+
};
|
|
26753
|
+
}
|
|
26754
|
+
function remoteSegment(url2) {
|
|
26755
|
+
let rest = url2.trim();
|
|
26756
|
+
if (rest.endsWith("/")) rest = rest.slice(0, -1);
|
|
26757
|
+
if (rest.endsWith(".git")) rest = rest.slice(0, -".git".length);
|
|
26758
|
+
const slash = rest.lastIndexOf("/");
|
|
26759
|
+
const colon = rest.lastIndexOf(":");
|
|
26760
|
+
const scpLike = !rest.includes("://") && colon > slash;
|
|
26761
|
+
return rest.slice((scpLike ? colon : slash) + 1);
|
|
26762
|
+
}
|
|
26763
|
+
function projectOfRemote(url2) {
|
|
26764
|
+
const segment = remoteSegment(url2);
|
|
26765
|
+
if (segment === "") return void 0;
|
|
26766
|
+
try {
|
|
26767
|
+
return parseProjectName(segment);
|
|
26768
|
+
} catch {
|
|
26769
|
+
return void 0;
|
|
26770
|
+
}
|
|
26771
|
+
}
|
|
26772
|
+
|
|
26555
26773
|
// src/hook.ts
|
|
26556
26774
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
26557
26775
|
import { dirname as dirname5, join as join5 } from "node:path";
|
|
@@ -26624,11 +26842,13 @@ var DEFAULT_SNIPPET_AGENT = "claude";
|
|
|
26624
26842
|
var AGENTS_FILE_AGENT = "codex";
|
|
26625
26843
|
var SNIPPET_INTRO = `This project uses Quibble. When the build, tests, docs or tooling waste your
|
|
26626
26844
|
time, file a complaint instead of quietly working around the problem.`;
|
|
26845
|
+
var SNIPPET_SECRETS = "- Never paste secrets, tokens or .env lines into a complaint; Quibble refuses them.";
|
|
26627
26846
|
var SNIPPET_USAGE = `- Set severity with \`-s low|medium|high\`. Add tags with \`-t <tag>\` (repeatable).
|
|
26628
26847
|
- Put a one-line summary first; it is the headline everywhere. Add details below.
|
|
26629
26848
|
- Name the command that failed with \`--cmd "<command>"\`.
|
|
26630
26849
|
- Pipe a long message on stdin with \`quibble complain -\`.
|
|
26631
|
-
- File one complaint per problem, as soon as you hit it, not at the end of the task
|
|
26850
|
+
- File one complaint per problem, as soon as you hit it, not at the end of the task.
|
|
26851
|
+
${SNIPPET_SECRETS}`;
|
|
26632
26852
|
var SKILL_FRONT_MATTER = `---
|
|
26633
26853
|
name: quibble
|
|
26634
26854
|
description: File a Quibble complaint when the build, tests, docs or tooling of this project waste your time. Use it as soon as you hit the problem, not at the end of the task.
|
|
@@ -26746,826 +26966,1273 @@ function refreshedLine(project, tags) {
|
|
|
26746
26966
|
return `${lead} tag each complaint with ${orList(tags)}, or ${OTHER_TAG_PREFIX}<name> when none fits.`;
|
|
26747
26967
|
}
|
|
26748
26968
|
|
|
26749
|
-
// src/commands/
|
|
26750
|
-
import {
|
|
26751
|
-
|
|
26752
|
-
|
|
26753
|
-
|
|
26754
|
-
|
|
26755
|
-
|
|
26756
|
-
|
|
26757
|
-
|
|
26758
|
-
|
|
26759
|
-
|
|
26760
|
-
|
|
26761
|
-
|
|
26762
|
-
}
|
|
26763
|
-
|
|
26764
|
-
|
|
26765
|
-
|
|
26969
|
+
// src/commands/login.ts
|
|
26970
|
+
import { hostname as hostname4 } from "node:os";
|
|
26971
|
+
|
|
26972
|
+
// src/opener.ts
|
|
26973
|
+
import { spawn as nodeSpawn2 } from "node:child_process";
|
|
26974
|
+
var OPEN_TIMEOUT_MS = 5e3;
|
|
26975
|
+
var OPENABLE_URL_RE = /^https?:\/\/[A-Za-z0-9.:[\]-]+(?:\/[A-Za-z0-9/?=._~-]*)?$/;
|
|
26976
|
+
function isWsl(platform, env) {
|
|
26977
|
+
if (platform !== "linux") return false;
|
|
26978
|
+
const set2 = (name) => (env[name] ?? "") !== "";
|
|
26979
|
+
return set2("WSL_DISTRO_NAME") || set2("WSL_INTEROP");
|
|
26980
|
+
}
|
|
26981
|
+
function openCommands(url2, platform, env) {
|
|
26982
|
+
if (platform === "darwin") return [{ command: "open", args: [url2] }];
|
|
26983
|
+
if (platform === "win32") {
|
|
26984
|
+
return [{ command: "cmd", args: ["/c", "start", "", url2] }];
|
|
26985
|
+
}
|
|
26986
|
+
if (isWsl(platform, env)) {
|
|
26987
|
+
return [
|
|
26988
|
+
{ command: "wslview", args: [url2] },
|
|
26989
|
+
{ command: "cmd.exe", args: ["/c", "start", "", url2] }
|
|
26990
|
+
];
|
|
26991
|
+
}
|
|
26992
|
+
return [{ command: "xdg-open", args: [url2] }];
|
|
26993
|
+
}
|
|
26994
|
+
function attempt(spawn, entry, timeoutMs) {
|
|
26995
|
+
return new Promise((resolve4) => {
|
|
26996
|
+
let settled = false;
|
|
26997
|
+
const settle2 = (opened) => {
|
|
26998
|
+
if (settled) return;
|
|
26999
|
+
settled = true;
|
|
27000
|
+
clearTimeout(timer);
|
|
27001
|
+
resolve4(opened);
|
|
27002
|
+
};
|
|
27003
|
+
const timer = setTimeout(() => {
|
|
27004
|
+
settle2(false);
|
|
27005
|
+
}, timeoutMs);
|
|
27006
|
+
timer.unref();
|
|
27007
|
+
let child;
|
|
27008
|
+
try {
|
|
27009
|
+
child = spawn(entry.command, entry.args, {
|
|
27010
|
+
shell: false,
|
|
27011
|
+
detached: true,
|
|
27012
|
+
stdio: "ignore",
|
|
27013
|
+
windowsHide: true
|
|
27014
|
+
});
|
|
27015
|
+
} catch {
|
|
27016
|
+
settle2(false);
|
|
27017
|
+
return;
|
|
27018
|
+
}
|
|
27019
|
+
child.on("error", () => {
|
|
27020
|
+
settle2(false);
|
|
27021
|
+
});
|
|
27022
|
+
child.on("exit", (code2) => {
|
|
27023
|
+
settle2(code2 === 0);
|
|
27024
|
+
});
|
|
27025
|
+
child.unref();
|
|
27026
|
+
});
|
|
26766
27027
|
}
|
|
26767
|
-
|
|
26768
|
-
|
|
26769
|
-
|
|
26770
|
-
|
|
26771
|
-
|
|
26772
|
-
|
|
26773
|
-
|
|
26774
|
-
|
|
26775
|
-
|
|
26776
|
-
|
|
26777
|
-
|
|
27028
|
+
function platformOpener(options = {}) {
|
|
27029
|
+
const platform = options.platform ?? process.platform;
|
|
27030
|
+
const env = options.env ?? process.env;
|
|
27031
|
+
const spawn = options.spawn ?? nodeSpawn2;
|
|
27032
|
+
const timeoutMs = options.timeoutMs ?? OPEN_TIMEOUT_MS;
|
|
27033
|
+
return {
|
|
27034
|
+
async open(url2) {
|
|
27035
|
+
if (!OPENABLE_URL_RE.test(url2)) return false;
|
|
27036
|
+
for (const entry of openCommands(url2, platform, env)) {
|
|
27037
|
+
if (await attempt(spawn, entry, timeoutMs)) return true;
|
|
27038
|
+
}
|
|
27039
|
+
return false;
|
|
27040
|
+
}
|
|
26778
27041
|
};
|
|
26779
|
-
return { tags, scope };
|
|
26780
27042
|
}
|
|
26781
|
-
async function runSnippet(options, context, io) {
|
|
26782
|
-
const project = requireProject(context);
|
|
26783
|
-
const override = options.agent === void 0 ? void 0 : parseAgentName(options.agent);
|
|
26784
|
-
const appendPath = options.append === void 0 ? void 0 : resolve2(io.cwd, options.append);
|
|
26785
|
-
const skillPath = options.skill === true ? join6(context.projectRoot ?? io.cwd, SKILL_PATH) : void 0;
|
|
26786
|
-
const existing = appendPath === void 0 ? "" : readOptional2(appendPath);
|
|
26787
|
-
if (options.append !== void 0 && hasSnippet(existing)) {
|
|
26788
|
-
throw new UsageError(
|
|
26789
|
-
`${options.append} already has a "${SNIPPET_HEADING}" section: run quibble sync to refresh it`
|
|
26790
|
-
);
|
|
26791
|
-
}
|
|
26792
|
-
if (skillPath !== void 0 && existsSync(skillPath) && options.force !== true) {
|
|
26793
|
-
throw existsError(skillPath);
|
|
26794
|
-
}
|
|
26795
|
-
const hook = options.hook === true ? planHook(context.projectRoot ?? io.cwd) : void 0;
|
|
26796
|
-
const { tags, scope } = await snippetSource(project, context, io);
|
|
26797
|
-
let shown = { agent: override ?? DEFAULT_SNIPPET_AGENT, snippet: "" };
|
|
26798
|
-
shown.snippet = agentSnippet(tags, { ...scope, agent: shown.agent });
|
|
26799
|
-
if (appendPath !== void 0) {
|
|
26800
|
-
const agent = override ?? agentForFile(appendPath);
|
|
26801
|
-
const snippet = agentSnippet(tags, { ...scope, agent });
|
|
26802
|
-
const kept = existing.replace(/\s+$/, "");
|
|
26803
|
-
writeFileSync4(appendPath, kept === "" ? `${snippet}
|
|
26804
|
-
` : `${kept}
|
|
26805
27043
|
|
|
26806
|
-
|
|
26807
|
-
|
|
26808
|
-
|
|
26809
|
-
|
|
26810
|
-
|
|
26811
|
-
|
|
26812
|
-
|
|
26813
|
-
|
|
26814
|
-
|
|
27044
|
+
// src/terminal.ts
|
|
27045
|
+
import { createInterface } from "node:readline";
|
|
27046
|
+
import { Writable } from "node:stream";
|
|
27047
|
+
var NodeTerminal = class {
|
|
27048
|
+
isTTY;
|
|
27049
|
+
#input;
|
|
27050
|
+
#output;
|
|
27051
|
+
#rl;
|
|
27052
|
+
/**
|
|
27053
|
+
* True unless a visible prompt is waiting, so a line that arrives early,
|
|
27054
|
+
* a pasted password after the email for one, echoes nowhere.
|
|
27055
|
+
*/
|
|
27056
|
+
#muted = true;
|
|
27057
|
+
/** Lines that arrived before a prompt asked for them. */
|
|
27058
|
+
#lines = [];
|
|
27059
|
+
#waiting;
|
|
27060
|
+
#closed = false;
|
|
27061
|
+
constructor(input2, output2) {
|
|
27062
|
+
this.#input = input2;
|
|
27063
|
+
this.#output = output2;
|
|
27064
|
+
this.isTTY = input2.isTTY === true;
|
|
26815
27065
|
}
|
|
26816
|
-
|
|
26817
|
-
|
|
26818
|
-
writeJson(io.stdout, {
|
|
26819
|
-
snippet: shown.snippet,
|
|
26820
|
-
agent: shown.agent,
|
|
26821
|
-
appended: appendPath ?? null,
|
|
26822
|
-
skill: skillPath ?? null,
|
|
26823
|
-
hook: hook?.path ?? null
|
|
26824
|
-
});
|
|
26825
|
-
return 0;
|
|
27066
|
+
prompt(label) {
|
|
27067
|
+
return this.#read(label, false);
|
|
26826
27068
|
}
|
|
26827
|
-
|
|
26828
|
-
|
|
27069
|
+
promptHidden(label) {
|
|
27070
|
+
return this.#read(label, true);
|
|
26829
27071
|
}
|
|
26830
|
-
|
|
26831
|
-
|
|
27072
|
+
close() {
|
|
27073
|
+
this.#closed = true;
|
|
27074
|
+
this.#rl?.close();
|
|
27075
|
+
this.#rl = void 0;
|
|
26832
27076
|
}
|
|
26833
|
-
|
|
26834
|
-
|
|
26835
|
-
|
|
26836
|
-
|
|
26837
|
-
|
|
26838
|
-
|
|
26839
|
-
|
|
26840
|
-
|
|
26841
|
-
|
|
26842
|
-
}
|
|
26843
|
-
|
|
26844
|
-
|
|
26845
|
-
|
|
26846
|
-
|
|
26847
|
-
|
|
27077
|
+
/** The one interface, created on the first prompt. */
|
|
27078
|
+
#interface() {
|
|
27079
|
+
if (this.#rl !== void 0) return this.#rl;
|
|
27080
|
+
const output2 = this.#output;
|
|
27081
|
+
const sink2 = new Writable({
|
|
27082
|
+
write: (chunk, _encoding, done) => {
|
|
27083
|
+
if (!this.#muted) output2.write(chunk);
|
|
27084
|
+
done();
|
|
27085
|
+
}
|
|
27086
|
+
});
|
|
27087
|
+
Object.defineProperties(sink2, {
|
|
27088
|
+
columns: { get: () => output2.columns },
|
|
27089
|
+
rows: { get: () => output2.rows }
|
|
27090
|
+
});
|
|
27091
|
+
const rl = createInterface({
|
|
27092
|
+
input: this.#input,
|
|
27093
|
+
output: sink2,
|
|
27094
|
+
terminal: true,
|
|
27095
|
+
// No answer enters the history, so an arrow key recalls no password.
|
|
27096
|
+
historySize: 0
|
|
27097
|
+
});
|
|
27098
|
+
rl.on("line", (line) => {
|
|
27099
|
+
this.#muted = true;
|
|
27100
|
+
if (this.#waiting !== void 0) {
|
|
27101
|
+
const settle2 = this.#waiting;
|
|
27102
|
+
this.#waiting = void 0;
|
|
27103
|
+
settle2(line);
|
|
27104
|
+
return;
|
|
27105
|
+
}
|
|
27106
|
+
this.#lines.push(line);
|
|
27107
|
+
});
|
|
27108
|
+
rl.on("close", () => {
|
|
27109
|
+
this.#closed = true;
|
|
27110
|
+
this.#waiting?.("");
|
|
27111
|
+
this.#waiting = void 0;
|
|
27112
|
+
});
|
|
27113
|
+
rl.on("SIGINT", () => {
|
|
27114
|
+
this.close();
|
|
27115
|
+
this.#output.write("\n");
|
|
27116
|
+
process.kill(process.pid, "SIGINT");
|
|
27117
|
+
});
|
|
27118
|
+
this.#rl = rl;
|
|
27119
|
+
return rl;
|
|
26848
27120
|
}
|
|
26849
|
-
|
|
26850
|
-
|
|
26851
|
-
|
|
27121
|
+
/** Waits for the next line, which a paste may have delivered already. */
|
|
27122
|
+
#next() {
|
|
27123
|
+
const queued = this.#lines.shift();
|
|
27124
|
+
if (queued !== void 0) return Promise.resolve(queued);
|
|
27125
|
+
if (this.#closed) return Promise.resolve("");
|
|
27126
|
+
return new Promise((resolve4) => {
|
|
27127
|
+
this.#waiting = resolve4;
|
|
27128
|
+
});
|
|
26852
27129
|
}
|
|
26853
|
-
|
|
26854
|
-
|
|
26855
|
-
|
|
26856
|
-
|
|
26857
|
-
|
|
26858
|
-
|
|
26859
|
-
|
|
26860
|
-
|
|
26861
|
-
|
|
26862
|
-
|
|
26863
|
-
|
|
26864
|
-
|
|
26865
|
-
|
|
26866
|
-
|
|
26867
|
-
|
|
26868
|
-
|
|
26869
|
-
|
|
26870
|
-
|
|
26871
|
-
|
|
26872
|
-
|
|
26873
|
-
|
|
26874
|
-
|
|
26875
|
-
|
|
26876
|
-
|
|
26877
|
-
|
|
26878
|
-
|
|
26879
|
-
|
|
26880
|
-
|
|
26881
|
-
|
|
26882
|
-
|
|
26883
|
-
|
|
26884
|
-
|
|
26885
|
-
|
|
26886
|
-
|
|
26887
|
-
|
|
26888
|
-
|
|
26889
|
-
|
|
26890
|
-
|
|
26891
|
-
|
|
26892
|
-
|
|
26893
|
-
|
|
26894
|
-
|
|
27130
|
+
async #read(label, hidden) {
|
|
27131
|
+
if (this.#closed) return "";
|
|
27132
|
+
const rl = this.#interface();
|
|
27133
|
+
if (hidden) {
|
|
27134
|
+
this.#output.write(`${label} `);
|
|
27135
|
+
rl.setPrompt("");
|
|
27136
|
+
rl.prompt();
|
|
27137
|
+
try {
|
|
27138
|
+
return await this.#next();
|
|
27139
|
+
} finally {
|
|
27140
|
+
this.#output.write("\n");
|
|
27141
|
+
}
|
|
27142
|
+
}
|
|
27143
|
+
const queued = this.#lines.shift();
|
|
27144
|
+
if (queued !== void 0) {
|
|
27145
|
+
this.#output.write(`${label} ${queued}
|
|
27146
|
+
`);
|
|
27147
|
+
return queued;
|
|
27148
|
+
}
|
|
27149
|
+
rl.setPrompt(`${label} `);
|
|
27150
|
+
this.#muted = false;
|
|
27151
|
+
rl.prompt();
|
|
27152
|
+
try {
|
|
27153
|
+
return await this.#next();
|
|
27154
|
+
} finally {
|
|
27155
|
+
this.#muted = true;
|
|
27156
|
+
}
|
|
27157
|
+
}
|
|
27158
|
+
};
|
|
27159
|
+
function nodeTerminal(input2, output2) {
|
|
27160
|
+
return new NodeTerminal(input2, output2);
|
|
27161
|
+
}
|
|
27162
|
+
|
|
27163
|
+
// src/commands/login.ts
|
|
27164
|
+
var LOCAL_IDENTITY_MESSAGE = "local mode: no identity.";
|
|
27165
|
+
var NEEDS_TERMINAL_MESSAGE = "quibble login --password needs a terminal: in CI, set QUIBBLE_TOKEN instead";
|
|
27166
|
+
var CI_MESSAGE = "quibble login needs a person to approve it in a browser: in CI, set QUIBBLE_TOKEN instead";
|
|
27167
|
+
var NO_BROWSER_MESSAGE = "--no-browser applies to the browser login only";
|
|
27168
|
+
var DENIED_MESSAGE = "login was denied in the browser";
|
|
27169
|
+
var EXPIRED_MESSAGE = "the login request expired: run quibble login again";
|
|
27170
|
+
var MALFORMED_MESSAGE = "the API answered a malformed login request";
|
|
27171
|
+
var UNKNOWN_STATUS_MESSAGE = "the API answered an unknown login status";
|
|
27172
|
+
var MFA_NEEDS_TERMINAL_MESSAGE = "this account needs a two-factor code: run quibble login in a terminal";
|
|
27173
|
+
var MFA_CHALLENGE = "SOFTWARE_TOKEN_MFA";
|
|
27174
|
+
var CUSTOM_CHALLENGE = "CUSTOM_CHALLENGE";
|
|
27175
|
+
var NEW_PASSWORD_CHALLENGE = "NEW_PASSWORD_REQUIRED";
|
|
27176
|
+
var CLI_LOGINS_PATH = "/v1/cli-logins";
|
|
27177
|
+
var CLI_LOGIN_POLL_PATH = "/v1/cli-logins/poll";
|
|
27178
|
+
var MIN_INTERVAL_SECONDS = 1;
|
|
27179
|
+
var MAX_INTERVAL_SECONDS = 30;
|
|
27180
|
+
var PLAIN_HOST_RE = /^(?:[a-z0-9.-]+|\[[0-9a-f:.]+\])(?::[0-9]+)?$/;
|
|
27181
|
+
var LOGIN_PAGE = "/login";
|
|
27182
|
+
var ORGS_PAGE = "/orgs";
|
|
27183
|
+
var NO_PROFILE_STATUSES = /* @__PURE__ */ new Set([404, 409]);
|
|
27184
|
+
function localModeLine(endpoint) {
|
|
27185
|
+
return `${endpoint} is in local mode: no login needed.`;
|
|
27186
|
+
}
|
|
27187
|
+
function terminalOf(io) {
|
|
27188
|
+
return io.terminal ?? nodeTerminal(io.stdin, io.stderr);
|
|
27189
|
+
}
|
|
27190
|
+
function clockOf2(io) {
|
|
27191
|
+
return io.now ?? (() => /* @__PURE__ */ new Date());
|
|
27192
|
+
}
|
|
27193
|
+
function sleepOf(io) {
|
|
27194
|
+
return io.sleep ?? ((ms) => new Promise((resolve4) => {
|
|
27195
|
+
setTimeout(resolve4, ms);
|
|
27196
|
+
}));
|
|
27197
|
+
}
|
|
27198
|
+
function ciIsSet(env) {
|
|
27199
|
+
const value = (env["CI"] ?? "").trim().toLowerCase();
|
|
27200
|
+
return value !== "" && value !== "0" && value !== "false";
|
|
27201
|
+
}
|
|
27202
|
+
async function answer(terminal, label, hidden = false) {
|
|
27203
|
+
const text = hidden ? await terminal.promptHidden(label) : await terminal.prompt(label);
|
|
27204
|
+
const value = hidden ? text : text.trim();
|
|
27205
|
+
if (value === "")
|
|
27206
|
+
throw new UsageError(`${label.replace(/:$/, "").toLowerCase()} must not be empty`);
|
|
27207
|
+
return value;
|
|
27208
|
+
}
|
|
27209
|
+
function unsupportedStep(name) {
|
|
27210
|
+
return new RemoteError(
|
|
27211
|
+
`this account needs a sign-in step that quibble doesn't support (${name})`
|
|
27212
|
+
);
|
|
27213
|
+
}
|
|
27214
|
+
function pageError(error62, webUrl) {
|
|
27215
|
+
if (error62 instanceof CognitoError) {
|
|
27216
|
+
const page = `${trimSlash2(webUrl)}${LOGIN_PAGE}`;
|
|
27217
|
+
if (error62.type === "PasswordResetRequiredException") {
|
|
27218
|
+
return new ConfigError(`reset your password at ${page}`);
|
|
27219
|
+
}
|
|
27220
|
+
if (error62.type === "UserNotConfirmedException") {
|
|
27221
|
+
return new ConfigError(`finish your sign-up at ${page}`);
|
|
27222
|
+
}
|
|
27223
|
+
}
|
|
27224
|
+
return error62;
|
|
27225
|
+
}
|
|
27226
|
+
async function signIn(terminal, identity, config2) {
|
|
27227
|
+
const email3 = await answer(terminal, "Email:");
|
|
27228
|
+
const password = await answer(terminal, "Password:", true);
|
|
27229
|
+
const clientId = config2.cliClientId;
|
|
27230
|
+
try {
|
|
27231
|
+
let result = await initiateAuth(identity, {
|
|
27232
|
+
clientId,
|
|
27233
|
+
flow: "USER_PASSWORD_AUTH",
|
|
27234
|
+
username: email3,
|
|
27235
|
+
password
|
|
26895
27236
|
});
|
|
27237
|
+
if (result.kind === "challenge" && result.challenge.challengeName === MFA_CHALLENGE) {
|
|
27238
|
+
const code2 = await answer(terminal, "Code:");
|
|
27239
|
+
result = await respondToAuthChallenge(identity, {
|
|
27240
|
+
clientId,
|
|
27241
|
+
challenge: result.challenge,
|
|
27242
|
+
email: email3,
|
|
27243
|
+
code: code2
|
|
27244
|
+
});
|
|
27245
|
+
}
|
|
27246
|
+
if (result.kind === "challenge") {
|
|
27247
|
+
const name = result.challenge.challengeName;
|
|
27248
|
+
if (name === NEW_PASSWORD_CHALLENGE) {
|
|
27249
|
+
throw new ConfigError(
|
|
27250
|
+
`finish your first sign-in at ${trimSlash2(config2.webUrl)}${LOGIN_PAGE}`
|
|
27251
|
+
);
|
|
27252
|
+
}
|
|
27253
|
+
throw unsupportedStep(name);
|
|
27254
|
+
}
|
|
27255
|
+
return result.tokens;
|
|
27256
|
+
} catch (error62) {
|
|
27257
|
+
throw pageError(error62, config2.webUrl);
|
|
27258
|
+
}
|
|
27259
|
+
}
|
|
27260
|
+
async function runLogin(options, context, io) {
|
|
27261
|
+
requireServer("login", context);
|
|
27262
|
+
if (options.password === true) {
|
|
27263
|
+
if (options.browser === false) throw new UsageError(NO_BROWSER_MESSAGE);
|
|
27264
|
+
return passwordLogin(options, context, io);
|
|
27265
|
+
}
|
|
27266
|
+
return browserLogin(options, context, io);
|
|
27267
|
+
}
|
|
27268
|
+
async function passwordLogin(options, context, io) {
|
|
27269
|
+
const terminal = terminalOf(io);
|
|
27270
|
+
if (!terminal.isTTY) throw new UsageError(NEEDS_TERMINAL_MESSAGE);
|
|
27271
|
+
const endpoint = await resolveEndpoint(context, io);
|
|
27272
|
+
const http = { debug: context.debug, fetch: io.fetch };
|
|
27273
|
+
const config2 = await fetchAuthConfig(endpoint, http);
|
|
27274
|
+
if (config2.mode === "local") {
|
|
27275
|
+
writeLine(io.stdout, localModeLine(endpoint));
|
|
26896
27276
|
return 0;
|
|
26897
27277
|
}
|
|
26898
|
-
|
|
26899
|
-
|
|
26900
|
-
|
|
26901
|
-
|
|
26902
|
-
|
|
26903
|
-
if (tags !== void 0) writeLine(io.stdout, `Tags: ${tags.join(", ")}`);
|
|
26904
|
-
writeLine(io.stdout);
|
|
26905
|
-
writeLine(io.stdout, agentLead(agent));
|
|
26906
|
-
writeLine(io.stdout);
|
|
26907
|
-
writeLine(io.stdout, snippet);
|
|
26908
|
-
if (agent === DEFAULT_SNIPPET_AGENT) {
|
|
26909
|
-
writeLine(io.stdout);
|
|
26910
|
-
writeLine(io.stdout, CODEX_LINE);
|
|
27278
|
+
let tokens;
|
|
27279
|
+
try {
|
|
27280
|
+
tokens = await signIn(terminal, { region: config2.region, ...http }, config2);
|
|
27281
|
+
} finally {
|
|
27282
|
+
terminal.close();
|
|
26911
27283
|
}
|
|
26912
|
-
|
|
26913
|
-
|
|
26914
|
-
|
|
27284
|
+
const me = await storeSession(tokens, endpoint, config2, http, io);
|
|
27285
|
+
return reportLogin(me, endpoint, options, context, io);
|
|
27286
|
+
}
|
|
27287
|
+
async function browserLogin(options, context, io) {
|
|
27288
|
+
if (ciIsSet(io.env)) throw new UsageError(CI_MESSAGE);
|
|
27289
|
+
const endpoint = await resolveEndpoint(context, io);
|
|
27290
|
+
const session = await browserSession(endpoint, options, context.debug, io);
|
|
27291
|
+
if (session === void 0) {
|
|
27292
|
+
writeLine(io.stdout, localModeLine(endpoint));
|
|
27293
|
+
return 0;
|
|
26915
27294
|
}
|
|
26916
|
-
return
|
|
27295
|
+
return reportLogin(session.me, endpoint, options, context, io);
|
|
26917
27296
|
}
|
|
26918
|
-
async function
|
|
26919
|
-
|
|
26920
|
-
const
|
|
26921
|
-
if (
|
|
26922
|
-
const
|
|
26923
|
-
|
|
26924
|
-
|
|
27297
|
+
async function browserSession(endpoint, options, debug, io) {
|
|
27298
|
+
const http = { debug, fetch: io.fetch };
|
|
27299
|
+
const config2 = await fetchAuthConfig(endpoint, http);
|
|
27300
|
+
if (config2.mode === "local") return void 0;
|
|
27301
|
+
const clock = clockOf2(io);
|
|
27302
|
+
const started = await startLogin(endpoint, config2.webUrl, http, {
|
|
27303
|
+
host: cliLoginHost((io.hostname ?? hostname4)()),
|
|
27304
|
+
os: process.platform,
|
|
27305
|
+
version: options.version
|
|
26925
27306
|
});
|
|
26926
|
-
|
|
27307
|
+
const deadline = clock().getTime() + (CLI_LOGIN_TTL_SECONDS + started.interval) * 1e3;
|
|
27308
|
+
writeLine(
|
|
27309
|
+
io.stderr,
|
|
27310
|
+
`Approve this login in your browser: ${started.url} (code ${started.code}). Waiting...`
|
|
27311
|
+
);
|
|
27312
|
+
if (options.browser !== false) {
|
|
27313
|
+
const opener = io.opener ?? platformOpener({ env: io.env });
|
|
27314
|
+
void opener.open(started.url).catch(() => false);
|
|
27315
|
+
}
|
|
27316
|
+
const approval = await waitForApproval(endpoint, started, deadline, http, io);
|
|
27317
|
+
const tokens = await browserSignIn(
|
|
27318
|
+
{ region: config2.region, ...http },
|
|
27319
|
+
config2,
|
|
27320
|
+
approval,
|
|
27321
|
+
io
|
|
27322
|
+
);
|
|
27323
|
+
const me = await storeSession(tokens, endpoint, config2, http, io);
|
|
27324
|
+
return { me, webUrl: config2.webUrl };
|
|
26927
27325
|
}
|
|
26928
|
-
|
|
26929
|
-
|
|
26930
|
-
|
|
26931
|
-
|
|
26932
|
-
|
|
26933
|
-
|
|
27326
|
+
function postJson(url2, body, http) {
|
|
27327
|
+
return send(url2, {
|
|
27328
|
+
method: "POST",
|
|
27329
|
+
headers: { "content-type": "application/json" },
|
|
27330
|
+
body: JSON.stringify(body),
|
|
27331
|
+
...http
|
|
27332
|
+
});
|
|
27333
|
+
}
|
|
27334
|
+
async function startLogin(endpoint, webUrl, http, body) {
|
|
27335
|
+
const url2 = `${trimSlash2(endpoint)}${CLI_LOGINS_PATH}`;
|
|
27336
|
+
const site = { method: "POST", url: url2, debug: http.debug };
|
|
27337
|
+
const response = await postJson(url2, body, http);
|
|
27338
|
+
const text = await readText(response, site);
|
|
27339
|
+
if (response.status === 404) {
|
|
27340
|
+
throw new RemoteError(
|
|
27341
|
+
`${endpoint} does not support the browser login: run quibble login --password${detail(site, 404)}`,
|
|
27342
|
+
{ status: 404 }
|
|
27343
|
+
);
|
|
26934
27344
|
}
|
|
26935
|
-
|
|
26936
|
-
|
|
26937
|
-
|
|
27345
|
+
if (!response.ok) throw remoteFailure(site, response, text);
|
|
27346
|
+
const started = checkStart(asRecord(parseQuietly(text)), webUrl);
|
|
27347
|
+
if (started === void 0) {
|
|
27348
|
+
throw new RemoteError(`${MALFORMED_MESSAGE}${detail(site, response.status)}`);
|
|
27349
|
+
}
|
|
27350
|
+
return started;
|
|
27351
|
+
}
|
|
27352
|
+
function checkStart(body, webUrl) {
|
|
27353
|
+
const loginKey = body?.["loginKey"];
|
|
27354
|
+
const code2 = body?.["code"];
|
|
27355
|
+
const url2 = body?.["url"];
|
|
27356
|
+
if (typeof loginKey !== "string" || !CLI_LOGIN_KEY_RE.test(loginKey)) return void 0;
|
|
27357
|
+
if (typeof code2 !== "string" || typeof url2 !== "string") return void 0;
|
|
26938
27358
|
try {
|
|
26939
|
-
|
|
26940
|
-
|
|
26941
|
-
|
|
26942
|
-
|
|
26943
|
-
|
|
26944
|
-
|
|
26945
|
-
|
|
27359
|
+
if (parseCliLoginCode(code2) !== code2) return void 0;
|
|
27360
|
+
} catch {
|
|
27361
|
+
return void 0;
|
|
27362
|
+
}
|
|
27363
|
+
let origin;
|
|
27364
|
+
try {
|
|
27365
|
+
const web = new URL(webUrl);
|
|
27366
|
+
const secure = web.protocol === "https:" || web.protocol === "http:" && isLoopbackUrl(web.origin);
|
|
27367
|
+
if (!secure || !PLAIN_HOST_RE.test(web.host)) return void 0;
|
|
27368
|
+
origin = web.origin;
|
|
27369
|
+
} catch {
|
|
27370
|
+
return void 0;
|
|
27371
|
+
}
|
|
27372
|
+
if (url2 !== cliLoginUrl(origin, code2)) return void 0;
|
|
27373
|
+
const raw = body?.["interval"];
|
|
27374
|
+
const interval = typeof raw === "number" && Number.isInteger(raw) ? Math.min(MAX_INTERVAL_SECONDS, Math.max(MIN_INTERVAL_SECONDS, raw)) : CLI_LOGIN_POLL_SECONDS;
|
|
27375
|
+
return { loginKey, code: code2, url: url2, interval };
|
|
27376
|
+
}
|
|
27377
|
+
async function waitForApproval(endpoint, started, deadline, http, io) {
|
|
27378
|
+
const clock = clockOf2(io);
|
|
27379
|
+
const sleep = sleepOf(io);
|
|
27380
|
+
for (; ; ) {
|
|
27381
|
+
await sleep(started.interval * 1e3);
|
|
27382
|
+
if (clock().getTime() > deadline) throw new ConfigError(EXPIRED_MESSAGE);
|
|
27383
|
+
const answer2 = await poll(endpoint, started.loginKey, http);
|
|
27384
|
+
if (answer2 === void 0) continue;
|
|
27385
|
+
const status = answer2["status"];
|
|
27386
|
+
switch (status) {
|
|
27387
|
+
case "pending":
|
|
27388
|
+
continue;
|
|
27389
|
+
case "denied":
|
|
27390
|
+
throw new ConfigError(DENIED_MESSAGE);
|
|
27391
|
+
case "expired":
|
|
27392
|
+
throw new ConfigError(EXPIRED_MESSAGE);
|
|
27393
|
+
case "approved": {
|
|
27394
|
+
const username = answer2["username"];
|
|
27395
|
+
const secret = answer2["secret"];
|
|
27396
|
+
if (typeof username !== "string" || username === "") {
|
|
27397
|
+
throw new RemoteError(MALFORMED_MESSAGE);
|
|
27398
|
+
}
|
|
27399
|
+
if (typeof secret !== "string" || secret === "") {
|
|
27400
|
+
throw new RemoteError(MALFORMED_MESSAGE);
|
|
27401
|
+
}
|
|
27402
|
+
return { username, secret };
|
|
26946
27403
|
}
|
|
26947
|
-
|
|
26948
|
-
|
|
26949
|
-
);
|
|
26950
|
-
return { server: "no_project" };
|
|
27404
|
+
default:
|
|
27405
|
+
throw new RemoteError(UNKNOWN_STATUS_MESSAGE);
|
|
26951
27406
|
}
|
|
26952
|
-
warn(`could not check the project on ${endpoint}: ${describeError(error62)}`);
|
|
26953
|
-
return { server: "unreachable" };
|
|
26954
27407
|
}
|
|
26955
27408
|
}
|
|
26956
|
-
function
|
|
26957
|
-
const
|
|
26958
|
-
|
|
26959
|
-
|
|
27409
|
+
async function poll(endpoint, loginKey, http) {
|
|
27410
|
+
const url2 = `${trimSlash2(endpoint)}${CLI_LOGIN_POLL_PATH}`;
|
|
27411
|
+
const site = { method: "POST", url: url2, debug: http.debug };
|
|
27412
|
+
let response;
|
|
27413
|
+
let text;
|
|
26960
27414
|
try {
|
|
26961
|
-
|
|
27415
|
+
response = await postJson(url2, { loginKey }, http);
|
|
27416
|
+
text = await readText(response, site);
|
|
26962
27417
|
} catch (error62) {
|
|
26963
|
-
|
|
26964
|
-
|
|
26965
|
-
|
|
26966
|
-
|
|
26967
|
-
);
|
|
27418
|
+
if (error62 instanceof RemoteError && error62.unanswered) return void 0;
|
|
27419
|
+
throw error62;
|
|
27420
|
+
}
|
|
27421
|
+
if (!response.ok) {
|
|
27422
|
+
if (response.status >= 500 || response.status === 429) return void 0;
|
|
27423
|
+
throw remoteFailure(site, response, text);
|
|
26968
27424
|
}
|
|
27425
|
+
return asRecord(parseJson(text, site, response.status)) ?? {};
|
|
26969
27426
|
}
|
|
26970
|
-
|
|
26971
|
-
|
|
26972
|
-
|
|
26973
|
-
|
|
27427
|
+
async function browserSignIn(identity, config2, approval, io) {
|
|
27428
|
+
const clientId = config2.cliClientId;
|
|
27429
|
+
try {
|
|
27430
|
+
let result = await initiateAuth(identity, {
|
|
27431
|
+
clientId,
|
|
27432
|
+
flow: "CUSTOM_AUTH",
|
|
27433
|
+
username: approval.username
|
|
27434
|
+
});
|
|
27435
|
+
if (result.kind === "challenge") {
|
|
27436
|
+
const name = result.challenge.challengeName;
|
|
27437
|
+
if (name !== CUSTOM_CHALLENGE) throw unsupportedStep(name);
|
|
27438
|
+
result = await respondToCustomChallenge(identity, {
|
|
27439
|
+
clientId,
|
|
27440
|
+
challenge: result.challenge,
|
|
27441
|
+
username: approval.username,
|
|
27442
|
+
answer: approval.secret
|
|
27443
|
+
});
|
|
27444
|
+
}
|
|
27445
|
+
if (result.kind === "challenge" && result.challenge.challengeName === MFA_CHALLENGE) {
|
|
27446
|
+
result = await answerCode(
|
|
27447
|
+
identity,
|
|
27448
|
+
clientId,
|
|
27449
|
+
result.challenge,
|
|
27450
|
+
approval.username,
|
|
27451
|
+
io
|
|
27452
|
+
);
|
|
27453
|
+
}
|
|
27454
|
+
if (result.kind === "challenge") {
|
|
27455
|
+
throw unsupportedStep(result.challenge.challengeName);
|
|
27456
|
+
}
|
|
27457
|
+
return result.tokens;
|
|
27458
|
+
} catch (error62) {
|
|
27459
|
+
throw pageError(error62, config2.webUrl);
|
|
27460
|
+
}
|
|
26974
27461
|
}
|
|
26975
|
-
async function
|
|
26976
|
-
|
|
26977
|
-
|
|
26978
|
-
|
|
27462
|
+
async function answerCode(identity, clientId, challenge, username, io) {
|
|
27463
|
+
const terminal = terminalOf(io);
|
|
27464
|
+
try {
|
|
27465
|
+
if (!terminal.isTTY) throw new ConfigError(MFA_NEEDS_TERMINAL_MESSAGE);
|
|
27466
|
+
const code2 = await answer(terminal, "Code:");
|
|
27467
|
+
return await respondToAuthChallenge(identity, {
|
|
27468
|
+
clientId,
|
|
27469
|
+
challenge,
|
|
27470
|
+
email: username,
|
|
27471
|
+
code: code2
|
|
27472
|
+
});
|
|
27473
|
+
} finally {
|
|
27474
|
+
terminal.close();
|
|
26979
27475
|
}
|
|
26980
|
-
return (await client.me()).reporter;
|
|
26981
27476
|
}
|
|
26982
|
-
|
|
26983
|
-
|
|
26984
|
-
|
|
26985
|
-
|
|
26986
|
-
|
|
26987
|
-
|
|
26988
|
-
|
|
26989
|
-
|
|
26990
|
-
|
|
26991
|
-
|
|
26992
|
-
|
|
26993
|
-
|
|
26994
|
-
|
|
26995
|
-
|
|
26996
|
-
|
|
26997
|
-
|
|
26998
|
-
|
|
26999
|
-
|
|
27000
|
-
|
|
27001
|
-
|
|
27002
|
-
|
|
27003
|
-
|
|
27004
|
-
|
|
27005
|
-
|
|
27006
|
-
|
|
27007
|
-
|
|
27008
|
-
return
|
|
27477
|
+
async function storeSession(tokens, endpoint, config2, http, io) {
|
|
27478
|
+
if (tokens.refreshToken === void 0) {
|
|
27479
|
+
throw new RemoteError("sign-in didn't return a refresh token");
|
|
27480
|
+
}
|
|
27481
|
+
const now = clockOf2(io)();
|
|
27482
|
+
const me = await readProfile(
|
|
27483
|
+
new ApiClient({
|
|
27484
|
+
endpoint,
|
|
27485
|
+
authorizer: bearerAuthorizer(tokens.accessToken),
|
|
27486
|
+
...http
|
|
27487
|
+
}),
|
|
27488
|
+
config2.webUrl
|
|
27489
|
+
);
|
|
27490
|
+
if (me.sub === void 0 || me.email === void 0) {
|
|
27491
|
+
throw new RemoteError(
|
|
27492
|
+
"unexpected response from the API: identity has no sub or email"
|
|
27493
|
+
);
|
|
27494
|
+
}
|
|
27495
|
+
saveCredential(io.env, {
|
|
27496
|
+
endpoint,
|
|
27497
|
+
refreshToken: tokens.refreshToken,
|
|
27498
|
+
accessToken: tokens.accessToken,
|
|
27499
|
+
expiresAt: new Date(now.getTime() + tokens.expiresIn * 1e3).toISOString(),
|
|
27500
|
+
sub: me.sub,
|
|
27501
|
+
email: me.email
|
|
27502
|
+
});
|
|
27503
|
+
return me;
|
|
27009
27504
|
}
|
|
27010
|
-
function
|
|
27011
|
-
const
|
|
27012
|
-
if (
|
|
27013
|
-
|
|
27505
|
+
function reportLogin(me, endpoint, options, context, io) {
|
|
27506
|
+
const saved = saveEndpoint(context, endpoint, io.env);
|
|
27507
|
+
if (options.json === true) {
|
|
27508
|
+
writeJson(io.stdout, { ...me.body, endpoint, saved: saved ?? null });
|
|
27509
|
+
return 0;
|
|
27510
|
+
}
|
|
27511
|
+
writeLine(io.stdout, loggedInLine(endpoint, me));
|
|
27512
|
+
if (saved !== void 0) {
|
|
27513
|
+
writeLine(io.stdout, `Saved ${endpoint} as your endpoint in ${saved}.`);
|
|
27514
|
+
}
|
|
27515
|
+
return 0;
|
|
27014
27516
|
}
|
|
27015
|
-
function
|
|
27016
|
-
|
|
27017
|
-
const stamp2 = iso(at);
|
|
27018
|
-
if (!stamp2.startsWith(value)) reject();
|
|
27019
|
-
return stamp2;
|
|
27517
|
+
function loggedInLine(endpoint, me) {
|
|
27518
|
+
return `Logged in to ${endpoint} as ${me.email ?? me.reporter}.`;
|
|
27020
27519
|
}
|
|
27021
|
-
function
|
|
27022
|
-
|
|
27023
|
-
|
|
27024
|
-
if (duration3 !== null) {
|
|
27025
|
-
return fromDuration(duration3[1] ?? "", duration3[2] ?? "", now);
|
|
27520
|
+
function saveEndpoint(context, endpoint, env) {
|
|
27521
|
+
if (context.endpointSource !== "--endpoint" || isDefaultEndpoint(endpoint)) {
|
|
27522
|
+
return void 0;
|
|
27026
27523
|
}
|
|
27027
|
-
|
|
27028
|
-
if (TIMESTAMP_RE.test(value)) return iso(new Date(value));
|
|
27029
|
-
return reject();
|
|
27524
|
+
return rememberEndpoint(env, endpoint);
|
|
27030
27525
|
}
|
|
27031
|
-
function
|
|
27032
|
-
const value = text.trim();
|
|
27033
|
-
const duration3 = DURATION_RE.exec(value);
|
|
27034
|
-
let stamp2;
|
|
27526
|
+
async function readProfile(client, webUrl) {
|
|
27035
27527
|
try {
|
|
27036
|
-
|
|
27037
|
-
|
|
27038
|
-
|
|
27039
|
-
|
|
27040
|
-
} else if (DATE_RE.test(value)) {
|
|
27041
|
-
stamp2 = fromDate(value);
|
|
27042
|
-
} else if (TIMESTAMP_RE.test(value)) {
|
|
27043
|
-
stamp2 = iso(new Date(value));
|
|
27044
|
-
} else {
|
|
27045
|
-
return reject(EXPIRES_MESSAGE);
|
|
27528
|
+
return await client.me();
|
|
27529
|
+
} catch (error62) {
|
|
27530
|
+
if (error62 instanceof RemoteError && NO_PROFILE_STATUSES.has(error62.status ?? 0)) {
|
|
27531
|
+
throw new ConfigError(`finish your account at ${trimSlash2(webUrl)}${LOGIN_PAGE}`);
|
|
27046
27532
|
}
|
|
27047
|
-
|
|
27048
|
-
return reject(EXPIRES_MESSAGE);
|
|
27533
|
+
throw error62;
|
|
27049
27534
|
}
|
|
27050
|
-
return stamp2;
|
|
27051
27535
|
}
|
|
27052
|
-
|
|
27053
|
-
|
|
27054
|
-
|
|
27055
|
-
|
|
27056
|
-
|
|
27536
|
+
async function runLogout(context, io) {
|
|
27537
|
+
requireServer("logout", context);
|
|
27538
|
+
const endpoint = await resolveEndpoint(context, io);
|
|
27539
|
+
const credential = findCredential(io.env, endpoint);
|
|
27540
|
+
if (credential === void 0) {
|
|
27541
|
+
writeLine(io.stdout, `Not logged in to ${endpoint}.`);
|
|
27542
|
+
return 0;
|
|
27057
27543
|
}
|
|
27058
|
-
|
|
27059
|
-
|
|
27544
|
+
const http = { debug: context.debug, fetch: io.fetch };
|
|
27545
|
+
if (!await revoke(endpoint, credential.refreshToken, http)) {
|
|
27546
|
+
writeLine(
|
|
27547
|
+
io.stderr,
|
|
27548
|
+
`quibble: warning: could not revoke the refresh token at ${endpoint}`
|
|
27549
|
+
);
|
|
27060
27550
|
}
|
|
27061
|
-
|
|
27062
|
-
|
|
27063
|
-
return
|
|
27064
|
-
// No list reads two organizations, because the path names one.
|
|
27065
|
-
org: requireOrg(context),
|
|
27066
|
-
...project === void 0 ? {} : { project },
|
|
27067
|
-
...status === void 0 ? {} : { status },
|
|
27068
|
-
...options.since === void 0 ? {} : { since: parseSince(options.since, now) }
|
|
27069
|
-
};
|
|
27070
|
-
}
|
|
27071
|
-
function tagFilter(tags) {
|
|
27072
|
-
return tags === void 0 || tags.length === 0 ? {} : { tags: [...tags] };
|
|
27073
|
-
}
|
|
27074
|
-
|
|
27075
|
-
// src/commands/list.ts
|
|
27076
|
-
var COLUMNS = [
|
|
27077
|
-
"id",
|
|
27078
|
-
"created",
|
|
27079
|
-
"severity",
|
|
27080
|
-
"status",
|
|
27081
|
-
"project",
|
|
27082
|
-
"reporter",
|
|
27083
|
-
"tags",
|
|
27084
|
-
"group",
|
|
27085
|
-
"summary"
|
|
27086
|
-
];
|
|
27087
|
-
var REPORTER_WIDTH = 20;
|
|
27088
|
-
var TAGS_WIDTH = 24;
|
|
27089
|
-
var SUMMARY_MAX = 60;
|
|
27090
|
-
var RECURRED_MARK = "\u21BB";
|
|
27091
|
-
function cut2(text, width) {
|
|
27092
|
-
if (text.length <= width) return text;
|
|
27093
|
-
return `${text.slice(0, width - 3)}...`;
|
|
27094
|
-
}
|
|
27095
|
-
function summaryOf(complaint) {
|
|
27096
|
-
return cut2(printable(summarize(complaint.message)), SUMMARY_MAX);
|
|
27551
|
+
removeCredential(io.env, endpoint);
|
|
27552
|
+
writeLine(io.stdout, `Logged out of ${endpoint}.`);
|
|
27553
|
+
return 0;
|
|
27097
27554
|
}
|
|
27098
|
-
function
|
|
27099
|
-
|
|
27100
|
-
|
|
27555
|
+
async function revoke(endpoint, refreshToken, http) {
|
|
27556
|
+
try {
|
|
27557
|
+
const config2 = await fetchAuthConfig(endpoint, http);
|
|
27558
|
+
if (config2.mode === "local") return true;
|
|
27559
|
+
await revokeToken(
|
|
27560
|
+
{ region: config2.region, ...http },
|
|
27561
|
+
{ clientId: config2.cliClientId, token: refreshToken }
|
|
27562
|
+
);
|
|
27563
|
+
return true;
|
|
27564
|
+
} catch {
|
|
27565
|
+
return false;
|
|
27101
27566
|
}
|
|
27102
|
-
return more ? `Showing the newest ${limit} complaints: pass --limit to see more (up to ${MAX_LIMIT}).` : void 0;
|
|
27103
27567
|
}
|
|
27104
|
-
|
|
27105
|
-
|
|
27106
|
-
|
|
27107
|
-
|
|
27108
|
-
}
|
|
27109
|
-
const { store, client } = await openBackend(context, io);
|
|
27110
|
-
const reporter = options.mine === true ? await resolveMine(context, client, io.env) : void 0;
|
|
27111
|
-
const filter = parseListFilter({
|
|
27112
|
-
...resolveScope(options, context),
|
|
27113
|
-
...options.severity === void 0 ? {} : { severity: options.severity },
|
|
27114
|
-
...tagFilter(options.tag),
|
|
27115
|
-
...options.search === void 0 ? {} : { q: options.search },
|
|
27116
|
-
...reporter === void 0 ? {} : { reporter },
|
|
27117
|
-
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
27118
|
-
});
|
|
27119
|
-
const limit = filter.limit ?? DEFAULT_LIMIT;
|
|
27120
|
-
const page = await listComplaintPage(store, {
|
|
27121
|
-
...filter,
|
|
27122
|
-
limit: Math.min(limit + 1, MAX_LIMIT)
|
|
27123
|
-
});
|
|
27124
|
-
const complaints = page.complaints.slice(0, limit);
|
|
27125
|
-
if (options.json === true) {
|
|
27126
|
-
writeJson(io.stdout, complaints);
|
|
27568
|
+
async function runWhoami(options, context, io) {
|
|
27569
|
+
if (context.mode === "local") {
|
|
27570
|
+
checkActAs(context);
|
|
27571
|
+
writeLine(io.stdout, LOCAL_IDENTITY_MESSAGE);
|
|
27127
27572
|
return 0;
|
|
27128
27573
|
}
|
|
27129
|
-
const
|
|
27130
|
-
|
|
27131
|
-
|
|
27132
|
-
|
|
27133
|
-
|
|
27574
|
+
const client = await openClient(context, io);
|
|
27575
|
+
const endpoint = requireEndpoint(context);
|
|
27576
|
+
const me = await client.me();
|
|
27577
|
+
const org = me.kind === "token" || client.org === void 0 ? void 0 : await client.organization();
|
|
27578
|
+
if (options.json === true) {
|
|
27579
|
+
writeJson(io.stdout, {
|
|
27580
|
+
...me.body,
|
|
27581
|
+
endpoint,
|
|
27582
|
+
...org === void 0 ? {} : { org: org.body }
|
|
27583
|
+
});
|
|
27134
27584
|
return 0;
|
|
27135
27585
|
}
|
|
27136
|
-
|
|
27137
|
-
|
|
27138
|
-
|
|
27586
|
+
const lines = whoamiLines(endpoint, me, org);
|
|
27587
|
+
if (me.kind !== "token" && me.orgs.length === 0) {
|
|
27588
|
+
const config2 = await fetchAuthConfig(endpoint, {
|
|
27589
|
+
debug: context.debug,
|
|
27590
|
+
fetch: io.fetch
|
|
27591
|
+
});
|
|
27592
|
+
const webUrl = config2.mode === "cognito" ? trimSlash2(config2.webUrl) : endpoint;
|
|
27593
|
+
lines.push("", `Create one at ${webUrl}${ORGS_PAGE}.`);
|
|
27139
27594
|
}
|
|
27140
|
-
const lines = options.ungroup === true ? formatTable(complaints.map((complaint) => ({ complaint }))) : formatTable(
|
|
27141
|
-
groupComplaints(complaints).map((group) => ({
|
|
27142
|
-
complaint: group.head,
|
|
27143
|
-
group
|
|
27144
|
-
}))
|
|
27145
|
-
);
|
|
27146
27595
|
for (const line of lines) writeLine(io.stdout, line);
|
|
27147
27596
|
return 0;
|
|
27148
27597
|
}
|
|
27149
|
-
function
|
|
27150
|
-
|
|
27598
|
+
function rows(pairs) {
|
|
27599
|
+
const width = Math.max(...pairs.map(([key]) => key.length));
|
|
27600
|
+
return pairs.map(([key, value]) => `${key.padEnd(width)} ${value}`);
|
|
27151
27601
|
}
|
|
27152
|
-
function
|
|
27153
|
-
|
|
27154
|
-
|
|
27602
|
+
function block(entries) {
|
|
27603
|
+
if (entries.length === 0) return [" none"];
|
|
27604
|
+
const width = Math.max(...entries.map(([key]) => key.length));
|
|
27605
|
+
return entries.map(([key, value]) => ` ${key.padEnd(width)} ${value}`);
|
|
27155
27606
|
}
|
|
27156
|
-
function
|
|
27157
|
-
|
|
27158
|
-
|
|
27159
|
-
|
|
27160
|
-
|
|
27161
|
-
|
|
27162
|
-
|
|
27163
|
-
|
|
27164
|
-
|
|
27165
|
-
|
|
27166
|
-
|
|
27167
|
-
|
|
27168
|
-
|
|
27169
|
-
|
|
27170
|
-
|
|
27171
|
-
|
|
27172
|
-
|
|
27173
|
-
|
|
27174
|
-
|
|
27175
|
-
|
|
27176
|
-
|
|
27177
|
-
|
|
27178
|
-
|
|
27179
|
-
const format = (line) => line.map((cell2, column) => cell2.padEnd(widths[column] ?? 0)).join(" ").trimEnd();
|
|
27180
|
-
return [format(columns), ...cells.map(format)];
|
|
27607
|
+
function whoamiLines(endpoint, me, org) {
|
|
27608
|
+
if (me.kind === "token") {
|
|
27609
|
+
return rows([
|
|
27610
|
+
["endpoint", endpoint],
|
|
27611
|
+
["token", me.name ?? me.reporter],
|
|
27612
|
+
["org", me.org ?? ""],
|
|
27613
|
+
["project", me.project ?? ""],
|
|
27614
|
+
["role", me.role ?? "filer"]
|
|
27615
|
+
]);
|
|
27616
|
+
}
|
|
27617
|
+
const lines = rows([
|
|
27618
|
+
["endpoint", endpoint],
|
|
27619
|
+
["email", me.email ?? ""],
|
|
27620
|
+
["name", me.name ?? ""],
|
|
27621
|
+
["sub", me.sub ?? ""],
|
|
27622
|
+
["platform", me.platform ? "yes" : "no"]
|
|
27623
|
+
]);
|
|
27624
|
+
lines.push("", "Organizations");
|
|
27625
|
+
lines.push(...block(me.orgs.map((entry) => [entry.org, entry.role])));
|
|
27626
|
+
if (org === void 0) return lines;
|
|
27627
|
+
lines.push("", `Roles in ${org.org}`);
|
|
27628
|
+
lines.push(...block(org.roles.map((entry) => [entry.project, entry.role])));
|
|
27629
|
+
return lines;
|
|
27181
27630
|
}
|
|
27182
27631
|
|
|
27183
|
-
// src/commands/
|
|
27184
|
-
|
|
27185
|
-
|
|
27186
|
-
|
|
27187
|
-
|
|
27188
|
-
|
|
27189
|
-
|
|
27190
|
-
|
|
27191
|
-
|
|
27192
|
-
|
|
27193
|
-
return
|
|
27632
|
+
// src/commands/snippet.ts
|
|
27633
|
+
import { existsSync, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
27634
|
+
import { dirname as dirname6, join as join6, resolve as resolve2 } from "node:path";
|
|
27635
|
+
function existsError(path) {
|
|
27636
|
+
return new UsageError(`${path} already exists: pass --force to overwrite it`);
|
|
27637
|
+
}
|
|
27638
|
+
function readOptional2(path) {
|
|
27639
|
+
try {
|
|
27640
|
+
return readFileSync5(path, "utf8");
|
|
27641
|
+
} catch (error62) {
|
|
27642
|
+
if (isNotFound(error62)) return "";
|
|
27643
|
+
throw error62;
|
|
27194
27644
|
}
|
|
27195
|
-
|
|
27196
|
-
|
|
27197
|
-
|
|
27198
|
-
|
|
27199
|
-
|
|
27645
|
+
}
|
|
27646
|
+
function writeSkill(path, snippet) {
|
|
27647
|
+
mkdirSync5(dirname6(path), { recursive: true });
|
|
27648
|
+
writeFileSync4(path, skillFile(snippet), "utf8");
|
|
27649
|
+
}
|
|
27650
|
+
async function snippetSource(project, context, io, options = {}) {
|
|
27651
|
+
let tags;
|
|
27652
|
+
if (context.mode === "remote") {
|
|
27653
|
+
tags = (await (await openOrgClient(context, io, options)).project(project)).tags;
|
|
27654
|
+
} else {
|
|
27655
|
+
checkActAs(context);
|
|
27656
|
+
tags = context.allowedTags;
|
|
27657
|
+
}
|
|
27658
|
+
const scope = {
|
|
27659
|
+
...context.mode === "remote" ? { endpoint: context.endpoint } : {},
|
|
27660
|
+
...context.slug === void 0 ? {} : { org: context.slug }
|
|
27661
|
+
};
|
|
27662
|
+
return { tags, scope };
|
|
27663
|
+
}
|
|
27664
|
+
async function runSnippet(options, context, io) {
|
|
27665
|
+
const project = requireProject(context);
|
|
27666
|
+
const override = options.agent === void 0 ? void 0 : parseAgentName(options.agent);
|
|
27667
|
+
const appendPath = options.append === void 0 ? void 0 : resolve2(io.cwd, options.append);
|
|
27668
|
+
const skillPath = options.skill === true ? join6(context.projectRoot ?? io.cwd, SKILL_PATH) : void 0;
|
|
27669
|
+
const existing = appendPath === void 0 ? "" : readOptional2(appendPath);
|
|
27670
|
+
if (options.append !== void 0 && hasSnippet(existing)) {
|
|
27671
|
+
throw new UsageError(
|
|
27672
|
+
`${options.append} already has a "${SNIPPET_HEADING}" section: run quibble sync to refresh it`
|
|
27673
|
+
);
|
|
27674
|
+
}
|
|
27675
|
+
if (skillPath !== void 0 && existsSync(skillPath) && options.force !== true) {
|
|
27676
|
+
throw existsError(skillPath);
|
|
27677
|
+
}
|
|
27678
|
+
const hook = options.hook === true ? planHook(context.projectRoot ?? io.cwd) : void 0;
|
|
27679
|
+
const { tags, scope } = await snippetSource(project, context, io);
|
|
27680
|
+
let shown = { agent: override ?? DEFAULT_SNIPPET_AGENT, snippet: "" };
|
|
27681
|
+
shown.snippet = agentSnippet(tags, { ...scope, agent: shown.agent });
|
|
27682
|
+
if (appendPath !== void 0) {
|
|
27683
|
+
const agent = override ?? agentForFile(appendPath);
|
|
27684
|
+
const snippet = agentSnippet(tags, { ...scope, agent });
|
|
27685
|
+
const kept = existing.replace(/\s+$/, "");
|
|
27686
|
+
writeFileSync4(appendPath, kept === "" ? `${snippet}
|
|
27687
|
+
` : `${kept}
|
|
27688
|
+
|
|
27689
|
+
${snippet}
|
|
27690
|
+
`);
|
|
27691
|
+
shown = { agent, snippet };
|
|
27692
|
+
}
|
|
27693
|
+
if (skillPath !== void 0) {
|
|
27694
|
+
const agent = override ?? DEFAULT_SNIPPET_AGENT;
|
|
27695
|
+
const snippet = agentSnippet(tags, { ...scope, agent });
|
|
27696
|
+
writeSkill(skillPath, snippet);
|
|
27697
|
+
if (appendPath === void 0) shown = { agent, snippet };
|
|
27698
|
+
}
|
|
27699
|
+
const hookLine = hook === void 0 ? void 0 : applyHook(hook);
|
|
27700
|
+
if (options.json === true) {
|
|
27701
|
+
writeJson(io.stdout, {
|
|
27702
|
+
snippet: shown.snippet,
|
|
27703
|
+
agent: shown.agent,
|
|
27704
|
+
appended: appendPath ?? null,
|
|
27705
|
+
skill: skillPath ?? null,
|
|
27706
|
+
hook: hook?.path ?? null
|
|
27200
27707
|
});
|
|
27201
|
-
const webUrl = config2.mode === "cognito" ? trimSlash2(config2.webUrl) : endpoint;
|
|
27202
|
-
writeLine(io.stdout, `No organizations yet: create one at ${webUrl}${ORGS_PAGE}.`);
|
|
27203
27708
|
return 0;
|
|
27204
27709
|
}
|
|
27205
|
-
|
|
27710
|
+
if (appendPath === void 0 && skillPath === void 0) {
|
|
27711
|
+
writeLine(io.stdout, shown.snippet);
|
|
27712
|
+
}
|
|
27713
|
+
if (appendPath !== void 0) {
|
|
27714
|
+
writeLine(io.stdout, `Appended the Quibble snippet to ${appendPath}`);
|
|
27715
|
+
}
|
|
27716
|
+
if (skillPath !== void 0) writeLine(io.stdout, `Wrote ${skillPath}`);
|
|
27717
|
+
if (hookLine !== void 0) writeLine(io.stdout, hookLine);
|
|
27206
27718
|
return 0;
|
|
27207
27719
|
}
|
|
27208
|
-
function formatOrgs(orgs) {
|
|
27209
|
-
const rows2 = [...orgs].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map((entry) => [
|
|
27210
|
-
entry.org,
|
|
27211
|
-
entry.name,
|
|
27212
|
-
entry.role,
|
|
27213
|
-
entry.openCount === void 0 ? "" : String(entry.openCount)
|
|
27214
|
-
]);
|
|
27215
|
-
const widths = COLUMNS2.map(
|
|
27216
|
-
(name, column) => rows2.reduce((width, row) => Math.max(width, (row[column] ?? "").length), name.length)
|
|
27217
|
-
);
|
|
27218
|
-
const format = (cells) => cells.map(
|
|
27219
|
-
(cell2, column) => column === COLUMNS2.length - 1 ? cell2.padStart(widths[column] ?? 0) : cell2.padEnd(widths[column] ?? 0)
|
|
27220
|
-
).join(" ").trimEnd();
|
|
27221
|
-
return [format(COLUMNS2), ...rows2.map(format)];
|
|
27222
|
-
}
|
|
27223
27720
|
|
|
27224
|
-
// src/
|
|
27225
|
-
|
|
27226
|
-
|
|
27227
|
-
var
|
|
27228
|
-
|
|
27229
|
-
|
|
27230
|
-
|
|
27231
|
-
|
|
27232
|
-
|
|
27233
|
-
|
|
27234
|
-
|
|
27235
|
-
|
|
27236
|
-
|
|
27237
|
-
|
|
27238
|
-
|
|
27239
|
-
|
|
27240
|
-
|
|
27241
|
-
|
|
27242
|
-
|
|
27243
|
-
this.#output = output2;
|
|
27244
|
-
this.isTTY = input2.isTTY === true;
|
|
27721
|
+
// src/commands/init.ts
|
|
27722
|
+
var CODEX_LINE = "For Codex, run `quibble snippet --append AGENTS.md`, which names the agent codex.";
|
|
27723
|
+
var LOGIN_PAGE2 = "/login";
|
|
27724
|
+
var ORGS_PAGE2 = "/orgs";
|
|
27725
|
+
var CONFLICT = "conflict";
|
|
27726
|
+
function agentLead(agent) {
|
|
27727
|
+
return agent === DEFAULT_SNIPPET_AGENT ? "Add this to your CLAUDE.md:" : "Add this to your agent's instructions file:";
|
|
27728
|
+
}
|
|
27729
|
+
var SOURCE_NOTES = {
|
|
27730
|
+
flag: "",
|
|
27731
|
+
git: " (from the git remote origin)",
|
|
27732
|
+
directory: " (from the directory name)",
|
|
27733
|
+
token: " (from the project token)"
|
|
27734
|
+
};
|
|
27735
|
+
async function runInit(options, io) {
|
|
27736
|
+
const directory = resolve3(io.cwd);
|
|
27737
|
+
const path = join7(directory, PROJECT_CONFIG_FILENAME);
|
|
27738
|
+
if (existsSync2(path) && options.force !== true) {
|
|
27739
|
+
throw new ConfigError(`${path} already exists: pass --force to overwrite it`, path);
|
|
27245
27740
|
}
|
|
27246
|
-
|
|
27247
|
-
|
|
27741
|
+
const skillPath = options.skill === true ? join7(directory, SKILL_PATH) : void 0;
|
|
27742
|
+
if (skillPath !== void 0 && existsSync2(skillPath) && options.force !== true) {
|
|
27743
|
+
throw existsError(skillPath);
|
|
27248
27744
|
}
|
|
27249
|
-
|
|
27250
|
-
|
|
27745
|
+
const given = options.project?.trim();
|
|
27746
|
+
const flagProject = given === void 0 || given === "" ? void 0 : parseProjectName(given);
|
|
27747
|
+
let org = options.org === void 0 || options.org.trim() === "" ? void 0 : parseOrgSlug(options.org);
|
|
27748
|
+
const tags = options.tag === void 0 ? void 0 : parseAllowedTags(options.tag);
|
|
27749
|
+
const agent = options.agent === void 0 ? DEFAULT_SNIPPET_AGENT : parseAgentName(options.agent);
|
|
27750
|
+
const hook = options.hook === true ? planHook(directory) : void 0;
|
|
27751
|
+
const endpoint = await endpointToWrite(options.endpoint, io);
|
|
27752
|
+
const context = {
|
|
27753
|
+
...resolveContext({
|
|
27754
|
+
cwd: io.cwd,
|
|
27755
|
+
env: io.env,
|
|
27756
|
+
endpoint: options.endpoint,
|
|
27757
|
+
local: options.local,
|
|
27758
|
+
readProjectConfig: false
|
|
27759
|
+
}),
|
|
27760
|
+
// `init` acts as nobody, so it ignores `QUIBBLE_ACT_AS`.
|
|
27761
|
+
actAs: void 0
|
|
27762
|
+
};
|
|
27763
|
+
let named;
|
|
27764
|
+
let nameRefusal;
|
|
27765
|
+
if (flagProject !== void 0) {
|
|
27766
|
+
named = { project: flagProject, source: "flag" };
|
|
27767
|
+
} else {
|
|
27768
|
+
try {
|
|
27769
|
+
named = await nameFromRepository(directory, io);
|
|
27770
|
+
} catch (error62) {
|
|
27771
|
+
if (!(error62 instanceof ConfigError) || context.token === void 0) throw error62;
|
|
27772
|
+
nameRefusal = error62;
|
|
27773
|
+
}
|
|
27251
27774
|
}
|
|
27252
|
-
|
|
27253
|
-
|
|
27254
|
-
|
|
27255
|
-
|
|
27775
|
+
let remote;
|
|
27776
|
+
if (context.mode === "remote") {
|
|
27777
|
+
const login = await logIn(context, options, io);
|
|
27778
|
+
if (login === "local") return 0;
|
|
27779
|
+
remote = login;
|
|
27256
27780
|
}
|
|
27257
|
-
|
|
27258
|
-
|
|
27259
|
-
|
|
27260
|
-
|
|
27261
|
-
|
|
27262
|
-
|
|
27263
|
-
|
|
27264
|
-
|
|
27265
|
-
|
|
27266
|
-
|
|
27267
|
-
|
|
27268
|
-
|
|
27269
|
-
|
|
27270
|
-
}
|
|
27271
|
-
|
|
27272
|
-
|
|
27273
|
-
|
|
27274
|
-
|
|
27275
|
-
|
|
27276
|
-
|
|
27277
|
-
}
|
|
27278
|
-
|
|
27279
|
-
|
|
27280
|
-
|
|
27281
|
-
|
|
27282
|
-
|
|
27283
|
-
|
|
27284
|
-
|
|
27285
|
-
|
|
27286
|
-
|
|
27287
|
-
|
|
27288
|
-
|
|
27289
|
-
|
|
27290
|
-
|
|
27291
|
-
|
|
27292
|
-
|
|
27293
|
-
|
|
27294
|
-
|
|
27295
|
-
|
|
27296
|
-
|
|
27781
|
+
if (org === void 0 && remote?.client !== void 0) {
|
|
27782
|
+
const account = await findOrganization(remote.client, remote, context, io);
|
|
27783
|
+
org = account.org;
|
|
27784
|
+
if (account.project !== void 0 && flagProject === void 0) {
|
|
27785
|
+
named = { project: account.project, source: "token" };
|
|
27786
|
+
}
|
|
27787
|
+
}
|
|
27788
|
+
if (named === void 0) throw nameRefusal;
|
|
27789
|
+
const { project, source } = named;
|
|
27790
|
+
const config2 = {
|
|
27791
|
+
project,
|
|
27792
|
+
...org === void 0 ? {} : { org },
|
|
27793
|
+
...endpoint === void 0 ? {} : { endpoint },
|
|
27794
|
+
...tags === void 0 ? {} : { tags }
|
|
27795
|
+
};
|
|
27796
|
+
writeFileSync5(path, `${JSON.stringify(config2, null, 2)}
|
|
27797
|
+
`, "utf8");
|
|
27798
|
+
const snippet = agentSnippet(tags, {
|
|
27799
|
+
agent,
|
|
27800
|
+
...context.mode === "remote" ? { endpoint: context.endpoint } : {},
|
|
27801
|
+
...org === void 0 ? {} : { org }
|
|
27802
|
+
});
|
|
27803
|
+
if (skillPath !== void 0) writeSkill(skillPath, snippet);
|
|
27804
|
+
const hookLine = hook === void 0 ? void 0 : applyHook(hook);
|
|
27805
|
+
const check2 = remote?.client === void 0 || org === void 0 ? { server: "unchecked" } : await checkServer(remote.client.forOrg(org), remote.endpoint, context, {
|
|
27806
|
+
org,
|
|
27807
|
+
project,
|
|
27808
|
+
tags,
|
|
27809
|
+
io
|
|
27810
|
+
});
|
|
27811
|
+
if (options.json === true) {
|
|
27812
|
+
writeJson(io.stdout, {
|
|
27813
|
+
path,
|
|
27814
|
+
project,
|
|
27815
|
+
projectSource: source,
|
|
27816
|
+
org: org ?? null,
|
|
27817
|
+
tags: tags ?? null,
|
|
27818
|
+
skill: skillPath ?? null,
|
|
27819
|
+
hook: hook?.path ?? null,
|
|
27820
|
+
server: check2.server
|
|
27297
27821
|
});
|
|
27298
|
-
|
|
27299
|
-
return rl;
|
|
27822
|
+
return 0;
|
|
27300
27823
|
}
|
|
27301
|
-
|
|
27302
|
-
|
|
27303
|
-
|
|
27304
|
-
|
|
27305
|
-
|
|
27306
|
-
|
|
27307
|
-
|
|
27308
|
-
|
|
27824
|
+
writeLine(io.stdout, `Wrote ${path}`);
|
|
27825
|
+
if (skillPath !== void 0) writeLine(io.stdout, `Wrote ${skillPath}`);
|
|
27826
|
+
if (hookLine !== void 0) writeLine(io.stdout, hookLine);
|
|
27827
|
+
writeLine(io.stdout, `Project: ${project}${SOURCE_NOTES[source]}`);
|
|
27828
|
+
if (org !== void 0) writeLine(io.stdout, `Organization: ${org}`);
|
|
27829
|
+
if (tags !== void 0) writeLine(io.stdout, `Tags: ${tags.join(", ")}`);
|
|
27830
|
+
writeLine(io.stdout);
|
|
27831
|
+
writeLine(io.stdout, agentLead(agent));
|
|
27832
|
+
writeLine(io.stdout);
|
|
27833
|
+
writeLine(io.stdout, snippet);
|
|
27834
|
+
if (agent === DEFAULT_SNIPPET_AGENT) {
|
|
27835
|
+
writeLine(io.stdout);
|
|
27836
|
+
writeLine(io.stdout, CODEX_LINE);
|
|
27309
27837
|
}
|
|
27310
|
-
|
|
27311
|
-
|
|
27312
|
-
|
|
27313
|
-
if (hidden) {
|
|
27314
|
-
this.#output.write(`${label} `);
|
|
27315
|
-
rl.setPrompt("");
|
|
27316
|
-
rl.prompt();
|
|
27317
|
-
try {
|
|
27318
|
-
return await this.#next();
|
|
27319
|
-
} finally {
|
|
27320
|
-
this.#output.write("\n");
|
|
27321
|
-
}
|
|
27322
|
-
}
|
|
27323
|
-
const queued = this.#lines.shift();
|
|
27324
|
-
if (queued !== void 0) {
|
|
27325
|
-
this.#output.write(`${label} ${queued}
|
|
27326
|
-
`);
|
|
27327
|
-
return queued;
|
|
27328
|
-
}
|
|
27329
|
-
rl.setPrompt(`${label} `);
|
|
27330
|
-
this.#muted = false;
|
|
27331
|
-
rl.prompt();
|
|
27332
|
-
try {
|
|
27333
|
-
return await this.#next();
|
|
27334
|
-
} finally {
|
|
27335
|
-
this.#muted = true;
|
|
27336
|
-
}
|
|
27838
|
+
if (check2.line !== void 0) {
|
|
27839
|
+
writeLine(io.stdout);
|
|
27840
|
+
writeLine(io.stdout, check2.line);
|
|
27337
27841
|
}
|
|
27338
|
-
|
|
27339
|
-
function nodeTerminal(input2, output2) {
|
|
27340
|
-
return new NodeTerminal(input2, output2);
|
|
27341
|
-
}
|
|
27342
|
-
|
|
27343
|
-
// src/commands/login.ts
|
|
27344
|
-
var LOCAL_IDENTITY_MESSAGE = "local mode: no identity.";
|
|
27345
|
-
var NEEDS_TERMINAL_MESSAGE = "quibble login needs a terminal: in CI, set QUIBBLE_TOKEN instead";
|
|
27346
|
-
var MFA_CHALLENGE = "SOFTWARE_TOKEN_MFA";
|
|
27347
|
-
var NEW_PASSWORD_CHALLENGE = "NEW_PASSWORD_REQUIRED";
|
|
27348
|
-
var LOGIN_PAGE = "/login";
|
|
27349
|
-
var ORGS_PAGE2 = "/orgs";
|
|
27350
|
-
var NO_PROFILE_STATUSES = /* @__PURE__ */ new Set([404, 409]);
|
|
27351
|
-
function terminalOf(io) {
|
|
27352
|
-
return io.terminal ?? nodeTerminal(io.stdin, io.stderr);
|
|
27842
|
+
return 0;
|
|
27353
27843
|
}
|
|
27354
|
-
async function
|
|
27355
|
-
|
|
27356
|
-
const value =
|
|
27357
|
-
if (value === "")
|
|
27358
|
-
|
|
27359
|
-
|
|
27844
|
+
async function endpointToWrite(given, io) {
|
|
27845
|
+
if (given === void 0 || given.trim() === "") return void 0;
|
|
27846
|
+
const value = parseEndpoint(given, "--endpoint");
|
|
27847
|
+
if (value.kind === "local") return LOCAL_ENDPOINT;
|
|
27848
|
+
const api = await discoverApiUrl(value.url, {
|
|
27849
|
+
fetch: io.fetch,
|
|
27850
|
+
debug: debugEnabled(io.env)
|
|
27851
|
+
});
|
|
27852
|
+
return isDefaultEndpoint(api) ? void 0 : api;
|
|
27360
27853
|
}
|
|
27361
|
-
async function
|
|
27362
|
-
const
|
|
27363
|
-
const
|
|
27364
|
-
|
|
27854
|
+
async function nameFromRepository(directory, io) {
|
|
27855
|
+
const url2 = await (io.git ?? nodeGit()).originUrl(directory);
|
|
27856
|
+
const fromGit = url2 === void 0 ? void 0 : projectOfRemote(url2);
|
|
27857
|
+
if (fromGit !== void 0) return { project: fromGit, source: "git" };
|
|
27858
|
+
const name = basename2(directory);
|
|
27365
27859
|
try {
|
|
27366
|
-
|
|
27367
|
-
clientId,
|
|
27368
|
-
flow: "USER_PASSWORD_AUTH",
|
|
27369
|
-
username: email3,
|
|
27370
|
-
password
|
|
27371
|
-
});
|
|
27372
|
-
if (result.kind === "challenge" && result.challenge.challengeName === MFA_CHALLENGE) {
|
|
27373
|
-
const code2 = await answer(terminal, "Code:");
|
|
27374
|
-
result = await respondToAuthChallenge(identity, {
|
|
27375
|
-
clientId,
|
|
27376
|
-
challenge: result.challenge,
|
|
27377
|
-
email: email3,
|
|
27378
|
-
code: code2
|
|
27379
|
-
});
|
|
27380
|
-
}
|
|
27381
|
-
if (result.kind === "challenge") {
|
|
27382
|
-
const name = result.challenge.challengeName;
|
|
27383
|
-
if (name === NEW_PASSWORD_CHALLENGE) {
|
|
27384
|
-
throw new ConfigError(
|
|
27385
|
-
`finish your first sign-in at ${trimSlash2(config2.webUrl)}${LOGIN_PAGE}`
|
|
27386
|
-
);
|
|
27387
|
-
}
|
|
27388
|
-
throw new RemoteError(
|
|
27389
|
-
`this account needs a sign-in step that quibble doesn't support (${name})`
|
|
27390
|
-
);
|
|
27391
|
-
}
|
|
27392
|
-
return result.tokens;
|
|
27860
|
+
return { project: parseProjectName(name), source: "directory" };
|
|
27393
27861
|
} catch (error62) {
|
|
27394
|
-
|
|
27395
|
-
|
|
27396
|
-
|
|
27397
|
-
|
|
27398
|
-
|
|
27399
|
-
if (error62.type === "UserNotConfirmedException") {
|
|
27400
|
-
throw new ConfigError(`finish your sign-up at ${page}`);
|
|
27401
|
-
}
|
|
27402
|
-
}
|
|
27403
|
-
throw error62;
|
|
27862
|
+
throw new ConfigError(
|
|
27863
|
+
`cannot use the directory name "${name}" as a project name: pass --project <name>`,
|
|
27864
|
+
void 0,
|
|
27865
|
+
{ cause: error62 }
|
|
27866
|
+
);
|
|
27404
27867
|
}
|
|
27405
27868
|
}
|
|
27406
|
-
async function
|
|
27407
|
-
requireServer("login", context);
|
|
27408
|
-
const terminal = terminalOf(io);
|
|
27409
|
-
if (!terminal.isTTY) throw new UsageError(NEEDS_TERMINAL_MESSAGE);
|
|
27869
|
+
async function logIn(context, options, io) {
|
|
27410
27870
|
const endpoint = await resolveEndpoint(context, io);
|
|
27411
|
-
|
|
27412
|
-
const
|
|
27413
|
-
if (
|
|
27414
|
-
|
|
27415
|
-
|
|
27871
|
+
let webUrl;
|
|
27872
|
+
const loopback = isLoopbackUrl(endpoint);
|
|
27873
|
+
if (context.token === void 0 && context.credential === void 0 && !loopback) {
|
|
27874
|
+
if (options.login === false || ciIsSet(io.env)) {
|
|
27875
|
+
return { endpoint, client: void 0, webUrl };
|
|
27876
|
+
}
|
|
27877
|
+
const session = await browserSession(
|
|
27878
|
+
endpoint,
|
|
27879
|
+
{ version: options.version },
|
|
27880
|
+
context.debug,
|
|
27881
|
+
io
|
|
27882
|
+
);
|
|
27883
|
+
if (session === void 0) {
|
|
27884
|
+
writeLine(io.stderr, localModeLine(endpoint));
|
|
27885
|
+
return "local";
|
|
27886
|
+
}
|
|
27887
|
+
writeLine(io.stderr, loggedInLine(endpoint, session.me));
|
|
27888
|
+
webUrl = session.webUrl;
|
|
27889
|
+
context.credential = findCredential(io.env, endpoint);
|
|
27416
27890
|
}
|
|
27417
|
-
|
|
27891
|
+
const client = await openClient(context, io, { queue: false });
|
|
27892
|
+
return { endpoint, client, webUrl };
|
|
27893
|
+
}
|
|
27894
|
+
async function findOrganization(client, remote, context, io) {
|
|
27895
|
+
const endpoint = remote.endpoint;
|
|
27896
|
+
let me;
|
|
27418
27897
|
try {
|
|
27419
|
-
|
|
27420
|
-
}
|
|
27421
|
-
|
|
27898
|
+
me = await client.me();
|
|
27899
|
+
} catch (error62) {
|
|
27900
|
+
if (error62 instanceof RemoteError && error62.status === 404 && error62.serverCode !== ORG_NOT_FOUND) {
|
|
27901
|
+
const webUrl = await webUrlOf(remote, context, io);
|
|
27902
|
+
throw new ConfigError(`finish your account at ${webUrl}${LOGIN_PAGE2}`);
|
|
27903
|
+
}
|
|
27904
|
+
writeLine(
|
|
27905
|
+
io.stderr,
|
|
27906
|
+
`quibble: warning: could not read the organizations of ${endpoint}: ${describeError(error62)}`
|
|
27907
|
+
);
|
|
27908
|
+
return { org: void 0, project: void 0 };
|
|
27422
27909
|
}
|
|
27423
|
-
if (
|
|
27424
|
-
|
|
27910
|
+
if (me.kind === "token") {
|
|
27911
|
+
return {
|
|
27912
|
+
org: me.org,
|
|
27913
|
+
project: me.project === void 0 ? void 0 : parseProjectName(me.project)
|
|
27914
|
+
};
|
|
27425
27915
|
}
|
|
27426
|
-
const
|
|
27427
|
-
const
|
|
27428
|
-
|
|
27429
|
-
|
|
27430
|
-
|
|
27431
|
-
|
|
27432
|
-
|
|
27433
|
-
|
|
27434
|
-
);
|
|
27435
|
-
if (me.sub === void 0 || me.email === void 0) {
|
|
27436
|
-
throw new RemoteError(
|
|
27437
|
-
"unexpected response from the API: identity has no sub or email"
|
|
27916
|
+
const who = me.email ?? me.reporter;
|
|
27917
|
+
const slugs = me.orgs.map((entry) => entry.org).sort();
|
|
27918
|
+
const [only] = slugs;
|
|
27919
|
+
if (slugs.length === 1 && only !== void 0) return { org: only, project: void 0 };
|
|
27920
|
+
if (slugs.length === 0) {
|
|
27921
|
+
const webUrl = await webUrlOf(remote, context, io);
|
|
27922
|
+
throw new ConfigError(
|
|
27923
|
+
`${who} belongs to no organization on ${endpoint}: create one at ${webUrl}${ORGS_PAGE2}, then run quibble init again`
|
|
27438
27924
|
);
|
|
27439
27925
|
}
|
|
27440
|
-
|
|
27441
|
-
endpoint,
|
|
27442
|
-
|
|
27443
|
-
|
|
27444
|
-
|
|
27445
|
-
|
|
27446
|
-
|
|
27926
|
+
throw new UsageError(
|
|
27927
|
+
`${who} belongs to several organizations on ${endpoint}: ${slugs.join(", ")}. Pass --org <slug>`
|
|
27928
|
+
);
|
|
27929
|
+
}
|
|
27930
|
+
async function webUrlOf(remote, context, io) {
|
|
27931
|
+
if (remote.webUrl !== void 0) return trimSlash2(remote.webUrl);
|
|
27932
|
+
const config2 = await fetchAuthConfig(remote.endpoint, {
|
|
27933
|
+
debug: context.debug,
|
|
27934
|
+
fetch: io.fetch
|
|
27447
27935
|
});
|
|
27448
|
-
|
|
27449
|
-
|
|
27450
|
-
|
|
27451
|
-
|
|
27936
|
+
return config2.mode === "cognito" ? trimSlash2(config2.webUrl) : remote.endpoint;
|
|
27937
|
+
}
|
|
27938
|
+
async function checkServer(client, endpoint, context, input2) {
|
|
27939
|
+
const { org, project, io } = input2;
|
|
27940
|
+
const warn = (text) => {
|
|
27941
|
+
writeLine(io.stderr, `quibble: warning: ${text}`);
|
|
27942
|
+
};
|
|
27943
|
+
const found = {
|
|
27944
|
+
server: "found",
|
|
27945
|
+
line: `Project ${org}/${project} exists on ${endpoint}.`
|
|
27946
|
+
};
|
|
27947
|
+
try {
|
|
27948
|
+
await client.project(project);
|
|
27949
|
+
return found;
|
|
27950
|
+
} catch (error62) {
|
|
27951
|
+
if (!(error62 instanceof RemoteError && error62.status === 404)) {
|
|
27952
|
+
warn(`could not check the project on ${endpoint}: ${describeError(error62)}`);
|
|
27953
|
+
return { server: "unreachable" };
|
|
27954
|
+
}
|
|
27955
|
+
if (error62.serverCode === ORG_NOT_FOUND) {
|
|
27956
|
+
warn(`no organization ${org} on ${endpoint} for this account`);
|
|
27957
|
+
return { server: "no_org" };
|
|
27958
|
+
}
|
|
27452
27959
|
}
|
|
27453
|
-
|
|
27454
|
-
|
|
27455
|
-
|
|
27960
|
+
const noAdmin = () => {
|
|
27961
|
+
warn(
|
|
27962
|
+
`${org} has no project ${project} on ${endpoint}: ask an admin of ${org} to create it`
|
|
27963
|
+
);
|
|
27964
|
+
return { server: "no_project" };
|
|
27965
|
+
};
|
|
27966
|
+
if (context.token !== void 0) return noAdmin();
|
|
27967
|
+
try {
|
|
27968
|
+
await client.createProject(project, input2.tags);
|
|
27969
|
+
return {
|
|
27970
|
+
server: "created",
|
|
27971
|
+
line: `Created the project ${org}/${project} on ${endpoint}.`
|
|
27972
|
+
};
|
|
27973
|
+
} catch (error62) {
|
|
27974
|
+
if (error62 instanceof RemoteError) {
|
|
27975
|
+
if (error62.status === 409 && error62.serverCode === CONFLICT) return found;
|
|
27976
|
+
if (error62.status === 403) return noAdmin();
|
|
27977
|
+
}
|
|
27978
|
+
warn(
|
|
27979
|
+
`could not create the project ${org}/${project} on ${endpoint}: ${describeError(error62)}`
|
|
27980
|
+
);
|
|
27981
|
+
return { server: "no_project" };
|
|
27456
27982
|
}
|
|
27457
|
-
return 0;
|
|
27458
27983
|
}
|
|
27459
|
-
|
|
27460
|
-
|
|
27461
|
-
|
|
27984
|
+
|
|
27985
|
+
// src/mine.ts
|
|
27986
|
+
function mineMessage(env) {
|
|
27987
|
+
return `--mine needs a reporter: set "reporter" in ${defaultConfigPath(env)}`;
|
|
27988
|
+
}
|
|
27989
|
+
async function resolveMine(context, client, env) {
|
|
27990
|
+
if (client === void 0) {
|
|
27991
|
+
if (context.reporter === DEFAULT_REPORTER) throw new UsageError(mineMessage(env));
|
|
27992
|
+
return context.reporter;
|
|
27993
|
+
}
|
|
27994
|
+
return (await client.me()).reporter;
|
|
27995
|
+
}
|
|
27996
|
+
|
|
27997
|
+
// src/since.ts
|
|
27998
|
+
var SINCE_MESSAGE = "--since must be a duration like 7d, a date like 2026-09-01, or an ISO 8601 UTC timestamp";
|
|
27999
|
+
var DURATION_RE = /^(\d+)(m|h|d|w)$/;
|
|
28000
|
+
var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
28001
|
+
var TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
28002
|
+
var MINUTE_MS = 6e4;
|
|
28003
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
28004
|
+
var DAY_MS3 = 24 * HOUR_MS;
|
|
28005
|
+
var UNIT_MS = {
|
|
28006
|
+
m: MINUTE_MS,
|
|
28007
|
+
h: HOUR_MS,
|
|
28008
|
+
d: DAY_MS3,
|
|
28009
|
+
w: 7 * DAY_MS3
|
|
28010
|
+
};
|
|
28011
|
+
var EXPIRES_MESSAGE = "--expires must be a duration like 30d, a date like 2026-12-01, or an ISO 8601 UTC timestamp";
|
|
28012
|
+
function reject(message = SINCE_MESSAGE) {
|
|
28013
|
+
throw new UsageError(message);
|
|
28014
|
+
}
|
|
28015
|
+
function iso(at) {
|
|
28016
|
+
if (Number.isNaN(at.getTime())) reject();
|
|
28017
|
+
return at.toISOString();
|
|
28018
|
+
}
|
|
28019
|
+
function durationMs(digits, unit) {
|
|
28020
|
+
if (digits.startsWith("0")) return void 0;
|
|
28021
|
+
const step = UNIT_MS[unit];
|
|
28022
|
+
return step === void 0 ? void 0 : Number(digits) * step;
|
|
28023
|
+
}
|
|
28024
|
+
function fromDuration(digits, unit, now) {
|
|
28025
|
+
const span = durationMs(digits, unit);
|
|
28026
|
+
if (span === void 0) reject();
|
|
28027
|
+
return iso(new Date(now.getTime() - span));
|
|
28028
|
+
}
|
|
28029
|
+
function fromDate(value) {
|
|
28030
|
+
const at = /* @__PURE__ */ new Date(`${value}T00:00:00.000Z`);
|
|
28031
|
+
const stamp2 = iso(at);
|
|
28032
|
+
if (!stamp2.startsWith(value)) reject();
|
|
28033
|
+
return stamp2;
|
|
28034
|
+
}
|
|
28035
|
+
function parseSince(text, now) {
|
|
28036
|
+
const value = text.trim();
|
|
28037
|
+
const duration3 = DURATION_RE.exec(value);
|
|
28038
|
+
if (duration3 !== null) {
|
|
28039
|
+
return fromDuration(duration3[1] ?? "", duration3[2] ?? "", now);
|
|
27462
28040
|
}
|
|
27463
|
-
return
|
|
28041
|
+
if (DATE_RE.test(value)) return fromDate(value);
|
|
28042
|
+
if (TIMESTAMP_RE.test(value)) return iso(new Date(value));
|
|
28043
|
+
return reject();
|
|
27464
28044
|
}
|
|
27465
|
-
|
|
28045
|
+
function parseExpires(text, now) {
|
|
28046
|
+
const value = text.trim();
|
|
28047
|
+
const duration3 = DURATION_RE.exec(value);
|
|
28048
|
+
let stamp2;
|
|
27466
28049
|
try {
|
|
27467
|
-
|
|
27468
|
-
|
|
27469
|
-
|
|
27470
|
-
|
|
28050
|
+
if (duration3 !== null) {
|
|
28051
|
+
const span = durationMs(duration3[1] ?? "", duration3[2] ?? "");
|
|
28052
|
+
if (span === void 0) reject(EXPIRES_MESSAGE);
|
|
28053
|
+
stamp2 = iso(new Date(now.getTime() + span));
|
|
28054
|
+
} else if (DATE_RE.test(value)) {
|
|
28055
|
+
stamp2 = fromDate(value);
|
|
28056
|
+
} else if (TIMESTAMP_RE.test(value)) {
|
|
28057
|
+
stamp2 = iso(new Date(value));
|
|
28058
|
+
} else {
|
|
28059
|
+
return reject(EXPIRES_MESSAGE);
|
|
27471
28060
|
}
|
|
27472
|
-
|
|
28061
|
+
} catch {
|
|
28062
|
+
return reject(EXPIRES_MESSAGE);
|
|
27473
28063
|
}
|
|
28064
|
+
return stamp2;
|
|
27474
28065
|
}
|
|
27475
|
-
|
|
27476
|
-
|
|
27477
|
-
|
|
27478
|
-
|
|
27479
|
-
|
|
27480
|
-
writeLine(io.stdout, `Not logged in to ${endpoint}.`);
|
|
27481
|
-
return 0;
|
|
28066
|
+
|
|
28067
|
+
// src/scope.ts
|
|
28068
|
+
function resolveScope(options, context, now = /* @__PURE__ */ new Date()) {
|
|
28069
|
+
if (options.project !== void 0 && options.all === true) {
|
|
28070
|
+
throw new UsageError("--project and --all cannot be used together");
|
|
27482
28071
|
}
|
|
27483
|
-
|
|
27484
|
-
|
|
27485
|
-
writeLine(
|
|
27486
|
-
io.stderr,
|
|
27487
|
-
`quibble: warning: could not revoke the refresh token at ${endpoint}`
|
|
27488
|
-
);
|
|
28072
|
+
if (options.open === true && options.status !== void 0) {
|
|
28073
|
+
throw new UsageError("--open and --status cannot be used together");
|
|
27489
28074
|
}
|
|
27490
|
-
|
|
27491
|
-
|
|
27492
|
-
return
|
|
28075
|
+
const project = options.project ?? (options.all === true ? void 0 : context.project);
|
|
28076
|
+
const status = options.open === true ? "open" : options.status;
|
|
28077
|
+
return {
|
|
28078
|
+
// No list reads two organizations, because the path names one.
|
|
28079
|
+
org: requireOrg(context),
|
|
28080
|
+
...project === void 0 ? {} : { project },
|
|
28081
|
+
...status === void 0 ? {} : { status },
|
|
28082
|
+
...options.since === void 0 ? {} : { since: parseSince(options.since, now) }
|
|
28083
|
+
};
|
|
27493
28084
|
}
|
|
27494
|
-
|
|
27495
|
-
|
|
27496
|
-
|
|
27497
|
-
|
|
27498
|
-
|
|
27499
|
-
|
|
27500
|
-
|
|
27501
|
-
|
|
27502
|
-
|
|
27503
|
-
|
|
27504
|
-
|
|
28085
|
+
function tagFilter(tags) {
|
|
28086
|
+
return tags === void 0 || tags.length === 0 ? {} : { tags: [...tags] };
|
|
28087
|
+
}
|
|
28088
|
+
|
|
28089
|
+
// src/commands/list.ts
|
|
28090
|
+
var COLUMNS = [
|
|
28091
|
+
"id",
|
|
28092
|
+
"created",
|
|
28093
|
+
"severity",
|
|
28094
|
+
"status",
|
|
28095
|
+
"project",
|
|
28096
|
+
"reporter",
|
|
28097
|
+
"tags",
|
|
28098
|
+
"group",
|
|
28099
|
+
"summary"
|
|
28100
|
+
];
|
|
28101
|
+
var REPORTER_WIDTH = 20;
|
|
28102
|
+
var TAGS_WIDTH = 24;
|
|
28103
|
+
var SUMMARY_MAX = 60;
|
|
28104
|
+
var RECURRED_MARK = "\u21BB";
|
|
28105
|
+
function cut3(text, width) {
|
|
28106
|
+
if (text.length <= width) return text;
|
|
28107
|
+
return `${text.slice(0, width - 3)}...`;
|
|
28108
|
+
}
|
|
28109
|
+
function summaryOf(complaint) {
|
|
28110
|
+
return cut3(printable(summarize(complaint.message)), SUMMARY_MAX);
|
|
28111
|
+
}
|
|
28112
|
+
function limitNote(count, limit, more) {
|
|
28113
|
+
if (limit >= MAX_LIMIT) {
|
|
28114
|
+
return count >= MAX_LIMIT ? `Showing the newest ${MAX_LIMIT} complaints: older ones may exist, narrow the filters to see them.` : void 0;
|
|
27505
28115
|
}
|
|
28116
|
+
return more ? `Showing the newest ${limit} complaints: pass --limit to see more (up to ${MAX_LIMIT}).` : void 0;
|
|
27506
28117
|
}
|
|
27507
|
-
|
|
27508
|
-
|
|
27509
|
-
|
|
27510
|
-
|
|
28118
|
+
var BUDGET_NOTE = "The server stopped searching early, so older matches may exist: narrow the filters or pass --since.";
|
|
28119
|
+
async function runList(options, context, io) {
|
|
28120
|
+
if (options.ids === true && options.json === true) {
|
|
28121
|
+
throw new UsageError("--ids and --json cannot be used together");
|
|
28122
|
+
}
|
|
28123
|
+
const { store, client } = await openBackend(context, io);
|
|
28124
|
+
const reporter = options.mine === true ? await resolveMine(context, client, io.env) : void 0;
|
|
28125
|
+
const filter = parseListFilter({
|
|
28126
|
+
...resolveScope(options, context),
|
|
28127
|
+
...options.severity === void 0 ? {} : { severity: options.severity },
|
|
28128
|
+
...tagFilter(options.tag),
|
|
28129
|
+
...options.search === void 0 ? {} : { q: options.search },
|
|
28130
|
+
...reporter === void 0 ? {} : { reporter },
|
|
28131
|
+
...options.limit === void 0 ? {} : { limit: options.limit }
|
|
28132
|
+
});
|
|
28133
|
+
const limit = filter.limit ?? DEFAULT_LIMIT;
|
|
28134
|
+
const page = await listComplaintPage(store, {
|
|
28135
|
+
...filter,
|
|
28136
|
+
limit: Math.min(limit + 1, MAX_LIMIT)
|
|
28137
|
+
});
|
|
28138
|
+
const complaints = page.complaints.slice(0, limit);
|
|
28139
|
+
if (options.json === true) {
|
|
28140
|
+
writeJson(io.stdout, complaints);
|
|
28141
|
+
return 0;
|
|
28142
|
+
}
|
|
28143
|
+
const note = limitNote(complaints.length, limit, page.complaints.length > limit);
|
|
28144
|
+
if (note !== void 0) writeLine(io.stderr, note);
|
|
28145
|
+
if (page.truncated) writeLine(io.stderr, BUDGET_NOTE);
|
|
28146
|
+
if (options.ids === true) {
|
|
28147
|
+
for (const complaint of complaints) writeLine(io.stdout, complaint.id);
|
|
28148
|
+
return 0;
|
|
28149
|
+
}
|
|
28150
|
+
if (complaints.length === 0) {
|
|
28151
|
+
writeLine(io.stdout, "No complaints found.");
|
|
27511
28152
|
return 0;
|
|
27512
28153
|
}
|
|
28154
|
+
const lines = options.ungroup === true ? formatTable(complaints.map((complaint) => ({ complaint }))) : formatTable(
|
|
28155
|
+
groupComplaints(complaints).map((group) => ({
|
|
28156
|
+
complaint: group.head,
|
|
28157
|
+
group
|
|
28158
|
+
}))
|
|
28159
|
+
);
|
|
28160
|
+
for (const line of lines) writeLine(io.stdout, line);
|
|
28161
|
+
return 0;
|
|
28162
|
+
}
|
|
28163
|
+
function reporterOf(complaint) {
|
|
28164
|
+
return cut3(printable(complaint.reporterName ?? complaint.reporter), REPORTER_WIDTH);
|
|
28165
|
+
}
|
|
28166
|
+
function groupCell(group) {
|
|
28167
|
+
const count = group.count > 1 ? `\xD7${group.count}` : "";
|
|
28168
|
+
return `${count}${group.recurred ? RECURRED_MARK : ""}`;
|
|
28169
|
+
}
|
|
28170
|
+
function formatTable(rows2) {
|
|
28171
|
+
const grouped = rows2.some((row) => row.group !== void 0);
|
|
28172
|
+
const columns = COLUMNS.filter((name) => grouped || name !== "group");
|
|
28173
|
+
const cells = rows2.map(({ complaint, group }) => {
|
|
28174
|
+
const values = {
|
|
28175
|
+
id: complaint.id.slice(0, 8),
|
|
28176
|
+
created: complaint.createdAt.slice(0, 16),
|
|
28177
|
+
severity: complaint.severity,
|
|
28178
|
+
status: complaint.status,
|
|
28179
|
+
project: complaint.project,
|
|
28180
|
+
reporter: reporterOf(complaint),
|
|
28181
|
+
tags: cut3(printable(complaint.tags.join(",")), TAGS_WIDTH),
|
|
28182
|
+
group: group === void 0 ? "" : groupCell(group),
|
|
28183
|
+
summary: summaryOf(complaint)
|
|
28184
|
+
};
|
|
28185
|
+
return columns.map((name) => values[name]);
|
|
28186
|
+
});
|
|
28187
|
+
const widths = columns.map(
|
|
28188
|
+
(name, column) => cells.reduce(
|
|
28189
|
+
(width, row) => Math.max(width, (row[column] ?? "").length),
|
|
28190
|
+
name.length
|
|
28191
|
+
)
|
|
28192
|
+
);
|
|
28193
|
+
const format = (line) => line.map((cell2, column) => cell2.padEnd(widths[column] ?? 0)).join(" ").trimEnd();
|
|
28194
|
+
return [format(columns), ...cells.map(format)];
|
|
28195
|
+
}
|
|
28196
|
+
|
|
28197
|
+
// src/commands/orgs.ts
|
|
28198
|
+
var ORGS_PAGE3 = "/orgs";
|
|
28199
|
+
var COLUMNS2 = ["SLUG", "NAME", "ROLE", "OPEN"];
|
|
28200
|
+
async function runOrgs(options, context, io) {
|
|
28201
|
+
requireServer("orgs", context);
|
|
27513
28202
|
const client = await openClient(context, io);
|
|
27514
28203
|
const endpoint = requireEndpoint(context);
|
|
27515
|
-
const
|
|
27516
|
-
const org = me.kind === "token" || client.org === void 0 ? void 0 : await client.organization();
|
|
28204
|
+
const answer2 = await client.orgs();
|
|
27517
28205
|
if (options.json === true) {
|
|
27518
|
-
writeJson(io.stdout,
|
|
27519
|
-
...me.body,
|
|
27520
|
-
endpoint,
|
|
27521
|
-
...org === void 0 ? {} : { org: org.body }
|
|
27522
|
-
});
|
|
28206
|
+
writeJson(io.stdout, answer2.body);
|
|
27523
28207
|
return 0;
|
|
27524
28208
|
}
|
|
27525
|
-
|
|
27526
|
-
if (me.kind !== "token" && me.orgs.length === 0) {
|
|
28209
|
+
if (answer2.orgs.length === 0) {
|
|
27527
28210
|
const config2 = await fetchAuthConfig(endpoint, {
|
|
27528
28211
|
debug: context.debug,
|
|
27529
|
-
fetch: io.fetch
|
|
28212
|
+
fetch: io.fetch,
|
|
28213
|
+
timeoutMs: context.timeoutMs
|
|
27530
28214
|
});
|
|
27531
28215
|
const webUrl = config2.mode === "cognito" ? trimSlash2(config2.webUrl) : endpoint;
|
|
27532
|
-
|
|
28216
|
+
writeLine(io.stdout, `No organizations yet: create one at ${webUrl}${ORGS_PAGE3}.`);
|
|
28217
|
+
return 0;
|
|
27533
28218
|
}
|
|
27534
|
-
for (const line of
|
|
28219
|
+
for (const line of formatOrgs(answer2.orgs)) writeLine(io.stdout, line);
|
|
27535
28220
|
return 0;
|
|
27536
28221
|
}
|
|
27537
|
-
function
|
|
27538
|
-
const
|
|
27539
|
-
|
|
27540
|
-
|
|
27541
|
-
|
|
27542
|
-
|
|
27543
|
-
const width = Math.max(...entries.map(([key]) => key.length));
|
|
27544
|
-
return entries.map(([key, value]) => ` ${key.padEnd(width)} ${value}`);
|
|
27545
|
-
}
|
|
27546
|
-
function whoamiLines(endpoint, me, org) {
|
|
27547
|
-
if (me.kind === "token") {
|
|
27548
|
-
return rows([
|
|
27549
|
-
["endpoint", endpoint],
|
|
27550
|
-
["token", me.name ?? me.reporter],
|
|
27551
|
-
["org", me.org ?? ""],
|
|
27552
|
-
["project", me.project ?? ""],
|
|
27553
|
-
["role", me.role ?? "filer"]
|
|
27554
|
-
]);
|
|
27555
|
-
}
|
|
27556
|
-
const lines = rows([
|
|
27557
|
-
["endpoint", endpoint],
|
|
27558
|
-
["email", me.email ?? ""],
|
|
27559
|
-
["name", me.name ?? ""],
|
|
27560
|
-
["sub", me.sub ?? ""],
|
|
27561
|
-
["platform", me.platform ? "yes" : "no"]
|
|
28222
|
+
function formatOrgs(orgs) {
|
|
28223
|
+
const rows2 = [...orgs].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map((entry) => [
|
|
28224
|
+
entry.org,
|
|
28225
|
+
entry.name,
|
|
28226
|
+
entry.role,
|
|
28227
|
+
entry.openCount === void 0 ? "" : String(entry.openCount)
|
|
27562
28228
|
]);
|
|
27563
|
-
|
|
27564
|
-
|
|
27565
|
-
|
|
27566
|
-
|
|
27567
|
-
|
|
27568
|
-
|
|
28229
|
+
const widths = COLUMNS2.map(
|
|
28230
|
+
(name, column) => rows2.reduce((width, row) => Math.max(width, (row[column] ?? "").length), name.length)
|
|
28231
|
+
);
|
|
28232
|
+
const format = (cells) => cells.map(
|
|
28233
|
+
(cell2, column) => column === COLUMNS2.length - 1 ? cell2.padStart(widths[column] ?? 0) : cell2.padEnd(widths[column] ?? 0)
|
|
28234
|
+
).join(" ").trimEnd();
|
|
28235
|
+
return [format(COLUMNS2), ...rows2.map(format)];
|
|
27569
28236
|
}
|
|
27570
28237
|
|
|
27571
28238
|
// src/commands/project.ts
|
|
@@ -28334,11 +29001,14 @@ function buildProgram(io, setCode) {
|
|
|
28334
29001
|
}).exitOverride();
|
|
28335
29002
|
withGlobalOptions(program2);
|
|
28336
29003
|
withGlobalOptions(
|
|
28337
|
-
program2.command("init").description("write a .quibblerc in the current directory").option(
|
|
29004
|
+
program2.command("init").description("write a .quibblerc in the current directory").option(
|
|
29005
|
+
"--project <name>",
|
|
29006
|
+
"project name (default: the git remote origin, else the directory name)"
|
|
29007
|
+
).option("-t, --tag <tag>", "allowed tag (repeatable)", collect).option("--agent <name>", "agent named in the snippet (default: claude)").option("--skill", "also write .claude/skills/quibble/SKILL.md").option("--hook", "also add the quibble sync hook to .claude/settings.json").option("--no-login", "do not log in through the browser without a credential").option("--force", "overwrite an existing .quibblerc or skill file")
|
|
28338
29008
|
).action(async function() {
|
|
28339
29009
|
const options = { ...this.opts(), ...globals(program2, this) };
|
|
28340
29010
|
rejectEmptyOptions(options);
|
|
28341
|
-
setCode(await runInit(options, io));
|
|
29011
|
+
setCode(await runInit({ ...options, version: packageVersion() }, io));
|
|
28342
29012
|
});
|
|
28343
29013
|
withGlobalOptions(
|
|
28344
29014
|
program2.command("snippet").description("print or write the agent snippet of this project").option("--append <file>", "append the snippet to a file such as CLAUDE.md").option("--skill", "write .claude/skills/quibble/SKILL.md").option("--hook", "add the quibble sync hook to .claude/settings.json").option("--agent <name>", "agent named in the snippet").option("--project <name>", "project name (overrides .quibblerc)").option("--force", "overwrite an existing skill file")
|
|
@@ -28487,11 +29157,17 @@ function buildProgram(io, setCode) {
|
|
|
28487
29157
|
setCode(await runOrgs(options, contextFor(options, io), io));
|
|
28488
29158
|
});
|
|
28489
29159
|
withGlobalOptions(
|
|
28490
|
-
program2.command("login").description("log in to the endpoint")
|
|
29160
|
+
program2.command("login").description("log in to the endpoint, approved in a browser").option("--no-browser", "print the approval URL, and open no browser").option("--password", "log in with an email and a password on the terminal")
|
|
28491
29161
|
).action(async function() {
|
|
28492
29162
|
const options = { ...this.opts(), ...globals(program2, this) };
|
|
28493
29163
|
rejectEmptyOptions(options);
|
|
28494
|
-
setCode(
|
|
29164
|
+
setCode(
|
|
29165
|
+
await runLogin(
|
|
29166
|
+
{ ...options, version: packageVersion() },
|
|
29167
|
+
contextFor(options, io),
|
|
29168
|
+
io
|
|
29169
|
+
)
|
|
29170
|
+
);
|
|
28495
29171
|
});
|
|
28496
29172
|
withGlobalOptions(
|
|
28497
29173
|
program2.command("logout").description("log out of the endpoint")
|