letthemquibble 1.1.0 → 2.0.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 +64 -14
- package/dist/quibble.js +1229 -307
- package/package.json +2 -1
package/dist/quibble.js
CHANGED
|
@@ -51,11 +51,11 @@ var require_error = __commonJS({
|
|
|
51
51
|
* @param {string} code an id string representing the error
|
|
52
52
|
* @param {string} message human-readable description of the error
|
|
53
53
|
*/
|
|
54
|
-
constructor(exitCode,
|
|
54
|
+
constructor(exitCode, code2, message) {
|
|
55
55
|
super(message);
|
|
56
56
|
Error.captureStackTrace(this, this.constructor);
|
|
57
57
|
this.name = this.constructor.name;
|
|
58
|
-
this.code =
|
|
58
|
+
this.code = code2;
|
|
59
59
|
this.exitCode = exitCode;
|
|
60
60
|
this.nestedError = void 0;
|
|
61
61
|
}
|
|
@@ -1649,9 +1649,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1649
1649
|
* @return never
|
|
1650
1650
|
* @private
|
|
1651
1651
|
*/
|
|
1652
|
-
_exit(exitCode,
|
|
1652
|
+
_exit(exitCode, code2, message) {
|
|
1653
1653
|
if (this._exitCallback) {
|
|
1654
|
-
this._exitCallback(new CommanderError2(exitCode,
|
|
1654
|
+
this._exitCallback(new CommanderError2(exitCode, code2, message));
|
|
1655
1655
|
}
|
|
1656
1656
|
process3.exit(exitCode);
|
|
1657
1657
|
}
|
|
@@ -2289,14 +2289,14 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2289
2289
|
});
|
|
2290
2290
|
}
|
|
2291
2291
|
const exitCallback = this._exitCallback;
|
|
2292
|
-
proc.on("close", (
|
|
2293
|
-
|
|
2292
|
+
proc.on("close", (code2) => {
|
|
2293
|
+
code2 = code2 ?? 1;
|
|
2294
2294
|
if (!exitCallback) {
|
|
2295
|
-
process3.exit(
|
|
2295
|
+
process3.exit(code2);
|
|
2296
2296
|
} else {
|
|
2297
2297
|
exitCallback(
|
|
2298
2298
|
new CommanderError2(
|
|
2299
|
-
|
|
2299
|
+
code2,
|
|
2300
2300
|
"commander.executeSubCommandAsync",
|
|
2301
2301
|
"(close)"
|
|
2302
2302
|
)
|
|
@@ -2803,8 +2803,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2803
2803
|
}
|
|
2804
2804
|
const config2 = errorOptions || {};
|
|
2805
2805
|
const exitCode = config2.exitCode || 1;
|
|
2806
|
-
const
|
|
2807
|
-
this._exit(exitCode,
|
|
2806
|
+
const code2 = config2.code || "commander.error";
|
|
2807
|
+
this._exit(exitCode, code2, message);
|
|
2808
2808
|
}
|
|
2809
2809
|
/**
|
|
2810
2810
|
* Apply any option related environment variables, if option does
|
|
@@ -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;
|
|
@@ -6578,12 +6578,12 @@ function isIso7064Mod97(iban3) {
|
|
|
6578
6578
|
let remainder = 0;
|
|
6579
6579
|
const len = iban3.length;
|
|
6580
6580
|
for (let i = 4; i < len; i++) {
|
|
6581
|
-
const
|
|
6582
|
-
remainder = (
|
|
6581
|
+
const code2 = iban3.charCodeAt(i);
|
|
6582
|
+
remainder = (code2 >= 65 ? remainder * 100 + (code2 - 55) : remainder * 10 + (code2 - 48)) % 97;
|
|
6583
6583
|
}
|
|
6584
6584
|
for (let i = 0; i < 4; i++) {
|
|
6585
|
-
const
|
|
6586
|
-
remainder = (
|
|
6585
|
+
const code2 = iban3.charCodeAt(i);
|
|
6586
|
+
remainder = (code2 >= 65 ? remainder * 100 + (code2 - 55) : remainder * 10 + (code2 - 48)) % 97;
|
|
6587
6587
|
}
|
|
6588
6588
|
return remainder === 1;
|
|
6589
6589
|
}
|
|
@@ -7013,7 +7013,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
7013
7013
|
}
|
|
7014
7014
|
return propValues;
|
|
7015
7015
|
});
|
|
7016
|
-
const
|
|
7016
|
+
const isObject3 = isObject;
|
|
7017
7017
|
const catchall = def.catchall;
|
|
7018
7018
|
let value;
|
|
7019
7019
|
const memo2 = globalConfig.memoizer;
|
|
@@ -7021,7 +7021,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
|
|
|
7021
7021
|
inst._zod.parse = (payload, ctx) => {
|
|
7022
7022
|
value ?? (value = _normalized.value);
|
|
7023
7023
|
const input2 = payload.value;
|
|
7024
|
-
if (!
|
|
7024
|
+
if (!isObject3(input2)) {
|
|
7025
7025
|
payload.issues.push({
|
|
7026
7026
|
expected: "object",
|
|
7027
7027
|
code: "invalid_type",
|
|
@@ -7157,7 +7157,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
|
|
|
7157
7157
|
return doc.compile();
|
|
7158
7158
|
};
|
|
7159
7159
|
let fastpass;
|
|
7160
|
-
const
|
|
7160
|
+
const isObject3 = isObject;
|
|
7161
7161
|
const jit = !globalConfig.jitless;
|
|
7162
7162
|
const allowsEval2 = allowsEval;
|
|
7163
7163
|
const fastEnabled = jit && allowsEval2.value;
|
|
@@ -7166,7 +7166,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
|
|
|
7166
7166
|
inst._zod.parse = (payload, ctx) => {
|
|
7167
7167
|
value ?? (value = _normalized.value);
|
|
7168
7168
|
const input2 = payload.value;
|
|
7169
|
-
if (!
|
|
7169
|
+
if (!isObject3(input2)) {
|
|
7170
7170
|
payload.issues.push({
|
|
7171
7171
|
expected: "object",
|
|
7172
7172
|
code: "invalid_type",
|
|
@@ -16501,12 +16501,12 @@ function compileFn(schema, options) {
|
|
|
16501
16501
|
doc.write(outputAccessor === null ? `return true;` : `return ${outputAccessor};`);
|
|
16502
16502
|
const constantNames = ["INVALID", ...ctx.constants.keys()];
|
|
16503
16503
|
const constantValues = [INVALID, ...ctx.constants.values()];
|
|
16504
|
-
const
|
|
16504
|
+
const code2 = doc.content.join("\n");
|
|
16505
16505
|
const fullCode = options?.debug ? constantNames.length > 0 ? `// Constants: ${constantNames.join(", ")}
|
|
16506
|
-
${
|
|
16506
|
+
${code2}` : code2 : "";
|
|
16507
16507
|
const F = Function;
|
|
16508
16508
|
const factoryCode = `return (input) => {
|
|
16509
|
-
${
|
|
16509
|
+
${code2}
|
|
16510
16510
|
}`;
|
|
16511
16511
|
let fn;
|
|
16512
16512
|
try {
|
|
@@ -19432,8 +19432,8 @@ function finalize(ctx, schema) {
|
|
|
19432
19432
|
compactTypeUnion(entry[1].def ?? entry[1].schema);
|
|
19433
19433
|
}
|
|
19434
19434
|
}
|
|
19435
|
-
for (const
|
|
19436
|
-
|
|
19435
|
+
for (const rewrite2 of ctx.deferred)
|
|
19436
|
+
rewrite2();
|
|
19437
19437
|
if (ctx.intersections.length) {
|
|
19438
19438
|
const carriers = /* @__PURE__ */ new Map();
|
|
19439
19439
|
for (const seen of ctx.seen.values()) {
|
|
@@ -23222,7 +23222,7 @@ var RESERVED_SLUGS = [
|
|
|
23222
23222
|
];
|
|
23223
23223
|
var LOCAL_ORG = "local";
|
|
23224
23224
|
var DISPLAY_NAME_MAX = 64;
|
|
23225
|
-
var PROJECT_NAME_RE = /^[
|
|
23225
|
+
var PROJECT_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
23226
23226
|
var DEFAULT_LIMIT = 50;
|
|
23227
23227
|
var MAX_LIMIT = 200;
|
|
23228
23228
|
var DEFAULT_SEVERITY = "medium";
|
|
@@ -23233,14 +23233,22 @@ 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 {
|
|
23239
23247
|
/** Stable machine-readable code, also used by the API error body. */
|
|
23240
23248
|
code;
|
|
23241
|
-
constructor(
|
|
23249
|
+
constructor(code2, message, options) {
|
|
23242
23250
|
super(message, options);
|
|
23243
|
-
this.code =
|
|
23251
|
+
this.code = code2;
|
|
23244
23252
|
this.name = new.target.name;
|
|
23245
23253
|
}
|
|
23246
23254
|
};
|
|
@@ -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) {
|
|
@@ -23476,7 +23513,10 @@ var timestampSchema = external_exports.string({ message: TIMESTAMP_MESSAGE }).re
|
|
|
23476
23513
|
function toMilliseconds(timestamp) {
|
|
23477
23514
|
return new Date(timestamp).toISOString();
|
|
23478
23515
|
}
|
|
23479
|
-
|
|
23516
|
+
function foldProjectName(name) {
|
|
23517
|
+
return name.replace(/[A-Z]+/g, (letters) => letters.toLowerCase());
|
|
23518
|
+
}
|
|
23519
|
+
var projectNameSchema = external_exports.string({ message: PROJECT_MESSAGE }).overwrite(foldProjectName).regex(PROJECT_NAME_RE, { message: PROJECT_MESSAGE });
|
|
23480
23520
|
var orgSlugSchema = external_exports.string({ message: SLUG_MESSAGE }).refine((value) => ORG_SLUG_RE.test(value) && !value.includes("--"), {
|
|
23481
23521
|
message: SLUG_MESSAGE
|
|
23482
23522
|
});
|
|
@@ -23903,6 +23943,23 @@ function newTokenSchema(now) {
|
|
|
23903
23943
|
...input2.expiresAt === void 0 ? {} : { expiresAt: input2.expiresAt }
|
|
23904
23944
|
}));
|
|
23905
23945
|
}
|
|
23946
|
+
function reportedField(max) {
|
|
23947
|
+
const message = lengthMessage(1, max);
|
|
23948
|
+
return external_exports.string({ message }).trim().min(1, { message }).max(max, { message }).refine((value) => !CLI_LOGIN_CONTROL_RE.test(value), { message: CONTROL_MESSAGE });
|
|
23949
|
+
}
|
|
23950
|
+
var newCliLoginSchema = external_exports.strictObject({
|
|
23951
|
+
host: reportedField(CLI_LOGIN_HOST_MAX),
|
|
23952
|
+
os: reportedField(CLI_LOGIN_OS_MAX),
|
|
23953
|
+
version: reportedField(CLI_LOGIN_VERSION_MAX)
|
|
23954
|
+
}).transform((input2) => ({
|
|
23955
|
+
host: input2.host,
|
|
23956
|
+
os: input2.os,
|
|
23957
|
+
version: input2.version
|
|
23958
|
+
}));
|
|
23959
|
+
var LOGIN_KEY_MESSAGE = "must be 43 base64url characters";
|
|
23960
|
+
var cliLoginPollSchema = external_exports.strictObject({
|
|
23961
|
+
loginKey: external_exports.string({ message: LOGIN_KEY_MESSAGE }).regex(CLI_LOGIN_KEY_RE, { message: LOGIN_KEY_MESSAGE })
|
|
23962
|
+
}).transform((input2) => ({ loginKey: input2.loginKey }));
|
|
23906
23963
|
var UNKNOWN_KEY_MESSAGE = "unknown key";
|
|
23907
23964
|
var emptyBodySchema = external_exports.strictObject({});
|
|
23908
23965
|
function formatIssues(error62) {
|
|
@@ -24046,6 +24103,34 @@ function listQueryOf(filter) {
|
|
|
24046
24103
|
return pairs;
|
|
24047
24104
|
}
|
|
24048
24105
|
|
|
24106
|
+
// ../core/dist/endpoint.js
|
|
24107
|
+
var DEFAULT_ENDPOINT = "https://api.letthemquibble.com";
|
|
24108
|
+
var DISCOVERY_PATH = "/.well-known/quibble.json";
|
|
24109
|
+
var LOCAL_ENDPOINT = "local";
|
|
24110
|
+
var LOOPBACK_HOSTS = ["localhost", "127.0.0.1"];
|
|
24111
|
+
function trimSlash(url2) {
|
|
24112
|
+
return url2.replace(/\/+$/, "");
|
|
24113
|
+
}
|
|
24114
|
+
function normalizeEndpoint(url2) {
|
|
24115
|
+
const text = trimSlash(url2.trim());
|
|
24116
|
+
try {
|
|
24117
|
+
const parsed = new URL(text);
|
|
24118
|
+
return `${parsed.protocol}//${parsed.host}${trimSlash(parsed.pathname)}`;
|
|
24119
|
+
} catch {
|
|
24120
|
+
return text;
|
|
24121
|
+
}
|
|
24122
|
+
}
|
|
24123
|
+
function isDefaultEndpoint(url2) {
|
|
24124
|
+
return normalizeEndpoint(url2) === DEFAULT_ENDPOINT;
|
|
24125
|
+
}
|
|
24126
|
+
function isLoopbackUrl(url2) {
|
|
24127
|
+
try {
|
|
24128
|
+
return LOOPBACK_HOSTS.includes(new URL(url2).hostname);
|
|
24129
|
+
} catch {
|
|
24130
|
+
return false;
|
|
24131
|
+
}
|
|
24132
|
+
}
|
|
24133
|
+
|
|
24049
24134
|
// ../core/dist/service.js
|
|
24050
24135
|
import { createHash, randomUUID } from "node:crypto";
|
|
24051
24136
|
function applyTagRules(tags, allowedTags) {
|
|
@@ -24459,7 +24544,7 @@ function defaultConfigPath(env = process.env) {
|
|
|
24459
24544
|
}
|
|
24460
24545
|
|
|
24461
24546
|
// src/config.ts
|
|
24462
|
-
import { readFileSync as readFileSync2 } from "node:fs";
|
|
24547
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
24463
24548
|
import { dirname as dirname3, isAbsolute, join as join3, relative, resolve, sep } from "node:path";
|
|
24464
24549
|
|
|
24465
24550
|
// src/credentials.ts
|
|
@@ -24473,6 +24558,92 @@ import {
|
|
|
24473
24558
|
writeFileSync
|
|
24474
24559
|
} from "node:fs";
|
|
24475
24560
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
24561
|
+
var CREDENTIALS_FILENAME = "credentials.json";
|
|
24562
|
+
var CREDENTIALS_MODE = 384;
|
|
24563
|
+
var HOME_MODE = 448;
|
|
24564
|
+
function defaultCredentialsPath(env) {
|
|
24565
|
+
return join2(defaultQuibbleHome(env), CREDENTIALS_FILENAME);
|
|
24566
|
+
}
|
|
24567
|
+
function readEntry(value) {
|
|
24568
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
24569
|
+
return void 0;
|
|
24570
|
+
}
|
|
24571
|
+
const raw = value;
|
|
24572
|
+
const keys = ["endpoint", "refreshToken", "accessToken", "expiresAt", "sub", "email"];
|
|
24573
|
+
for (const key of keys) {
|
|
24574
|
+
if (typeof raw[key] !== "string" || raw[key] === "") return void 0;
|
|
24575
|
+
}
|
|
24576
|
+
return {
|
|
24577
|
+
endpoint: raw["endpoint"],
|
|
24578
|
+
refreshToken: raw["refreshToken"],
|
|
24579
|
+
accessToken: raw["accessToken"],
|
|
24580
|
+
expiresAt: raw["expiresAt"],
|
|
24581
|
+
sub: raw["sub"],
|
|
24582
|
+
email: raw["email"]
|
|
24583
|
+
};
|
|
24584
|
+
}
|
|
24585
|
+
function readCredentials(env) {
|
|
24586
|
+
const path = defaultCredentialsPath(env);
|
|
24587
|
+
let text;
|
|
24588
|
+
try {
|
|
24589
|
+
text = readFileSync(path, "utf8");
|
|
24590
|
+
} catch (error62) {
|
|
24591
|
+
if (isNotFound(error62)) return {};
|
|
24592
|
+
throw new ConfigError(`cannot read ${path}`, path, { cause: error62 });
|
|
24593
|
+
}
|
|
24594
|
+
let parsed;
|
|
24595
|
+
try {
|
|
24596
|
+
parsed = JSON.parse(text);
|
|
24597
|
+
} catch (error62) {
|
|
24598
|
+
throw new ConfigError(`invalid JSON in ${path}`, path, { cause: error62 });
|
|
24599
|
+
}
|
|
24600
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
24601
|
+
throw new ConfigError(`invalid credentials in ${path}: must be a JSON object`, path);
|
|
24602
|
+
}
|
|
24603
|
+
const entries = {};
|
|
24604
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
24605
|
+
const entry = readEntry(value);
|
|
24606
|
+
if (entry !== void 0) entries[key] = entry;
|
|
24607
|
+
}
|
|
24608
|
+
return entries;
|
|
24609
|
+
}
|
|
24610
|
+
function findCredential(env, endpoint) {
|
|
24611
|
+
return readCredentials(env)[normalizeEndpoint(endpoint)];
|
|
24612
|
+
}
|
|
24613
|
+
function writeCredentials(env, entries) {
|
|
24614
|
+
const path = defaultCredentialsPath(env);
|
|
24615
|
+
mkdirSync(dirname2(path), { recursive: true, mode: HOME_MODE });
|
|
24616
|
+
const temp = join2(
|
|
24617
|
+
dirname2(path),
|
|
24618
|
+
`.${CREDENTIALS_FILENAME}.${randomBytes(6).toString("hex")}`
|
|
24619
|
+
);
|
|
24620
|
+
try {
|
|
24621
|
+
writeFileSync(temp, `${JSON.stringify(entries, null, 2)}
|
|
24622
|
+
`, {
|
|
24623
|
+
encoding: "utf8",
|
|
24624
|
+
mode: CREDENTIALS_MODE
|
|
24625
|
+
});
|
|
24626
|
+
chmodSync(temp, CREDENTIALS_MODE);
|
|
24627
|
+
renameSync(temp, path);
|
|
24628
|
+
} catch (error62) {
|
|
24629
|
+
rmSync(temp, { force: true });
|
|
24630
|
+
throw error62;
|
|
24631
|
+
}
|
|
24632
|
+
}
|
|
24633
|
+
function saveCredential(env, credential) {
|
|
24634
|
+
const key = normalizeEndpoint(credential.endpoint);
|
|
24635
|
+
const stored = { ...credential, endpoint: key };
|
|
24636
|
+
writeCredentials(env, { ...readCredentials(env), [key]: stored });
|
|
24637
|
+
return stored;
|
|
24638
|
+
}
|
|
24639
|
+
function removeCredential(env, endpoint) {
|
|
24640
|
+
const entries = readCredentials(env);
|
|
24641
|
+
const key = normalizeEndpoint(endpoint);
|
|
24642
|
+
if (entries[key] === void 0) return false;
|
|
24643
|
+
delete entries[key];
|
|
24644
|
+
writeCredentials(env, entries);
|
|
24645
|
+
return true;
|
|
24646
|
+
}
|
|
24476
24647
|
|
|
24477
24648
|
// src/errors.ts
|
|
24478
24649
|
var AMBIGUOUS_ID = "ambiguous_id";
|
|
@@ -24525,7 +24696,7 @@ var DEBUG_ENV = "QUIBBLE_DEBUG";
|
|
|
24525
24696
|
function debugEnabled(env) {
|
|
24526
24697
|
return env[DEBUG_ENV] === "1";
|
|
24527
24698
|
}
|
|
24528
|
-
function
|
|
24699
|
+
function trimSlash2(url2) {
|
|
24529
24700
|
return url2.replace(/\/+$/, "");
|
|
24530
24701
|
}
|
|
24531
24702
|
function originOf(url2) {
|
|
@@ -24629,103 +24800,6 @@ function remoteFailure(site, response, text) {
|
|
|
24629
24800
|
});
|
|
24630
24801
|
}
|
|
24631
24802
|
|
|
24632
|
-
// src/credentials.ts
|
|
24633
|
-
var CREDENTIALS_FILENAME = "credentials.json";
|
|
24634
|
-
var CREDENTIALS_MODE = 384;
|
|
24635
|
-
var HOME_MODE = 448;
|
|
24636
|
-
function defaultCredentialsPath(env) {
|
|
24637
|
-
return join2(defaultQuibbleHome(env), CREDENTIALS_FILENAME);
|
|
24638
|
-
}
|
|
24639
|
-
function normalizeEndpoint(endpoint) {
|
|
24640
|
-
const text = trimSlash(endpoint.trim());
|
|
24641
|
-
try {
|
|
24642
|
-
const url2 = new URL(text);
|
|
24643
|
-
return `${url2.protocol}//${url2.host}${trimSlash(url2.pathname)}`;
|
|
24644
|
-
} catch {
|
|
24645
|
-
return text;
|
|
24646
|
-
}
|
|
24647
|
-
}
|
|
24648
|
-
function readEntry(value) {
|
|
24649
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
24650
|
-
return void 0;
|
|
24651
|
-
}
|
|
24652
|
-
const raw = value;
|
|
24653
|
-
const keys = ["endpoint", "refreshToken", "accessToken", "expiresAt", "sub", "email"];
|
|
24654
|
-
for (const key of keys) {
|
|
24655
|
-
if (typeof raw[key] !== "string" || raw[key] === "") return void 0;
|
|
24656
|
-
}
|
|
24657
|
-
return {
|
|
24658
|
-
endpoint: raw["endpoint"],
|
|
24659
|
-
refreshToken: raw["refreshToken"],
|
|
24660
|
-
accessToken: raw["accessToken"],
|
|
24661
|
-
expiresAt: raw["expiresAt"],
|
|
24662
|
-
sub: raw["sub"],
|
|
24663
|
-
email: raw["email"]
|
|
24664
|
-
};
|
|
24665
|
-
}
|
|
24666
|
-
function readCredentials(env) {
|
|
24667
|
-
const path = defaultCredentialsPath(env);
|
|
24668
|
-
let text;
|
|
24669
|
-
try {
|
|
24670
|
-
text = readFileSync(path, "utf8");
|
|
24671
|
-
} catch (error62) {
|
|
24672
|
-
if (isNotFound(error62)) return {};
|
|
24673
|
-
throw new ConfigError(`cannot read ${path}`, path, { cause: error62 });
|
|
24674
|
-
}
|
|
24675
|
-
let parsed;
|
|
24676
|
-
try {
|
|
24677
|
-
parsed = JSON.parse(text);
|
|
24678
|
-
} catch (error62) {
|
|
24679
|
-
throw new ConfigError(`invalid JSON in ${path}`, path, { cause: error62 });
|
|
24680
|
-
}
|
|
24681
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
24682
|
-
throw new ConfigError(`invalid credentials in ${path}: must be a JSON object`, path);
|
|
24683
|
-
}
|
|
24684
|
-
const entries = {};
|
|
24685
|
-
for (const [key, value] of Object.entries(parsed)) {
|
|
24686
|
-
const entry = readEntry(value);
|
|
24687
|
-
if (entry !== void 0) entries[key] = entry;
|
|
24688
|
-
}
|
|
24689
|
-
return entries;
|
|
24690
|
-
}
|
|
24691
|
-
function findCredential(env, endpoint) {
|
|
24692
|
-
return readCredentials(env)[normalizeEndpoint(endpoint)];
|
|
24693
|
-
}
|
|
24694
|
-
function writeCredentials(env, entries) {
|
|
24695
|
-
const path = defaultCredentialsPath(env);
|
|
24696
|
-
mkdirSync(dirname2(path), { recursive: true, mode: HOME_MODE });
|
|
24697
|
-
const temp = join2(
|
|
24698
|
-
dirname2(path),
|
|
24699
|
-
`.${CREDENTIALS_FILENAME}.${randomBytes(6).toString("hex")}`
|
|
24700
|
-
);
|
|
24701
|
-
try {
|
|
24702
|
-
writeFileSync(temp, `${JSON.stringify(entries, null, 2)}
|
|
24703
|
-
`, {
|
|
24704
|
-
encoding: "utf8",
|
|
24705
|
-
mode: CREDENTIALS_MODE
|
|
24706
|
-
});
|
|
24707
|
-
chmodSync(temp, CREDENTIALS_MODE);
|
|
24708
|
-
renameSync(temp, path);
|
|
24709
|
-
} catch (error62) {
|
|
24710
|
-
rmSync(temp, { force: true });
|
|
24711
|
-
throw error62;
|
|
24712
|
-
}
|
|
24713
|
-
}
|
|
24714
|
-
function saveCredential(env, credential) {
|
|
24715
|
-
const key = normalizeEndpoint(credential.endpoint);
|
|
24716
|
-
const stored = { ...credential, endpoint: key };
|
|
24717
|
-
writeCredentials(env, { ...readCredentials(env), [key]: stored });
|
|
24718
|
-
return stored;
|
|
24719
|
-
}
|
|
24720
|
-
function removeCredential(env, endpoint) {
|
|
24721
|
-
const entries = readCredentials(env);
|
|
24722
|
-
const key = normalizeEndpoint(endpoint);
|
|
24723
|
-
if (entries[key] === void 0) return false;
|
|
24724
|
-
delete entries[key];
|
|
24725
|
-
writeCredentials(env, entries);
|
|
24726
|
-
return true;
|
|
24727
|
-
}
|
|
24728
|
-
|
|
24729
24803
|
// src/cognito.ts
|
|
24730
24804
|
var TARGET_PREFIX = "AWSCognitoIdentityProviderService.";
|
|
24731
24805
|
var CONTENT_TYPE = "application/x-amz-json-1.1";
|
|
@@ -24736,6 +24810,7 @@ var TOO_MANY_LINE = "Too many attempts. Try again later.";
|
|
|
24736
24810
|
var WRONG_CODE_LINE = "Wrong code.";
|
|
24737
24811
|
var EXPIRED_CODE_LINE = "That code has expired. Request a new one.";
|
|
24738
24812
|
var PASSWORD_POLICY_LINE = "Use at least 12 characters, with an uppercase letter, a lowercase letter, a number and a symbol.";
|
|
24813
|
+
var BROWSER_REFUSED_LINE = "the browser approval could not be used: run quibble login again";
|
|
24739
24814
|
var UNREACHABLE_TYPE = "Unreachable";
|
|
24740
24815
|
var INCORRECT_CREDENTIALS_MESSAGE = "Incorrect username or password.";
|
|
24741
24816
|
var CognitoError = class extends RemoteError {
|
|
@@ -24752,14 +24827,15 @@ var CognitoError = class extends RemoteError {
|
|
|
24752
24827
|
function endpointUrl(region) {
|
|
24753
24828
|
return `https://cognito-idp.${region}.amazonaws.com/`;
|
|
24754
24829
|
}
|
|
24755
|
-
function errorLineOf(type, detail2,
|
|
24830
|
+
function errorLineOf(type, detail2, step) {
|
|
24756
24831
|
switch (type) {
|
|
24757
24832
|
case UNREACHABLE_TYPE:
|
|
24758
24833
|
return UNREACHABLE_LINE;
|
|
24759
24834
|
case "UserNotFoundException":
|
|
24760
|
-
return
|
|
24835
|
+
return step === "sign-in" ? WRONG_CREDENTIALS_LINE : detail2 ?? type;
|
|
24761
24836
|
case "NotAuthorizedException":
|
|
24762
|
-
|
|
24837
|
+
if (step === "browser") return BROWSER_REFUSED_LINE;
|
|
24838
|
+
return step === "sign-in" && detail2 === INCORRECT_CREDENTIALS_MESSAGE ? WRONG_CREDENTIALS_LINE : detail2 ?? type;
|
|
24763
24839
|
case "LimitExceededException":
|
|
24764
24840
|
case "TooManyRequestsException":
|
|
24765
24841
|
case "TooManyFailedAttemptsException":
|
|
@@ -24780,7 +24856,7 @@ function typeOf(value) {
|
|
|
24780
24856
|
const hash2 = value.indexOf("#");
|
|
24781
24857
|
return hash2 === -1 ? value : value.slice(hash2 + 1);
|
|
24782
24858
|
}
|
|
24783
|
-
async function call(endpoint, operation, body,
|
|
24859
|
+
async function call(endpoint, operation, body, step = "other") {
|
|
24784
24860
|
const url2 = endpointUrl(endpoint.region);
|
|
24785
24861
|
const send2 = endpoint.fetch ?? globalThis.fetch;
|
|
24786
24862
|
const site = {
|
|
@@ -24792,7 +24868,7 @@ async function call(endpoint, operation, body, signIn2 = false) {
|
|
|
24792
24868
|
throw new CognitoError(
|
|
24793
24869
|
type,
|
|
24794
24870
|
detail2,
|
|
24795
|
-
errorLineOf(type, detail2,
|
|
24871
|
+
errorLineOf(type, detail2, step),
|
|
24796
24872
|
detail(site, status)
|
|
24797
24873
|
);
|
|
24798
24874
|
};
|
|
@@ -24875,14 +24951,27 @@ function authAnswerOf(body) {
|
|
|
24875
24951
|
}
|
|
24876
24952
|
};
|
|
24877
24953
|
}
|
|
24954
|
+
function flowOf(input2) {
|
|
24955
|
+
switch (input2.flow) {
|
|
24956
|
+
case "USER_PASSWORD_AUTH":
|
|
24957
|
+
return {
|
|
24958
|
+
parameters: { USERNAME: input2.username, PASSWORD: input2.password },
|
|
24959
|
+
step: "sign-in"
|
|
24960
|
+
};
|
|
24961
|
+
case "REFRESH_TOKEN_AUTH":
|
|
24962
|
+
return { parameters: { REFRESH_TOKEN: input2.refreshToken }, step: "other" };
|
|
24963
|
+
case "CUSTOM_AUTH":
|
|
24964
|
+
return { parameters: { USERNAME: input2.username }, step: "browser" };
|
|
24965
|
+
}
|
|
24966
|
+
}
|
|
24878
24967
|
async function initiateAuth(endpoint, input2) {
|
|
24879
|
-
const
|
|
24968
|
+
const { parameters, step } = flowOf(input2);
|
|
24880
24969
|
return authAnswerOf(
|
|
24881
24970
|
await call(
|
|
24882
24971
|
endpoint,
|
|
24883
24972
|
"InitiateAuth",
|
|
24884
24973
|
{ ClientId: input2.clientId, AuthFlow: input2.flow, AuthParameters: parameters },
|
|
24885
|
-
|
|
24974
|
+
step
|
|
24886
24975
|
)
|
|
24887
24976
|
);
|
|
24888
24977
|
}
|
|
@@ -24897,6 +24986,22 @@ async function respondToAuthChallenge(endpoint, input2) {
|
|
|
24897
24986
|
})
|
|
24898
24987
|
);
|
|
24899
24988
|
}
|
|
24989
|
+
async function respondToCustomChallenge(endpoint, input2) {
|
|
24990
|
+
const username = input2.challenge.challengeParameters["USERNAME"] ?? input2.username;
|
|
24991
|
+
return authAnswerOf(
|
|
24992
|
+
await call(
|
|
24993
|
+
endpoint,
|
|
24994
|
+
"RespondToAuthChallenge",
|
|
24995
|
+
{
|
|
24996
|
+
ClientId: input2.clientId,
|
|
24997
|
+
ChallengeName: input2.challenge.challengeName,
|
|
24998
|
+
Session: input2.challenge.session,
|
|
24999
|
+
ChallengeResponses: { USERNAME: username, ANSWER: input2.answer }
|
|
25000
|
+
},
|
|
25001
|
+
"browser"
|
|
25002
|
+
)
|
|
25003
|
+
);
|
|
25004
|
+
}
|
|
24900
25005
|
async function revokeToken(endpoint, input2) {
|
|
24901
25006
|
await call(endpoint, "RevokeToken", { ClientId: input2.clientId, Token: input2.token });
|
|
24902
25007
|
}
|
|
@@ -24925,9 +25030,8 @@ function actAsRefused(sub, endpoint) {
|
|
|
24925
25030
|
function clockOf(options) {
|
|
24926
25031
|
return options.now ?? (() => /* @__PURE__ */ new Date());
|
|
24927
25032
|
}
|
|
24928
|
-
var LOCAL_HOSTS = ["localhost", "127.0.0.1"];
|
|
24929
25033
|
async function fetchAuthConfig(endpoint, options = {}) {
|
|
24930
|
-
const url2 = `${
|
|
25034
|
+
const url2 = `${trimSlash2(endpoint)}${AUTH_CONFIG_PATH}`;
|
|
24931
25035
|
const site = { method: "GET", url: url2, debug: options.debug };
|
|
24932
25036
|
const response = await send(url2, { method: "GET", ...options });
|
|
24933
25037
|
const text = await readText(response, site);
|
|
@@ -24956,13 +25060,6 @@ function bearerAuthorizer(token) {
|
|
|
24956
25060
|
};
|
|
24957
25061
|
}
|
|
24958
25062
|
var DEV_TOKEN = "dev";
|
|
24959
|
-
function isLoopback(endpoint) {
|
|
24960
|
-
try {
|
|
24961
|
-
return LOCAL_HOSTS.includes(new URL(endpoint).hostname);
|
|
24962
|
-
} catch {
|
|
24963
|
-
return false;
|
|
24964
|
-
}
|
|
24965
|
-
}
|
|
24966
25063
|
var DevAuthorizer = class {
|
|
24967
25064
|
actAs;
|
|
24968
25065
|
#endpoint;
|
|
@@ -25067,12 +25164,97 @@ function createAuthorizer(endpoint, options) {
|
|
|
25067
25164
|
return new SessionAuthorizer(endpoint, options);
|
|
25068
25165
|
}
|
|
25069
25166
|
|
|
25167
|
+
// src/endpoint.ts
|
|
25168
|
+
var DISCOVERY_TIMEOUT_MS = 5e3;
|
|
25169
|
+
var SCHEMES = ["http:", "https:"];
|
|
25170
|
+
function parseEndpoint(value, from) {
|
|
25171
|
+
const text = value.trim();
|
|
25172
|
+
if (text.toLowerCase() === LOCAL_ENDPOINT) return { kind: "local" };
|
|
25173
|
+
const invalid = new ConfigError(
|
|
25174
|
+
`invalid endpoint "${text}" from ${from}: pass an https URL, a host, or local`
|
|
25175
|
+
);
|
|
25176
|
+
let url2 = text;
|
|
25177
|
+
if (!text.includes("://")) {
|
|
25178
|
+
url2 = isLoopbackUrl(`http://${text}`) ? `http://${text}` : `https://${text}`;
|
|
25179
|
+
}
|
|
25180
|
+
let parsed;
|
|
25181
|
+
try {
|
|
25182
|
+
parsed = new URL(url2);
|
|
25183
|
+
} catch {
|
|
25184
|
+
throw invalid;
|
|
25185
|
+
}
|
|
25186
|
+
if (!SCHEMES.includes(parsed.protocol) || parsed.hostname === "") throw invalid;
|
|
25187
|
+
return { kind: "url", url: normalizeEndpoint(url2) };
|
|
25188
|
+
}
|
|
25189
|
+
var discoveries = /* @__PURE__ */ new Map();
|
|
25190
|
+
function resetDiscovery() {
|
|
25191
|
+
discoveries.clear();
|
|
25192
|
+
}
|
|
25193
|
+
function discoverApiUrl(url2, options = {}) {
|
|
25194
|
+
const key = normalizeEndpoint(url2);
|
|
25195
|
+
if (isDefaultEndpoint(key)) return Promise.resolve(DEFAULT_ENDPOINT);
|
|
25196
|
+
const known = discoveries.get(key);
|
|
25197
|
+
if (known !== void 0) return known;
|
|
25198
|
+
const found = lookup(key, options).then((api) => {
|
|
25199
|
+
if (!discoveries.has(api)) discoveries.set(api, Promise.resolve(api));
|
|
25200
|
+
return api;
|
|
25201
|
+
});
|
|
25202
|
+
discoveries.set(key, found);
|
|
25203
|
+
return found;
|
|
25204
|
+
}
|
|
25205
|
+
async function lookup(url2, options) {
|
|
25206
|
+
const target = `${url2}${DISCOVERY_PATH}`;
|
|
25207
|
+
try {
|
|
25208
|
+
const response = await send(target, {
|
|
25209
|
+
method: "GET",
|
|
25210
|
+
headers: { accept: "application/json" },
|
|
25211
|
+
fetch: options.fetch,
|
|
25212
|
+
debug: options.debug,
|
|
25213
|
+
timeoutMs: options.timeoutMs ?? DISCOVERY_TIMEOUT_MS
|
|
25214
|
+
});
|
|
25215
|
+
const text = await readText(response, { method: "GET", url: target });
|
|
25216
|
+
if (!response.ok) return url2;
|
|
25217
|
+
const api = stringOf(asRecord(parseQuietly(text)), "api");
|
|
25218
|
+
if (api === void 0) return url2;
|
|
25219
|
+
const parsed = new URL(api.trim());
|
|
25220
|
+
if (!SCHEMES.includes(parsed.protocol)) return url2;
|
|
25221
|
+
return normalizeEndpoint(api);
|
|
25222
|
+
} catch {
|
|
25223
|
+
return url2;
|
|
25224
|
+
}
|
|
25225
|
+
}
|
|
25226
|
+
|
|
25070
25227
|
// src/config.ts
|
|
25071
|
-
var NO_ENDPOINT_MESSAGE = "no endpoint: pass --endpoint <url>, set QUIBBLE_ENDPOINT, or add one to .quibblerc";
|
|
25072
25228
|
function requireEndpoint(context) {
|
|
25073
|
-
if (context.endpoint === void 0)
|
|
25229
|
+
if (context.endpoint === void 0) {
|
|
25230
|
+
throw new ConfigError(`local mode has no endpoint: ${context.localFix ?? ""}`);
|
|
25231
|
+
}
|
|
25074
25232
|
return context.endpoint;
|
|
25075
25233
|
}
|
|
25234
|
+
async function resolveEndpoint(context, io) {
|
|
25235
|
+
const endpoint = requireEndpoint(context);
|
|
25236
|
+
if (context.discovered) return endpoint;
|
|
25237
|
+
const api = await discoverApiUrl(endpoint, {
|
|
25238
|
+
fetch: io.fetch,
|
|
25239
|
+
debug: context.debug,
|
|
25240
|
+
timeoutMs: context.timeoutMs
|
|
25241
|
+
});
|
|
25242
|
+
context.endpoint = api;
|
|
25243
|
+
context.credential = findCredential(io.env, api);
|
|
25244
|
+
context.discovered = true;
|
|
25245
|
+
return api;
|
|
25246
|
+
}
|
|
25247
|
+
function foreignOrgMessage(context) {
|
|
25248
|
+
const slug = context.slug;
|
|
25249
|
+
if (context.mode !== "local" || slug === void 0 || slug === LOCAL_ORG) {
|
|
25250
|
+
return void 0;
|
|
25251
|
+
}
|
|
25252
|
+
return `local mode holds the organization ${LOCAL_ORG} only, not ${slug}: ${context.localFix ?? ""} to file to ${slug} on a server, or pass --org ${LOCAL_ORG}`;
|
|
25253
|
+
}
|
|
25254
|
+
function refuseForeignOrg(context) {
|
|
25255
|
+
const message = foreignOrgMessage(context);
|
|
25256
|
+
if (message !== void 0) throw new ConfigError(message);
|
|
25257
|
+
}
|
|
25076
25258
|
var NO_ORG_MESSAGE = "no organization: pass --org <slug> or run `quibble init --org <slug>`";
|
|
25077
25259
|
function requireOrg(context) {
|
|
25078
25260
|
if (context.org === void 0) throw new ConfigError(NO_ORG_MESSAGE);
|
|
@@ -25196,20 +25378,76 @@ function firstValue(...values) {
|
|
|
25196
25378
|
}
|
|
25197
25379
|
return void 0;
|
|
25198
25380
|
}
|
|
25199
|
-
function
|
|
25200
|
-
|
|
25201
|
-
|
|
25202
|
-
|
|
25203
|
-
|
|
25204
|
-
|
|
25205
|
-
|
|
25206
|
-
|
|
25381
|
+
function localFixOf(candidate) {
|
|
25382
|
+
switch (candidate.source) {
|
|
25383
|
+
case "--endpoint":
|
|
25384
|
+
return "drop --endpoint local";
|
|
25385
|
+
case "QUIBBLE_ENDPOINT":
|
|
25386
|
+
return "unset QUIBBLE_ENDPOINT";
|
|
25387
|
+
default:
|
|
25388
|
+
return `remove "endpoint": "local" from ${candidate.path ?? ""}`;
|
|
25389
|
+
}
|
|
25390
|
+
}
|
|
25391
|
+
function resolveEndpointSource(options, project, user) {
|
|
25392
|
+
if (options.local === true) {
|
|
25393
|
+
return {
|
|
25394
|
+
mode: "local",
|
|
25395
|
+
endpoint: void 0,
|
|
25396
|
+
endpointSource: "--local",
|
|
25397
|
+
localFix: "drop --local"
|
|
25398
|
+
};
|
|
25399
|
+
}
|
|
25400
|
+
const candidates2 = [
|
|
25401
|
+
{ source: "--endpoint", value: options.endpoint },
|
|
25402
|
+
{ source: "QUIBBLE_ENDPOINT", value: options.env["QUIBBLE_ENDPOINT"] },
|
|
25403
|
+
{ source: ".quibblerc", value: project?.endpoint, path: project?.path },
|
|
25404
|
+
{
|
|
25405
|
+
source: "config.json",
|
|
25406
|
+
value: user.endpoint,
|
|
25407
|
+
path: defaultConfigPath(options.env)
|
|
25408
|
+
}
|
|
25409
|
+
];
|
|
25410
|
+
const chosen = candidates2.find(
|
|
25411
|
+
(candidate) => candidate.value !== void 0 && candidate.value.trim() !== ""
|
|
25207
25412
|
);
|
|
25208
|
-
|
|
25209
|
-
|
|
25210
|
-
|
|
25413
|
+
if (chosen === void 0) {
|
|
25414
|
+
return {
|
|
25415
|
+
mode: "remote",
|
|
25416
|
+
endpoint: DEFAULT_ENDPOINT,
|
|
25417
|
+
endpointSource: "default",
|
|
25418
|
+
localFix: void 0
|
|
25419
|
+
};
|
|
25420
|
+
}
|
|
25421
|
+
const value = parseEndpoint(chosen.value ?? "", chosen.path ?? chosen.source);
|
|
25422
|
+
if (value.kind === "local") {
|
|
25423
|
+
return {
|
|
25424
|
+
mode: "local",
|
|
25425
|
+
endpoint: void 0,
|
|
25426
|
+
endpointSource: chosen.source,
|
|
25427
|
+
localFix: localFixOf(chosen)
|
|
25428
|
+
};
|
|
25429
|
+
}
|
|
25430
|
+
return {
|
|
25431
|
+
mode: "remote",
|
|
25432
|
+
endpoint: value.url,
|
|
25433
|
+
endpointSource: chosen.source,
|
|
25434
|
+
localFix: void 0
|
|
25435
|
+
};
|
|
25436
|
+
}
|
|
25437
|
+
function resolveContext(options) {
|
|
25438
|
+
const project = findProjectConfig(options.cwd);
|
|
25439
|
+
const user = loadUserConfig(options.env);
|
|
25440
|
+
const { mode, endpoint, endpointSource, localFix } = resolveEndpointSource(
|
|
25441
|
+
options,
|
|
25442
|
+
project,
|
|
25443
|
+
user
|
|
25444
|
+
);
|
|
25445
|
+
const given = firstValue(options.project, project?.project);
|
|
25446
|
+
const name = given === void 0 ? void 0 : foldProjectName(given);
|
|
25447
|
+
const org = firstValue(options.org, project?.org);
|
|
25448
|
+
const slug = org === void 0 ? void 0 : parseOrgSlug(org);
|
|
25211
25449
|
const token = firstValue(options.env[TOKEN_ENV]);
|
|
25212
|
-
const
|
|
25450
|
+
const discovered = endpoint !== void 0 && isDefaultEndpoint(endpoint);
|
|
25213
25451
|
return {
|
|
25214
25452
|
project: name,
|
|
25215
25453
|
org: mode === "local" ? LOCAL_ORG : slug,
|
|
@@ -25219,10 +25457,12 @@ function resolveContext(options) {
|
|
|
25219
25457
|
cwd: project === void 0 ? void 0 : relativeCwd(project.root, options.cwd),
|
|
25220
25458
|
mode,
|
|
25221
25459
|
endpoint,
|
|
25460
|
+
endpointSource,
|
|
25461
|
+
localFix,
|
|
25462
|
+
discovered,
|
|
25463
|
+
timeoutMs: void 0,
|
|
25222
25464
|
token,
|
|
25223
|
-
|
|
25224
|
-
// and no command has to read the file to describe the session.
|
|
25225
|
-
credential: endpoint === void 0 ? void 0 : findCredential(options.env, endpoint),
|
|
25465
|
+
credential: discovered && endpoint !== void 0 ? findCredential(options.env, endpoint) : void 0,
|
|
25226
25466
|
actAs: firstValue(options.env[ACT_AS_ENV]),
|
|
25227
25467
|
debug: debugEnabled(options.env),
|
|
25228
25468
|
agent: firstValue(options.agent, user.agent) ?? DEFAULT_AGENT,
|
|
@@ -25235,6 +25475,15 @@ function resolveContext(options) {
|
|
|
25235
25475
|
projectConfig: project
|
|
25236
25476
|
};
|
|
25237
25477
|
}
|
|
25478
|
+
function rememberEndpoint(env, endpoint) {
|
|
25479
|
+
const path = defaultConfigPath(env);
|
|
25480
|
+
const raw = readJsonFile(path) ?? {};
|
|
25481
|
+
if (stringField(raw, "endpoint", path) !== void 0) return void 0;
|
|
25482
|
+
mkdirSync2(dirname3(path), { recursive: true });
|
|
25483
|
+
writeFileSync2(path, `${JSON.stringify({ ...raw, endpoint }, null, 2)}
|
|
25484
|
+
`, "utf8");
|
|
25485
|
+
return path;
|
|
25486
|
+
}
|
|
25238
25487
|
|
|
25239
25488
|
// src/ids.ts
|
|
25240
25489
|
var FULL_ID_LENGTH = 36;
|
|
@@ -25438,7 +25687,7 @@ var ApiClient = class {
|
|
|
25438
25687
|
constructor(options) {
|
|
25439
25688
|
this.endpoint = options.endpoint;
|
|
25440
25689
|
this.org = options.org;
|
|
25441
|
-
this.#base =
|
|
25690
|
+
this.#base = trimSlash2(options.endpoint);
|
|
25442
25691
|
this.#auth = options.authorizer;
|
|
25443
25692
|
this.#beforeFirst = options.beforeFirst;
|
|
25444
25693
|
this.#http = {
|
|
@@ -25516,6 +25765,13 @@ var ApiClient = class {
|
|
|
25516
25765
|
const retagged = answer2.body["retagged"];
|
|
25517
25766
|
return { ...answer2, retagged: typeof retagged === "number" ? retagged : 0 };
|
|
25518
25767
|
}
|
|
25768
|
+
/**
|
|
25769
|
+
* Soft-deletes a project (section 7.5). The route hides it and its
|
|
25770
|
+
* complaints everywhere and revokes its tokens, and answers `204`.
|
|
25771
|
+
*/
|
|
25772
|
+
async deleteProject(name) {
|
|
25773
|
+
await this.request("DELETE", this.projectPath(name));
|
|
25774
|
+
}
|
|
25519
25775
|
/** Lists the tokens of a project, newest first, as the route sorts them. */
|
|
25520
25776
|
async tokens(project) {
|
|
25521
25777
|
const body = await this.request("GET", `${this.projectPath(project)}/tokens`);
|
|
@@ -25804,7 +26060,7 @@ import {
|
|
|
25804
26060
|
closeSync,
|
|
25805
26061
|
fstatSync,
|
|
25806
26062
|
linkSync,
|
|
25807
|
-
mkdirSync as
|
|
26063
|
+
mkdirSync as mkdirSync3,
|
|
25808
26064
|
openSync,
|
|
25809
26065
|
readFileSync as readFileSync3,
|
|
25810
26066
|
readSync,
|
|
@@ -25878,7 +26134,7 @@ function endsInsideLine(path) {
|
|
|
25878
26134
|
}
|
|
25879
26135
|
function appendLine(path, value) {
|
|
25880
26136
|
try {
|
|
25881
|
-
|
|
26137
|
+
mkdirSync3(dirname4(path), { recursive: true, mode: HOME_MODE });
|
|
25882
26138
|
const lead = endsInsideLine(path) ? "\n" : "";
|
|
25883
26139
|
appendFileSync(path, `${lead}${JSON.stringify(value)}
|
|
25884
26140
|
`, {
|
|
@@ -25996,7 +26252,7 @@ function createLock(lock, owner = { pid: process.pid, host: hostname3(), at: (/*
|
|
|
25996
26252
|
const text = JSON.stringify(owner);
|
|
25997
26253
|
let fd;
|
|
25998
26254
|
try {
|
|
25999
|
-
|
|
26255
|
+
mkdirSync3(dirname4(lock), { recursive: true, mode: HOME_MODE });
|
|
26000
26256
|
fd = openSync(lock, "wx", QUEUE_MODE);
|
|
26001
26257
|
} catch (error62) {
|
|
26002
26258
|
if (error62.code === "EEXIST") return void 0;
|
|
@@ -26046,8 +26302,8 @@ function releaseLock(lock, text) {
|
|
|
26046
26302
|
}
|
|
26047
26303
|
function stopReason(status, text) {
|
|
26048
26304
|
if (status >= 500) return `the API answered ${status}`;
|
|
26049
|
-
const
|
|
26050
|
-
const stops = status === 429 || status === 401 || status === 403 || status === 404 &&
|
|
26305
|
+
const code2 = serverCode(text);
|
|
26306
|
+
const stops = status === 429 || status === 401 || status === 403 || status === 404 && code2 === ORG_NOT_FOUND;
|
|
26051
26307
|
if (!stops) return void 0;
|
|
26052
26308
|
const message = stringOf(asRecord(asRecord(parseQuietly(text))?.["error"]), "message");
|
|
26053
26309
|
return message ?? `the API answered ${status}`;
|
|
@@ -26117,12 +26373,12 @@ async function flushQueue(options) {
|
|
|
26117
26373
|
return result;
|
|
26118
26374
|
}
|
|
26119
26375
|
const record2 = asRecord(asRecord(parseQuietly(text))?.["error"]);
|
|
26120
|
-
const
|
|
26376
|
+
const code2 = stringOf(record2, "code") ?? `http_${status}`;
|
|
26121
26377
|
const message = stringOf(record2, "message") ?? `the API answered ${status}`;
|
|
26122
26378
|
appendLine(options.path, {
|
|
26123
26379
|
op: "dropped",
|
|
26124
26380
|
key: entry.key,
|
|
26125
|
-
code,
|
|
26381
|
+
code: code2,
|
|
26126
26382
|
message,
|
|
26127
26383
|
droppedAt: now().toISOString()
|
|
26128
26384
|
});
|
|
@@ -26176,23 +26432,23 @@ function actAsProblem(context) {
|
|
|
26176
26432
|
if (context.credential !== void 0) {
|
|
26177
26433
|
return `you are logged in to ${endpoint}`;
|
|
26178
26434
|
}
|
|
26179
|
-
if (!
|
|
26435
|
+
if (!isLoopbackUrl(endpoint)) return `${endpoint} is not a loopback endpoint`;
|
|
26180
26436
|
return void 0;
|
|
26181
26437
|
}
|
|
26182
26438
|
function checkActAs(context) {
|
|
26183
26439
|
const problem = actAsProblem(context);
|
|
26184
26440
|
if (problem !== void 0) throw actAsNotApplicable(problem);
|
|
26185
26441
|
}
|
|
26186
|
-
function openClient(context, io, options = {}) {
|
|
26442
|
+
async function openClient(context, io, options = {}) {
|
|
26443
|
+
const endpoint = await resolveEndpoint(context, io);
|
|
26187
26444
|
checkActAs(context);
|
|
26188
|
-
const
|
|
26189
|
-
const http = { debug: context.debug, fetch: io.fetch };
|
|
26445
|
+
const http = { debug: context.debug, fetch: io.fetch, timeoutMs: context.timeoutMs };
|
|
26190
26446
|
const org = {
|
|
26191
26447
|
...context.org === void 0 ? {} : { org: context.org },
|
|
26192
26448
|
...options.queue === false ? {} : { beforeFirst: queueSender(context, endpoint, io) }
|
|
26193
26449
|
};
|
|
26194
26450
|
if (context.token === void 0 && context.credential === void 0) {
|
|
26195
|
-
if (!
|
|
26451
|
+
if (!isLoopbackUrl(endpoint)) throw notLoggedIn(endpoint);
|
|
26196
26452
|
return new ApiClient({
|
|
26197
26453
|
endpoint,
|
|
26198
26454
|
authorizer: devAuthorizer(endpoint, context.actAs, http),
|
|
@@ -26207,26 +26463,27 @@ function openClient(context, io, options = {}) {
|
|
|
26207
26463
|
...http
|
|
26208
26464
|
});
|
|
26209
26465
|
}
|
|
26210
|
-
function openOrgClient(context, io) {
|
|
26211
|
-
checkActAs(context);
|
|
26466
|
+
async function openOrgClient(context, io, options = {}) {
|
|
26467
|
+
if (context.mode === "local") checkActAs(context);
|
|
26212
26468
|
requireOrg(context);
|
|
26213
|
-
return openClient(context, io);
|
|
26469
|
+
return openClient(context, io, options);
|
|
26214
26470
|
}
|
|
26215
|
-
function openBackend(context, io) {
|
|
26471
|
+
async function openBackend(context, io) {
|
|
26216
26472
|
if (context.mode === "local") {
|
|
26217
26473
|
checkActAs(context);
|
|
26474
|
+
refuseForeignOrg(context);
|
|
26218
26475
|
const local = new FileStore(context.storePath);
|
|
26219
26476
|
return { store: local, client: void 0, local };
|
|
26220
26477
|
}
|
|
26221
|
-
const client = openOrgClient(context, io);
|
|
26478
|
+
const client = await openOrgClient(context, io);
|
|
26222
26479
|
return { store: new RemoteStore(client), client, local: void 0 };
|
|
26223
26480
|
}
|
|
26224
|
-
function openStore(context, io) {
|
|
26225
|
-
return openBackend(context, io).store;
|
|
26481
|
+
async function openStore(context, io) {
|
|
26482
|
+
return (await openBackend(context, io)).store;
|
|
26226
26483
|
}
|
|
26227
26484
|
function needsServer(command, context) {
|
|
26228
26485
|
return new ConfigError(
|
|
26229
|
-
|
|
26486
|
+
`${command} needs a server: ${context.localFix ?? "drop --local"}`
|
|
26230
26487
|
);
|
|
26231
26488
|
}
|
|
26232
26489
|
function requireServer(command, context) {
|
|
@@ -26242,7 +26499,7 @@ async function runComplain(message, options, context, io) {
|
|
|
26242
26499
|
throw new UsageError(REMOTE_REPORTER_MESSAGE);
|
|
26243
26500
|
}
|
|
26244
26501
|
const body = await readMessage(message, io);
|
|
26245
|
-
const store = openStore(context, io);
|
|
26502
|
+
const store = await openStore(context, io);
|
|
26246
26503
|
const input2 = {
|
|
26247
26504
|
org,
|
|
26248
26505
|
project,
|
|
@@ -26266,6 +26523,7 @@ async function runComplain(message, options, context, io) {
|
|
|
26266
26523
|
}
|
|
26267
26524
|
if (options.json === true) {
|
|
26268
26525
|
writeJson(io.stdout, complaint);
|
|
26526
|
+
localNotice(context, io);
|
|
26269
26527
|
return 0;
|
|
26270
26528
|
}
|
|
26271
26529
|
const scope = context.mode === "remote" ? `${complaint.org}/${complaint.project}` : complaint.project;
|
|
@@ -26274,8 +26532,16 @@ async function runComplain(message, options, context, io) {
|
|
|
26274
26532
|
`Filed complaint ${complaint.id} for ${scope} (${complaint.severity})`
|
|
26275
26533
|
);
|
|
26276
26534
|
for (const line of matchLines(complaint)) writeLine(io.stdout, line);
|
|
26535
|
+
localNotice(context, io);
|
|
26277
26536
|
return 0;
|
|
26278
26537
|
}
|
|
26538
|
+
function localNotice(context, io) {
|
|
26539
|
+
if (context.mode !== "local") return;
|
|
26540
|
+
writeLine(
|
|
26541
|
+
io.stderr,
|
|
26542
|
+
`quibble: local mode: nothing was sent; the complaint is in ${context.storePath}`
|
|
26543
|
+
);
|
|
26544
|
+
}
|
|
26279
26545
|
function matchLines(complaint) {
|
|
26280
26546
|
const lines = [];
|
|
26281
26547
|
if (complaint.openMatches === 1) {
|
|
@@ -26319,7 +26585,7 @@ function describeCredential(context) {
|
|
|
26319
26585
|
if (context.credential !== void 0) {
|
|
26320
26586
|
return `logged in as ${context.credential.email}`;
|
|
26321
26587
|
}
|
|
26322
|
-
if (context.endpoint !== void 0 &&
|
|
26588
|
+
if (context.endpoint !== void 0 && isLoopbackUrl(context.endpoint)) {
|
|
26323
26589
|
return "loopback dev token";
|
|
26324
26590
|
}
|
|
26325
26591
|
return "not logged in";
|
|
@@ -26331,14 +26597,18 @@ function describeAuth(context) {
|
|
|
26331
26597
|
if (problem === void 0) return `${credential}, acting as ${context.actAs}`;
|
|
26332
26598
|
return `${credential}; ${ACT_AS_ENV} does not apply: ${problem}`;
|
|
26333
26599
|
}
|
|
26334
|
-
function runConfig(options, context, io) {
|
|
26600
|
+
async function runConfig(options, context, io) {
|
|
26601
|
+
if (context.mode === "remote") await resolveEndpoint(context, io);
|
|
26602
|
+
const ignored = foreignOrgMessage(context) === void 0 ? void 0 : context.slug;
|
|
26335
26603
|
const report = {
|
|
26336
26604
|
project: context.project ?? "none",
|
|
26337
26605
|
org: context.org ?? null,
|
|
26606
|
+
orgIgnored: ignored ?? null,
|
|
26338
26607
|
projectRoot: context.projectRoot ?? null,
|
|
26339
26608
|
configPath: context.configPath ?? null,
|
|
26340
26609
|
mode: context.mode,
|
|
26341
|
-
endpoint: context.endpoint ??
|
|
26610
|
+
endpoint: context.endpoint ?? "local",
|
|
26611
|
+
endpointSource: context.endpointSource,
|
|
26342
26612
|
storePath: context.storePath,
|
|
26343
26613
|
agent: context.agent,
|
|
26344
26614
|
reporter: context.reporter,
|
|
@@ -26351,16 +26621,84 @@ function runConfig(options, context, io) {
|
|
|
26351
26621
|
writeJson(io.stdout, report);
|
|
26352
26622
|
return 0;
|
|
26353
26623
|
}
|
|
26354
|
-
const
|
|
26355
|
-
|
|
26356
|
-
|
|
26624
|
+
const { orgIgnored, ...lines } = report;
|
|
26625
|
+
const text = { ...lines };
|
|
26626
|
+
if (orgIgnored !== null) {
|
|
26627
|
+
text["org"] = `${report.org ?? ""}; ${orgIgnored} does not apply: ${context.localFix ?? ""}`;
|
|
26628
|
+
}
|
|
26629
|
+
const width = Math.max(...Object.keys(text).map((key) => key.length));
|
|
26630
|
+
for (const [key, value] of Object.entries(text)) {
|
|
26631
|
+
writeLine(io.stdout, `${key.padEnd(width)} ${String(value ?? "none")}`);
|
|
26357
26632
|
}
|
|
26358
26633
|
return 0;
|
|
26359
26634
|
}
|
|
26360
26635
|
|
|
26361
26636
|
// src/commands/init.ts
|
|
26362
|
-
import { existsSync as existsSync2, writeFileSync as
|
|
26363
|
-
import { basename as basename2, join as
|
|
26637
|
+
import { existsSync as existsSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
26638
|
+
import { basename as basename2, join as join7, resolve as resolve3 } from "node:path";
|
|
26639
|
+
|
|
26640
|
+
// src/hook.ts
|
|
26641
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
26642
|
+
import { dirname as dirname5, join as join5 } from "node:path";
|
|
26643
|
+
var SETTINGS_PATH = ".claude/settings.json";
|
|
26644
|
+
var HOOK_COMMAND = "command -v quibble >/dev/null 2>&1 && quibble sync --quiet || true";
|
|
26645
|
+
var HOOK_MARK = "quibble sync --quiet";
|
|
26646
|
+
function hookEntry() {
|
|
26647
|
+
return {
|
|
26648
|
+
matcher: "startup",
|
|
26649
|
+
hooks: [{ type: "command", command: HOOK_COMMAND }]
|
|
26650
|
+
};
|
|
26651
|
+
}
|
|
26652
|
+
function isObject2(value) {
|
|
26653
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
26654
|
+
}
|
|
26655
|
+
function runsSync(entry) {
|
|
26656
|
+
if (!isObject2(entry) || !Array.isArray(entry["hooks"])) return false;
|
|
26657
|
+
return entry["hooks"].some(
|
|
26658
|
+
(hook) => isObject2(hook) && typeof hook["command"] === "string" && hook["command"].includes(HOOK_MARK)
|
|
26659
|
+
);
|
|
26660
|
+
}
|
|
26661
|
+
function planHook(root) {
|
|
26662
|
+
const path = join5(root, SETTINGS_PATH);
|
|
26663
|
+
let text;
|
|
26664
|
+
try {
|
|
26665
|
+
text = readFileSync4(path, "utf8");
|
|
26666
|
+
} catch (error62) {
|
|
26667
|
+
if (!isNotFound(error62)) throw error62;
|
|
26668
|
+
const created = { hooks: { SessionStart: [hookEntry()] } };
|
|
26669
|
+
return { path, text: `${JSON.stringify(created, null, 2)}
|
|
26670
|
+
` };
|
|
26671
|
+
}
|
|
26672
|
+
const invalid = new UsageError(
|
|
26673
|
+
`${path} is not valid JSON: fix it, then run the command again`
|
|
26674
|
+
);
|
|
26675
|
+
let settings;
|
|
26676
|
+
try {
|
|
26677
|
+
settings = JSON.parse(text);
|
|
26678
|
+
} catch {
|
|
26679
|
+
throw invalid;
|
|
26680
|
+
}
|
|
26681
|
+
if (!isObject2(settings)) throw invalid;
|
|
26682
|
+
const hooks = settings["hooks"] === void 0 ? {} : settings["hooks"];
|
|
26683
|
+
if (!isObject2(hooks)) throw new UsageError(`${path}: hooks must be an object`);
|
|
26684
|
+
const start = hooks["SessionStart"] === void 0 ? [] : hooks["SessionStart"];
|
|
26685
|
+
if (!Array.isArray(start)) {
|
|
26686
|
+
throw new UsageError(`${path}: hooks.SessionStart must be an array`);
|
|
26687
|
+
}
|
|
26688
|
+
if (start.some(runsSync)) return { path, text: void 0 };
|
|
26689
|
+
const merged = {
|
|
26690
|
+
...settings,
|
|
26691
|
+
hooks: { ...hooks, SessionStart: [...start, hookEntry()] }
|
|
26692
|
+
};
|
|
26693
|
+
return { path, text: `${JSON.stringify(merged, null, 2)}
|
|
26694
|
+
` };
|
|
26695
|
+
}
|
|
26696
|
+
function applyHook(plan) {
|
|
26697
|
+
if (plan.text === void 0) return `${plan.path} already runs quibble sync`;
|
|
26698
|
+
mkdirSync4(dirname5(plan.path), { recursive: true });
|
|
26699
|
+
writeFileSync3(plan.path, plan.text, "utf8");
|
|
26700
|
+
return `Added the quibble sync hook to ${plan.path}`;
|
|
26701
|
+
}
|
|
26364
26702
|
|
|
26365
26703
|
// src/snippet.ts
|
|
26366
26704
|
import { basename } from "node:path";
|
|
@@ -26423,57 +26761,131 @@ function agentForFile(path) {
|
|
|
26423
26761
|
function hasSnippet(text) {
|
|
26424
26762
|
return text.split(/\r?\n/).some((line) => line.trimEnd() === SNIPPET_HEADING);
|
|
26425
26763
|
}
|
|
26764
|
+
function splitLines(text) {
|
|
26765
|
+
const lines = [];
|
|
26766
|
+
const pattern = /([^\r\n]*)(\r\n|\n|\r)?/g;
|
|
26767
|
+
let match;
|
|
26768
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
26769
|
+
const [whole, content = "", eol = ""] = match;
|
|
26770
|
+
if (whole === "") break;
|
|
26771
|
+
lines.push({ text: content, eol });
|
|
26772
|
+
}
|
|
26773
|
+
return lines;
|
|
26774
|
+
}
|
|
26775
|
+
function fenceOf(text) {
|
|
26776
|
+
if (text.startsWith("```")) return "```";
|
|
26777
|
+
if (text.startsWith("~~~")) return "~~~";
|
|
26778
|
+
return void 0;
|
|
26779
|
+
}
|
|
26780
|
+
function endsSection(text) {
|
|
26781
|
+
return text.startsWith("# ") || text.startsWith("## ");
|
|
26782
|
+
}
|
|
26783
|
+
function agentOfText(lines) {
|
|
26784
|
+
const line = lines.find(
|
|
26785
|
+
(text) => text.includes("quibble complain") && text.includes("--agent")
|
|
26786
|
+
);
|
|
26787
|
+
if (line === void 0) return void 0;
|
|
26788
|
+
return /--agent\s+(\S+)/.exec(line)?.[1];
|
|
26789
|
+
}
|
|
26790
|
+
function findSection(text) {
|
|
26791
|
+
const lines = splitLines(text);
|
|
26792
|
+
const start = lines.findIndex((line) => line.text.trimEnd() === SNIPPET_HEADING);
|
|
26793
|
+
if (start < 0) return void 0;
|
|
26794
|
+
let next = lines.length;
|
|
26795
|
+
let fence;
|
|
26796
|
+
for (let index = start + 1; index < lines.length; index += 1) {
|
|
26797
|
+
const content = lines[index]?.text ?? "";
|
|
26798
|
+
if (fence !== void 0) {
|
|
26799
|
+
if (content.startsWith(fence)) fence = void 0;
|
|
26800
|
+
continue;
|
|
26801
|
+
}
|
|
26802
|
+
fence = fenceOf(content);
|
|
26803
|
+
if (fence === void 0 && endsSection(content)) {
|
|
26804
|
+
next = index;
|
|
26805
|
+
break;
|
|
26806
|
+
}
|
|
26807
|
+
}
|
|
26808
|
+
let end = next;
|
|
26809
|
+
while (end > start + 1 && (lines[end - 1]?.text.trim() ?? "") === "") end -= 1;
|
|
26810
|
+
const eol = lines[start]?.eol || "\n";
|
|
26811
|
+
return { lines, start, end, eol: eol === "\r\n" ? "\r\n" : "\n" };
|
|
26812
|
+
}
|
|
26813
|
+
function sectionText(section) {
|
|
26814
|
+
return section.lines.slice(section.start, section.end).map((line) => line.text);
|
|
26815
|
+
}
|
|
26816
|
+
function replaceSection(section, snippet) {
|
|
26817
|
+
const join9 = (lines) => lines.map((line) => `${line.text}${line.eol}`).join("");
|
|
26818
|
+
const body = snippet.split("\n").map((line) => `${line}${section.eol}`).join("");
|
|
26819
|
+
return `${join9(section.lines.slice(0, section.start))}${body}${join9(section.lines.slice(section.end))}`;
|
|
26820
|
+
}
|
|
26821
|
+
function withLineBreak(text, old) {
|
|
26822
|
+
return old.includes("\r\n") ? text.replace(/\r?\n/g, "\r\n") : text;
|
|
26823
|
+
}
|
|
26824
|
+
function orList(items) {
|
|
26825
|
+
if (items.length <= 1) return items.join("");
|
|
26826
|
+
return `${items.slice(0, -1).join(", ")} or ${items.at(-1) ?? ""}`;
|
|
26827
|
+
}
|
|
26828
|
+
function refreshedLine(project, tags) {
|
|
26829
|
+
const lead = `Quibble refreshed its instructions for ${project}:`;
|
|
26830
|
+
if (tags === void 0 || tags.length === 0) return `${lead} any tag is allowed.`;
|
|
26831
|
+
return `${lead} tag each complaint with ${orList(tags)}, or ${OTHER_TAG_PREFIX}<name> when none fits.`;
|
|
26832
|
+
}
|
|
26426
26833
|
|
|
26427
26834
|
// src/commands/snippet.ts
|
|
26428
|
-
import { existsSync, mkdirSync as
|
|
26429
|
-
import { dirname as
|
|
26835
|
+
import { existsSync, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
26836
|
+
import { dirname as dirname6, join as join6, resolve as resolve2 } from "node:path";
|
|
26430
26837
|
function existsError(path) {
|
|
26431
26838
|
return new UsageError(`${path} already exists: pass --force to overwrite it`);
|
|
26432
26839
|
}
|
|
26433
26840
|
function readOptional2(path) {
|
|
26434
26841
|
try {
|
|
26435
|
-
return
|
|
26842
|
+
return readFileSync5(path, "utf8");
|
|
26436
26843
|
} catch (error62) {
|
|
26437
26844
|
if (isNotFound(error62)) return "";
|
|
26438
26845
|
throw error62;
|
|
26439
26846
|
}
|
|
26440
26847
|
}
|
|
26441
26848
|
function writeSkill(path, snippet) {
|
|
26442
|
-
|
|
26443
|
-
|
|
26849
|
+
mkdirSync5(dirname6(path), { recursive: true });
|
|
26850
|
+
writeFileSync4(path, skillFile(snippet), "utf8");
|
|
26851
|
+
}
|
|
26852
|
+
async function snippetSource(project, context, io, options = {}) {
|
|
26853
|
+
let tags;
|
|
26854
|
+
if (context.mode === "remote") {
|
|
26855
|
+
tags = (await (await openOrgClient(context, io, options)).project(project)).tags;
|
|
26856
|
+
} else {
|
|
26857
|
+
checkActAs(context);
|
|
26858
|
+
tags = context.allowedTags;
|
|
26859
|
+
}
|
|
26860
|
+
const scope = {
|
|
26861
|
+
...context.mode === "remote" ? { endpoint: context.endpoint } : {},
|
|
26862
|
+
...context.slug === void 0 ? {} : { org: context.slug }
|
|
26863
|
+
};
|
|
26864
|
+
return { tags, scope };
|
|
26444
26865
|
}
|
|
26445
26866
|
async function runSnippet(options, context, io) {
|
|
26446
26867
|
const project = requireProject(context);
|
|
26447
26868
|
const override = options.agent === void 0 ? void 0 : parseAgentName(options.agent);
|
|
26448
26869
|
const appendPath = options.append === void 0 ? void 0 : resolve2(io.cwd, options.append);
|
|
26449
|
-
const skillPath = options.skill === true ?
|
|
26870
|
+
const skillPath = options.skill === true ? join6(context.projectRoot ?? io.cwd, SKILL_PATH) : void 0;
|
|
26450
26871
|
const existing = appendPath === void 0 ? "" : readOptional2(appendPath);
|
|
26451
26872
|
if (options.append !== void 0 && hasSnippet(existing)) {
|
|
26452
26873
|
throw new UsageError(
|
|
26453
|
-
`${options.append} already has a "${SNIPPET_HEADING}" section:
|
|
26874
|
+
`${options.append} already has a "${SNIPPET_HEADING}" section: run quibble sync to refresh it`
|
|
26454
26875
|
);
|
|
26455
26876
|
}
|
|
26456
26877
|
if (skillPath !== void 0 && existsSync(skillPath) && options.force !== true) {
|
|
26457
26878
|
throw existsError(skillPath);
|
|
26458
26879
|
}
|
|
26459
|
-
|
|
26460
|
-
|
|
26461
|
-
tags = (await openOrgClient(context, io).project(project)).tags;
|
|
26462
|
-
} else {
|
|
26463
|
-
checkActAs(context);
|
|
26464
|
-
tags = context.allowedTags;
|
|
26465
|
-
}
|
|
26466
|
-
const scope = {
|
|
26467
|
-
...context.mode === "remote" ? { endpoint: context.endpoint } : {},
|
|
26468
|
-
...context.slug === void 0 ? {} : { org: context.slug }
|
|
26469
|
-
};
|
|
26880
|
+
const hook = options.hook === true ? planHook(context.projectRoot ?? io.cwd) : void 0;
|
|
26881
|
+
const { tags, scope } = await snippetSource(project, context, io);
|
|
26470
26882
|
let shown = { agent: override ?? DEFAULT_SNIPPET_AGENT, snippet: "" };
|
|
26471
26883
|
shown.snippet = agentSnippet(tags, { ...scope, agent: shown.agent });
|
|
26472
26884
|
if (appendPath !== void 0) {
|
|
26473
26885
|
const agent = override ?? agentForFile(appendPath);
|
|
26474
26886
|
const snippet = agentSnippet(tags, { ...scope, agent });
|
|
26475
26887
|
const kept = existing.replace(/\s+$/, "");
|
|
26476
|
-
|
|
26888
|
+
writeFileSync4(appendPath, kept === "" ? `${snippet}
|
|
26477
26889
|
` : `${kept}
|
|
26478
26890
|
|
|
26479
26891
|
${snippet}
|
|
@@ -26486,23 +26898,25 @@ ${snippet}
|
|
|
26486
26898
|
writeSkill(skillPath, snippet);
|
|
26487
26899
|
if (appendPath === void 0) shown = { agent, snippet };
|
|
26488
26900
|
}
|
|
26901
|
+
const hookLine = hook === void 0 ? void 0 : applyHook(hook);
|
|
26489
26902
|
if (options.json === true) {
|
|
26490
26903
|
writeJson(io.stdout, {
|
|
26491
26904
|
snippet: shown.snippet,
|
|
26492
26905
|
agent: shown.agent,
|
|
26493
26906
|
appended: appendPath ?? null,
|
|
26494
|
-
skill: skillPath ?? null
|
|
26907
|
+
skill: skillPath ?? null,
|
|
26908
|
+
hook: hook?.path ?? null
|
|
26495
26909
|
});
|
|
26496
26910
|
return 0;
|
|
26497
26911
|
}
|
|
26498
26912
|
if (appendPath === void 0 && skillPath === void 0) {
|
|
26499
26913
|
writeLine(io.stdout, shown.snippet);
|
|
26500
|
-
return 0;
|
|
26501
26914
|
}
|
|
26502
26915
|
if (appendPath !== void 0) {
|
|
26503
26916
|
writeLine(io.stdout, `Appended the Quibble snippet to ${appendPath}`);
|
|
26504
26917
|
}
|
|
26505
26918
|
if (skillPath !== void 0) writeLine(io.stdout, `Wrote ${skillPath}`);
|
|
26919
|
+
if (hookLine !== void 0) writeLine(io.stdout, hookLine);
|
|
26506
26920
|
return 0;
|
|
26507
26921
|
}
|
|
26508
26922
|
|
|
@@ -26513,34 +26927,47 @@ function agentLead(agent) {
|
|
|
26513
26927
|
}
|
|
26514
26928
|
async function runInit(options, io) {
|
|
26515
26929
|
const directory = resolve3(io.cwd);
|
|
26516
|
-
const path =
|
|
26930
|
+
const path = join7(directory, PROJECT_CONFIG_FILENAME);
|
|
26517
26931
|
if (existsSync2(path) && options.force !== true) {
|
|
26518
26932
|
throw new ConfigError(`${path} already exists: pass --force to overwrite it`, path);
|
|
26519
26933
|
}
|
|
26520
|
-
const skillPath = options.skill === true ?
|
|
26934
|
+
const skillPath = options.skill === true ? join7(directory, SKILL_PATH) : void 0;
|
|
26521
26935
|
if (skillPath !== void 0 && existsSync2(skillPath) && options.force !== true) {
|
|
26522
26936
|
throw existsError(skillPath);
|
|
26523
26937
|
}
|
|
26524
26938
|
const project = resolveProjectName(options.project, directory);
|
|
26525
26939
|
const org = options.org === void 0 || options.org.trim() === "" ? void 0 : parseOrgSlug(options.org);
|
|
26526
|
-
const endpoint = options.endpoint?.trim();
|
|
26527
26940
|
const tags = options.tag === void 0 ? void 0 : parseAllowedTags(options.tag);
|
|
26528
26941
|
const agent = options.agent === void 0 ? DEFAULT_SNIPPET_AGENT : parseAgentName(options.agent);
|
|
26942
|
+
const hook = options.hook === true ? planHook(directory) : void 0;
|
|
26943
|
+
const endpoint = await endpointToWrite(options.endpoint, io);
|
|
26529
26944
|
const config2 = {
|
|
26530
26945
|
project,
|
|
26531
26946
|
...org === void 0 ? {} : { org },
|
|
26532
|
-
...endpoint === void 0
|
|
26947
|
+
...endpoint === void 0 ? {} : { endpoint },
|
|
26533
26948
|
...tags === void 0 ? {} : { tags }
|
|
26534
26949
|
};
|
|
26535
|
-
|
|
26950
|
+
writeFileSync5(path, `${JSON.stringify(config2, null, 2)}
|
|
26536
26951
|
`, "utf8");
|
|
26952
|
+
const context = {
|
|
26953
|
+
...resolveContext({
|
|
26954
|
+
cwd: io.cwd,
|
|
26955
|
+
env: io.env,
|
|
26956
|
+
endpoint: options.endpoint,
|
|
26957
|
+
org: options.org,
|
|
26958
|
+
local: options.local
|
|
26959
|
+
}),
|
|
26960
|
+
// `init` acts as nobody, so it ignores `QUIBBLE_ACT_AS`.
|
|
26961
|
+
actAs: void 0
|
|
26962
|
+
};
|
|
26537
26963
|
const snippet = agentSnippet(tags, {
|
|
26538
26964
|
agent,
|
|
26539
|
-
...
|
|
26965
|
+
...context.mode === "remote" ? { endpoint: context.endpoint } : {},
|
|
26540
26966
|
...org === void 0 ? {} : { org }
|
|
26541
26967
|
});
|
|
26542
26968
|
if (skillPath !== void 0) writeSkill(skillPath, snippet);
|
|
26543
|
-
const
|
|
26969
|
+
const hookLine = hook === void 0 ? void 0 : applyHook(hook);
|
|
26970
|
+
const check2 = await checkServer(context, project, io);
|
|
26544
26971
|
if (options.json === true) {
|
|
26545
26972
|
writeJson(io.stdout, {
|
|
26546
26973
|
path,
|
|
@@ -26548,12 +26975,14 @@ async function runInit(options, io) {
|
|
|
26548
26975
|
org: org ?? null,
|
|
26549
26976
|
tags: tags ?? null,
|
|
26550
26977
|
skill: skillPath ?? null,
|
|
26978
|
+
hook: hook?.path ?? null,
|
|
26551
26979
|
server: check2.server
|
|
26552
26980
|
});
|
|
26553
26981
|
return 0;
|
|
26554
26982
|
}
|
|
26555
26983
|
writeLine(io.stdout, `Wrote ${path}`);
|
|
26556
26984
|
if (skillPath !== void 0) writeLine(io.stdout, `Wrote ${skillPath}`);
|
|
26985
|
+
if (hookLine !== void 0) writeLine(io.stdout, hookLine);
|
|
26557
26986
|
writeLine(io.stdout, `Project: ${project}`);
|
|
26558
26987
|
if (org !== void 0) writeLine(io.stdout, `Organization: ${org}`);
|
|
26559
26988
|
if (tags !== void 0) writeLine(io.stdout, `Tags: ${tags.join(", ")}`);
|
|
@@ -26571,26 +27000,28 @@ async function runInit(options, io) {
|
|
|
26571
27000
|
}
|
|
26572
27001
|
return 0;
|
|
26573
27002
|
}
|
|
26574
|
-
async function
|
|
26575
|
-
|
|
26576
|
-
|
|
26577
|
-
|
|
26578
|
-
|
|
26579
|
-
|
|
26580
|
-
|
|
26581
|
-
|
|
26582
|
-
|
|
26583
|
-
|
|
26584
|
-
|
|
26585
|
-
const
|
|
26586
|
-
if (context.mode !== "remote" ||
|
|
27003
|
+
async function endpointToWrite(given, io) {
|
|
27004
|
+
if (given === void 0 || given.trim() === "") return void 0;
|
|
27005
|
+
const value = parseEndpoint(given, "--endpoint");
|
|
27006
|
+
if (value.kind === "local") return LOCAL_ENDPOINT;
|
|
27007
|
+
const api = await discoverApiUrl(value.url, {
|
|
27008
|
+
fetch: io.fetch,
|
|
27009
|
+
debug: debugEnabled(io.env)
|
|
27010
|
+
});
|
|
27011
|
+
return isDefaultEndpoint(api) ? void 0 : api;
|
|
27012
|
+
}
|
|
27013
|
+
async function checkServer(context, project, io) {
|
|
27014
|
+
const org = context.org;
|
|
27015
|
+
if (context.mode !== "remote" || org === void 0) return { server: "unchecked" };
|
|
27016
|
+
const endpoint = await resolveEndpoint(context, io);
|
|
27017
|
+
if (context.token === void 0 && context.credential === void 0 && !isLoopbackUrl(endpoint)) {
|
|
26587
27018
|
return { server: "unchecked" };
|
|
26588
27019
|
}
|
|
26589
27020
|
const warn = (text) => {
|
|
26590
27021
|
writeLine(io.stderr, `quibble: warning: ${text}`);
|
|
26591
27022
|
};
|
|
26592
27023
|
try {
|
|
26593
|
-
await openClient(context, io, { queue: false }).project(project);
|
|
27024
|
+
await (await openClient(context, io, { queue: false })).project(project);
|
|
26594
27025
|
return { server: "found", line: `Project ${org}/${project} exists on ${endpoint}.` };
|
|
26595
27026
|
} catch (error62) {
|
|
26596
27027
|
if (error62 instanceof RemoteError && error62.status === 404) {
|
|
@@ -26742,12 +27173,12 @@ var REPORTER_WIDTH = 20;
|
|
|
26742
27173
|
var TAGS_WIDTH = 24;
|
|
26743
27174
|
var SUMMARY_MAX = 60;
|
|
26744
27175
|
var RECURRED_MARK = "\u21BB";
|
|
26745
|
-
function
|
|
27176
|
+
function cut3(text, width) {
|
|
26746
27177
|
if (text.length <= width) return text;
|
|
26747
27178
|
return `${text.slice(0, width - 3)}...`;
|
|
26748
27179
|
}
|
|
26749
27180
|
function summaryOf(complaint) {
|
|
26750
|
-
return
|
|
27181
|
+
return cut3(printable(summarize(complaint.message)), SUMMARY_MAX);
|
|
26751
27182
|
}
|
|
26752
27183
|
function limitNote(count, limit, more) {
|
|
26753
27184
|
if (limit >= MAX_LIMIT) {
|
|
@@ -26760,7 +27191,7 @@ async function runList(options, context, io) {
|
|
|
26760
27191
|
if (options.ids === true && options.json === true) {
|
|
26761
27192
|
throw new UsageError("--ids and --json cannot be used together");
|
|
26762
27193
|
}
|
|
26763
|
-
const { store, client } = openBackend(context, io);
|
|
27194
|
+
const { store, client } = await openBackend(context, io);
|
|
26764
27195
|
const reporter = options.mine === true ? await resolveMine(context, client, io.env) : void 0;
|
|
26765
27196
|
const filter = parseListFilter({
|
|
26766
27197
|
...resolveScope(options, context),
|
|
@@ -26801,7 +27232,7 @@ async function runList(options, context, io) {
|
|
|
26801
27232
|
return 0;
|
|
26802
27233
|
}
|
|
26803
27234
|
function reporterOf(complaint) {
|
|
26804
|
-
return
|
|
27235
|
+
return cut3(printable(complaint.reporterName ?? complaint.reporter), REPORTER_WIDTH);
|
|
26805
27236
|
}
|
|
26806
27237
|
function groupCell(group) {
|
|
26807
27238
|
const count = group.count > 1 ? `\xD7${group.count}` : "";
|
|
@@ -26818,7 +27249,7 @@ function formatTable(rows2) {
|
|
|
26818
27249
|
status: complaint.status,
|
|
26819
27250
|
project: complaint.project,
|
|
26820
27251
|
reporter: reporterOf(complaint),
|
|
26821
|
-
tags:
|
|
27252
|
+
tags: cut3(printable(complaint.tags.join(",")), TAGS_WIDTH),
|
|
26822
27253
|
group: group === void 0 ? "" : groupCell(group),
|
|
26823
27254
|
summary: summaryOf(complaint)
|
|
26824
27255
|
};
|
|
@@ -26835,13 +27266,13 @@ function formatTable(rows2) {
|
|
|
26835
27266
|
}
|
|
26836
27267
|
|
|
26837
27268
|
// src/commands/orgs.ts
|
|
26838
|
-
var ORGS_NEEDS_SERVER = "orgs needs a server: pass --endpoint <url>, set QUIBBLE_ENDPOINT, or add one to .quibblerc";
|
|
26839
27269
|
var ORGS_PAGE = "/orgs";
|
|
26840
27270
|
var COLUMNS2 = ["SLUG", "NAME", "ROLE", "OPEN"];
|
|
26841
27271
|
async function runOrgs(options, context, io) {
|
|
26842
|
-
|
|
27272
|
+
requireServer("orgs", context);
|
|
27273
|
+
const client = await openClient(context, io);
|
|
26843
27274
|
const endpoint = requireEndpoint(context);
|
|
26844
|
-
const answer2 = await
|
|
27275
|
+
const answer2 = await client.orgs();
|
|
26845
27276
|
if (options.json === true) {
|
|
26846
27277
|
writeJson(io.stdout, answer2.body);
|
|
26847
27278
|
return 0;
|
|
@@ -26849,9 +27280,10 @@ async function runOrgs(options, context, io) {
|
|
|
26849
27280
|
if (answer2.orgs.length === 0) {
|
|
26850
27281
|
const config2 = await fetchAuthConfig(endpoint, {
|
|
26851
27282
|
debug: context.debug,
|
|
26852
|
-
fetch: io.fetch
|
|
27283
|
+
fetch: io.fetch,
|
|
27284
|
+
timeoutMs: context.timeoutMs
|
|
26853
27285
|
});
|
|
26854
|
-
const webUrl = config2.mode === "cognito" ?
|
|
27286
|
+
const webUrl = config2.mode === "cognito" ? trimSlash2(config2.webUrl) : endpoint;
|
|
26855
27287
|
writeLine(io.stdout, `No organizations yet: create one at ${webUrl}${ORGS_PAGE}.`);
|
|
26856
27288
|
return 0;
|
|
26857
27289
|
}
|
|
@@ -26874,6 +27306,81 @@ function formatOrgs(orgs) {
|
|
|
26874
27306
|
return [format(COLUMNS2), ...rows2.map(format)];
|
|
26875
27307
|
}
|
|
26876
27308
|
|
|
27309
|
+
// src/commands/login.ts
|
|
27310
|
+
import { hostname as hostname4 } from "node:os";
|
|
27311
|
+
|
|
27312
|
+
// src/opener.ts
|
|
27313
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
27314
|
+
var OPEN_TIMEOUT_MS = 5e3;
|
|
27315
|
+
var OPENABLE_URL_RE = /^https?:\/\/[A-Za-z0-9.:[\]-]+(?:\/[A-Za-z0-9/?=._~-]*)?$/;
|
|
27316
|
+
function isWsl(platform, env) {
|
|
27317
|
+
if (platform !== "linux") return false;
|
|
27318
|
+
const set2 = (name) => (env[name] ?? "") !== "";
|
|
27319
|
+
return set2("WSL_DISTRO_NAME") || set2("WSL_INTEROP");
|
|
27320
|
+
}
|
|
27321
|
+
function openCommands(url2, platform, env) {
|
|
27322
|
+
if (platform === "darwin") return [{ command: "open", args: [url2] }];
|
|
27323
|
+
if (platform === "win32") {
|
|
27324
|
+
return [{ command: "cmd", args: ["/c", "start", "", url2] }];
|
|
27325
|
+
}
|
|
27326
|
+
if (isWsl(platform, env)) {
|
|
27327
|
+
return [
|
|
27328
|
+
{ command: "wslview", args: [url2] },
|
|
27329
|
+
{ command: "cmd.exe", args: ["/c", "start", "", url2] }
|
|
27330
|
+
];
|
|
27331
|
+
}
|
|
27332
|
+
return [{ command: "xdg-open", args: [url2] }];
|
|
27333
|
+
}
|
|
27334
|
+
function attempt(spawn, entry, timeoutMs) {
|
|
27335
|
+
return new Promise((resolve4) => {
|
|
27336
|
+
let settled = false;
|
|
27337
|
+
const settle2 = (opened) => {
|
|
27338
|
+
if (settled) return;
|
|
27339
|
+
settled = true;
|
|
27340
|
+
clearTimeout(timer);
|
|
27341
|
+
resolve4(opened);
|
|
27342
|
+
};
|
|
27343
|
+
const timer = setTimeout(() => {
|
|
27344
|
+
settle2(false);
|
|
27345
|
+
}, timeoutMs);
|
|
27346
|
+
timer.unref();
|
|
27347
|
+
let child;
|
|
27348
|
+
try {
|
|
27349
|
+
child = spawn(entry.command, entry.args, {
|
|
27350
|
+
shell: false,
|
|
27351
|
+
detached: true,
|
|
27352
|
+
stdio: "ignore",
|
|
27353
|
+
windowsHide: true
|
|
27354
|
+
});
|
|
27355
|
+
} catch {
|
|
27356
|
+
settle2(false);
|
|
27357
|
+
return;
|
|
27358
|
+
}
|
|
27359
|
+
child.on("error", () => {
|
|
27360
|
+
settle2(false);
|
|
27361
|
+
});
|
|
27362
|
+
child.on("exit", (code2) => {
|
|
27363
|
+
settle2(code2 === 0);
|
|
27364
|
+
});
|
|
27365
|
+
child.unref();
|
|
27366
|
+
});
|
|
27367
|
+
}
|
|
27368
|
+
function platformOpener(options = {}) {
|
|
27369
|
+
const platform = options.platform ?? process.platform;
|
|
27370
|
+
const env = options.env ?? process.env;
|
|
27371
|
+
const spawn = options.spawn ?? nodeSpawn;
|
|
27372
|
+
const timeoutMs = options.timeoutMs ?? OPEN_TIMEOUT_MS;
|
|
27373
|
+
return {
|
|
27374
|
+
async open(url2) {
|
|
27375
|
+
if (!OPENABLE_URL_RE.test(url2)) return false;
|
|
27376
|
+
for (const entry of openCommands(url2, platform, env)) {
|
|
27377
|
+
if (await attempt(spawn, entry, timeoutMs)) return true;
|
|
27378
|
+
}
|
|
27379
|
+
return false;
|
|
27380
|
+
}
|
|
27381
|
+
};
|
|
27382
|
+
}
|
|
27383
|
+
|
|
26877
27384
|
// src/terminal.ts
|
|
26878
27385
|
import { createInterface } from "node:readline";
|
|
26879
27386
|
import { Writable } from "node:stream";
|
|
@@ -26911,19 +27418,19 @@ var NodeTerminal = class {
|
|
|
26911
27418
|
#interface() {
|
|
26912
27419
|
if (this.#rl !== void 0) return this.#rl;
|
|
26913
27420
|
const output2 = this.#output;
|
|
26914
|
-
const
|
|
27421
|
+
const sink2 = new Writable({
|
|
26915
27422
|
write: (chunk, _encoding, done) => {
|
|
26916
27423
|
if (!this.#muted) output2.write(chunk);
|
|
26917
27424
|
done();
|
|
26918
27425
|
}
|
|
26919
27426
|
});
|
|
26920
|
-
Object.defineProperties(
|
|
27427
|
+
Object.defineProperties(sink2, {
|
|
26921
27428
|
columns: { get: () => output2.columns },
|
|
26922
27429
|
rows: { get: () => output2.rows }
|
|
26923
27430
|
});
|
|
26924
27431
|
const rl = createInterface({
|
|
26925
27432
|
input: this.#input,
|
|
26926
|
-
output:
|
|
27433
|
+
output: sink2,
|
|
26927
27434
|
terminal: true,
|
|
26928
27435
|
// No answer enters the history, so an arrow key recalls no password.
|
|
26929
27436
|
historySize: 0
|
|
@@ -26995,15 +27502,40 @@ function nodeTerminal(input2, output2) {
|
|
|
26995
27502
|
|
|
26996
27503
|
// src/commands/login.ts
|
|
26997
27504
|
var LOCAL_IDENTITY_MESSAGE = "local mode: no identity.";
|
|
26998
|
-
var NEEDS_TERMINAL_MESSAGE = "quibble login needs a terminal: in CI, set QUIBBLE_TOKEN instead";
|
|
27505
|
+
var NEEDS_TERMINAL_MESSAGE = "quibble login --password needs a terminal: in CI, set QUIBBLE_TOKEN instead";
|
|
27506
|
+
var CI_MESSAGE = "quibble login needs a person to approve it in a browser: in CI, set QUIBBLE_TOKEN instead";
|
|
27507
|
+
var NO_BROWSER_MESSAGE = "--no-browser applies to the browser login only";
|
|
27508
|
+
var DENIED_MESSAGE = "login was denied in the browser";
|
|
27509
|
+
var EXPIRED_MESSAGE = "the login request expired: run quibble login again";
|
|
27510
|
+
var MALFORMED_MESSAGE = "the API answered a malformed login request";
|
|
27511
|
+
var UNKNOWN_STATUS_MESSAGE = "the API answered an unknown login status";
|
|
27512
|
+
var MFA_NEEDS_TERMINAL_MESSAGE = "this account needs a two-factor code: run quibble login in a terminal";
|
|
26999
27513
|
var MFA_CHALLENGE = "SOFTWARE_TOKEN_MFA";
|
|
27514
|
+
var CUSTOM_CHALLENGE = "CUSTOM_CHALLENGE";
|
|
27000
27515
|
var NEW_PASSWORD_CHALLENGE = "NEW_PASSWORD_REQUIRED";
|
|
27516
|
+
var CLI_LOGINS_PATH = "/v1/cli-logins";
|
|
27517
|
+
var CLI_LOGIN_POLL_PATH = "/v1/cli-logins/poll";
|
|
27518
|
+
var MIN_INTERVAL_SECONDS = 1;
|
|
27519
|
+
var MAX_INTERVAL_SECONDS = 30;
|
|
27520
|
+
var PLAIN_HOST_RE = /^(?:[a-z0-9.-]+|\[[0-9a-f:.]+\])(?::[0-9]+)?$/;
|
|
27001
27521
|
var LOGIN_PAGE = "/login";
|
|
27002
27522
|
var ORGS_PAGE2 = "/orgs";
|
|
27003
27523
|
var NO_PROFILE_STATUSES = /* @__PURE__ */ new Set([404, 409]);
|
|
27004
27524
|
function terminalOf(io) {
|
|
27005
27525
|
return io.terminal ?? nodeTerminal(io.stdin, io.stderr);
|
|
27006
27526
|
}
|
|
27527
|
+
function clockOf2(io) {
|
|
27528
|
+
return io.now ?? (() => /* @__PURE__ */ new Date());
|
|
27529
|
+
}
|
|
27530
|
+
function sleepOf(io) {
|
|
27531
|
+
return io.sleep ?? ((ms) => new Promise((resolve4) => {
|
|
27532
|
+
setTimeout(resolve4, ms);
|
|
27533
|
+
}));
|
|
27534
|
+
}
|
|
27535
|
+
function ciIsSet(env) {
|
|
27536
|
+
const value = (env["CI"] ?? "").trim().toLowerCase();
|
|
27537
|
+
return value !== "" && value !== "0" && value !== "false";
|
|
27538
|
+
}
|
|
27007
27539
|
async function answer(terminal, label, hidden = false) {
|
|
27008
27540
|
const text = hidden ? await terminal.promptHidden(label) : await terminal.prompt(label);
|
|
27009
27541
|
const value = hidden ? text : text.trim();
|
|
@@ -27011,6 +27543,23 @@ async function answer(terminal, label, hidden = false) {
|
|
|
27011
27543
|
throw new UsageError(`${label.replace(/:$/, "").toLowerCase()} must not be empty`);
|
|
27012
27544
|
return value;
|
|
27013
27545
|
}
|
|
27546
|
+
function unsupportedStep(name) {
|
|
27547
|
+
return new RemoteError(
|
|
27548
|
+
`this account needs a sign-in step that quibble doesn't support (${name})`
|
|
27549
|
+
);
|
|
27550
|
+
}
|
|
27551
|
+
function pageError(error62, webUrl) {
|
|
27552
|
+
if (error62 instanceof CognitoError) {
|
|
27553
|
+
const page = `${trimSlash2(webUrl)}${LOGIN_PAGE}`;
|
|
27554
|
+
if (error62.type === "PasswordResetRequiredException") {
|
|
27555
|
+
return new ConfigError(`reset your password at ${page}`);
|
|
27556
|
+
}
|
|
27557
|
+
if (error62.type === "UserNotConfirmedException") {
|
|
27558
|
+
return new ConfigError(`finish your sign-up at ${page}`);
|
|
27559
|
+
}
|
|
27560
|
+
}
|
|
27561
|
+
return error62;
|
|
27562
|
+
}
|
|
27014
27563
|
async function signIn(terminal, identity, config2) {
|
|
27015
27564
|
const email3 = await answer(terminal, "Email:");
|
|
27016
27565
|
const password = await answer(terminal, "Password:", true);
|
|
@@ -27023,43 +27572,40 @@ async function signIn(terminal, identity, config2) {
|
|
|
27023
27572
|
password
|
|
27024
27573
|
});
|
|
27025
27574
|
if (result.kind === "challenge" && result.challenge.challengeName === MFA_CHALLENGE) {
|
|
27026
|
-
const
|
|
27575
|
+
const code2 = await answer(terminal, "Code:");
|
|
27027
27576
|
result = await respondToAuthChallenge(identity, {
|
|
27028
27577
|
clientId,
|
|
27029
27578
|
challenge: result.challenge,
|
|
27030
27579
|
email: email3,
|
|
27031
|
-
code
|
|
27580
|
+
code: code2
|
|
27032
27581
|
});
|
|
27033
27582
|
}
|
|
27034
27583
|
if (result.kind === "challenge") {
|
|
27035
27584
|
const name = result.challenge.challengeName;
|
|
27036
27585
|
if (name === NEW_PASSWORD_CHALLENGE) {
|
|
27037
27586
|
throw new ConfigError(
|
|
27038
|
-
`finish your first sign-in at ${
|
|
27587
|
+
`finish your first sign-in at ${trimSlash2(config2.webUrl)}${LOGIN_PAGE}`
|
|
27039
27588
|
);
|
|
27040
27589
|
}
|
|
27041
|
-
throw
|
|
27042
|
-
`this account needs a sign-in step that quibble doesn't support (${name})`
|
|
27043
|
-
);
|
|
27590
|
+
throw unsupportedStep(name);
|
|
27044
27591
|
}
|
|
27045
27592
|
return result.tokens;
|
|
27046
27593
|
} catch (error62) {
|
|
27047
|
-
|
|
27048
|
-
const page = `${trimSlash(config2.webUrl)}${LOGIN_PAGE}`;
|
|
27049
|
-
if (error62.type === "PasswordResetRequiredException") {
|
|
27050
|
-
throw new ConfigError(`reset your password at ${page}`);
|
|
27051
|
-
}
|
|
27052
|
-
if (error62.type === "UserNotConfirmedException") {
|
|
27053
|
-
throw new ConfigError(`finish your sign-up at ${page}`);
|
|
27054
|
-
}
|
|
27055
|
-
}
|
|
27056
|
-
throw error62;
|
|
27594
|
+
throw pageError(error62, config2.webUrl);
|
|
27057
27595
|
}
|
|
27058
27596
|
}
|
|
27059
27597
|
async function runLogin(options, context, io) {
|
|
27060
|
-
|
|
27598
|
+
requireServer("login", context);
|
|
27599
|
+
if (options.password === true) {
|
|
27600
|
+
if (options.browser === false) throw new UsageError(NO_BROWSER_MESSAGE);
|
|
27601
|
+
return passwordLogin(options, context, io);
|
|
27602
|
+
}
|
|
27603
|
+
return browserLogin(options, context, io);
|
|
27604
|
+
}
|
|
27605
|
+
async function passwordLogin(options, context, io) {
|
|
27061
27606
|
const terminal = terminalOf(io);
|
|
27062
27607
|
if (!terminal.isTTY) throw new UsageError(NEEDS_TERMINAL_MESSAGE);
|
|
27608
|
+
const endpoint = await resolveEndpoint(context, io);
|
|
27063
27609
|
const http = { debug: context.debug, fetch: io.fetch };
|
|
27064
27610
|
const config2 = await fetchAuthConfig(endpoint, http);
|
|
27065
27611
|
if (config2.mode === "local") {
|
|
@@ -27072,15 +27618,203 @@ async function runLogin(options, context, io) {
|
|
|
27072
27618
|
} finally {
|
|
27073
27619
|
terminal.close();
|
|
27074
27620
|
}
|
|
27621
|
+
return finishLogin(tokens, endpoint, config2, options, context, io);
|
|
27622
|
+
}
|
|
27623
|
+
async function browserLogin(options, context, io) {
|
|
27624
|
+
if (ciIsSet(io.env)) throw new UsageError(CI_MESSAGE);
|
|
27625
|
+
const endpoint = await resolveEndpoint(context, io);
|
|
27626
|
+
const http = { debug: context.debug, fetch: io.fetch };
|
|
27627
|
+
const config2 = await fetchAuthConfig(endpoint, http);
|
|
27628
|
+
if (config2.mode === "local") {
|
|
27629
|
+
writeLine(io.stdout, `${endpoint} is in local mode: no login needed.`);
|
|
27630
|
+
return 0;
|
|
27631
|
+
}
|
|
27632
|
+
const clock = clockOf2(io);
|
|
27633
|
+
const started = await startLogin(endpoint, config2.webUrl, http, {
|
|
27634
|
+
host: cliLoginHost((io.hostname ?? hostname4)()),
|
|
27635
|
+
os: process.platform,
|
|
27636
|
+
version: options.version
|
|
27637
|
+
});
|
|
27638
|
+
const deadline = clock().getTime() + (CLI_LOGIN_TTL_SECONDS + started.interval) * 1e3;
|
|
27639
|
+
writeLine(
|
|
27640
|
+
io.stderr,
|
|
27641
|
+
`Approve this login in your browser: ${started.url} (code ${started.code}). Waiting...`
|
|
27642
|
+
);
|
|
27643
|
+
if (options.browser !== false) {
|
|
27644
|
+
const opener = io.opener ?? platformOpener({ env: io.env });
|
|
27645
|
+
void opener.open(started.url).catch(() => false);
|
|
27646
|
+
}
|
|
27647
|
+
const approval = await waitForApproval(endpoint, started, deadline, http, io);
|
|
27648
|
+
const tokens = await browserSignIn(
|
|
27649
|
+
{ region: config2.region, ...http },
|
|
27650
|
+
config2,
|
|
27651
|
+
approval,
|
|
27652
|
+
io
|
|
27653
|
+
);
|
|
27654
|
+
return finishLogin(tokens, endpoint, config2, options, context, io);
|
|
27655
|
+
}
|
|
27656
|
+
function postJson(url2, body, http) {
|
|
27657
|
+
return send(url2, {
|
|
27658
|
+
method: "POST",
|
|
27659
|
+
headers: { "content-type": "application/json" },
|
|
27660
|
+
body: JSON.stringify(body),
|
|
27661
|
+
...http
|
|
27662
|
+
});
|
|
27663
|
+
}
|
|
27664
|
+
async function startLogin(endpoint, webUrl, http, body) {
|
|
27665
|
+
const url2 = `${trimSlash2(endpoint)}${CLI_LOGINS_PATH}`;
|
|
27666
|
+
const site = { method: "POST", url: url2, debug: http.debug };
|
|
27667
|
+
const response = await postJson(url2, body, http);
|
|
27668
|
+
const text = await readText(response, site);
|
|
27669
|
+
if (response.status === 404) {
|
|
27670
|
+
throw new RemoteError(
|
|
27671
|
+
`${endpoint} does not support the browser login: run quibble login --password${detail(site, 404)}`,
|
|
27672
|
+
{ status: 404 }
|
|
27673
|
+
);
|
|
27674
|
+
}
|
|
27675
|
+
if (!response.ok) throw remoteFailure(site, response, text);
|
|
27676
|
+
const started = checkStart(asRecord(parseQuietly(text)), webUrl);
|
|
27677
|
+
if (started === void 0) {
|
|
27678
|
+
throw new RemoteError(`${MALFORMED_MESSAGE}${detail(site, response.status)}`);
|
|
27679
|
+
}
|
|
27680
|
+
return started;
|
|
27681
|
+
}
|
|
27682
|
+
function checkStart(body, webUrl) {
|
|
27683
|
+
const loginKey = body?.["loginKey"];
|
|
27684
|
+
const code2 = body?.["code"];
|
|
27685
|
+
const url2 = body?.["url"];
|
|
27686
|
+
if (typeof loginKey !== "string" || !CLI_LOGIN_KEY_RE.test(loginKey)) return void 0;
|
|
27687
|
+
if (typeof code2 !== "string" || typeof url2 !== "string") return void 0;
|
|
27688
|
+
try {
|
|
27689
|
+
if (parseCliLoginCode(code2) !== code2) return void 0;
|
|
27690
|
+
} catch {
|
|
27691
|
+
return void 0;
|
|
27692
|
+
}
|
|
27693
|
+
let origin;
|
|
27694
|
+
try {
|
|
27695
|
+
const web = new URL(webUrl);
|
|
27696
|
+
const secure = web.protocol === "https:" || web.protocol === "http:" && isLoopbackUrl(web.origin);
|
|
27697
|
+
if (!secure || !PLAIN_HOST_RE.test(web.host)) return void 0;
|
|
27698
|
+
origin = web.origin;
|
|
27699
|
+
} catch {
|
|
27700
|
+
return void 0;
|
|
27701
|
+
}
|
|
27702
|
+
if (url2 !== cliLoginUrl(origin, code2)) return void 0;
|
|
27703
|
+
const raw = body?.["interval"];
|
|
27704
|
+
const interval = typeof raw === "number" && Number.isInteger(raw) ? Math.min(MAX_INTERVAL_SECONDS, Math.max(MIN_INTERVAL_SECONDS, raw)) : CLI_LOGIN_POLL_SECONDS;
|
|
27705
|
+
return { loginKey, code: code2, url: url2, interval };
|
|
27706
|
+
}
|
|
27707
|
+
async function waitForApproval(endpoint, started, deadline, http, io) {
|
|
27708
|
+
const clock = clockOf2(io);
|
|
27709
|
+
const sleep = sleepOf(io);
|
|
27710
|
+
for (; ; ) {
|
|
27711
|
+
await sleep(started.interval * 1e3);
|
|
27712
|
+
if (clock().getTime() > deadline) throw new ConfigError(EXPIRED_MESSAGE);
|
|
27713
|
+
const answer2 = await poll(endpoint, started.loginKey, http);
|
|
27714
|
+
if (answer2 === void 0) continue;
|
|
27715
|
+
const status = answer2["status"];
|
|
27716
|
+
switch (status) {
|
|
27717
|
+
case "pending":
|
|
27718
|
+
continue;
|
|
27719
|
+
case "denied":
|
|
27720
|
+
throw new ConfigError(DENIED_MESSAGE);
|
|
27721
|
+
case "expired":
|
|
27722
|
+
throw new ConfigError(EXPIRED_MESSAGE);
|
|
27723
|
+
case "approved": {
|
|
27724
|
+
const username = answer2["username"];
|
|
27725
|
+
const secret = answer2["secret"];
|
|
27726
|
+
if (typeof username !== "string" || username === "") {
|
|
27727
|
+
throw new RemoteError(MALFORMED_MESSAGE);
|
|
27728
|
+
}
|
|
27729
|
+
if (typeof secret !== "string" || secret === "") {
|
|
27730
|
+
throw new RemoteError(MALFORMED_MESSAGE);
|
|
27731
|
+
}
|
|
27732
|
+
return { username, secret };
|
|
27733
|
+
}
|
|
27734
|
+
default:
|
|
27735
|
+
throw new RemoteError(UNKNOWN_STATUS_MESSAGE);
|
|
27736
|
+
}
|
|
27737
|
+
}
|
|
27738
|
+
}
|
|
27739
|
+
async function poll(endpoint, loginKey, http) {
|
|
27740
|
+
const url2 = `${trimSlash2(endpoint)}${CLI_LOGIN_POLL_PATH}`;
|
|
27741
|
+
const site = { method: "POST", url: url2, debug: http.debug };
|
|
27742
|
+
let response;
|
|
27743
|
+
let text;
|
|
27744
|
+
try {
|
|
27745
|
+
response = await postJson(url2, { loginKey }, http);
|
|
27746
|
+
text = await readText(response, site);
|
|
27747
|
+
} catch (error62) {
|
|
27748
|
+
if (error62 instanceof RemoteError && error62.unanswered) return void 0;
|
|
27749
|
+
throw error62;
|
|
27750
|
+
}
|
|
27751
|
+
if (!response.ok) {
|
|
27752
|
+
if (response.status >= 500 || response.status === 429) return void 0;
|
|
27753
|
+
throw remoteFailure(site, response, text);
|
|
27754
|
+
}
|
|
27755
|
+
return asRecord(parseJson(text, site, response.status)) ?? {};
|
|
27756
|
+
}
|
|
27757
|
+
async function browserSignIn(identity, config2, approval, io) {
|
|
27758
|
+
const clientId = config2.cliClientId;
|
|
27759
|
+
try {
|
|
27760
|
+
let result = await initiateAuth(identity, {
|
|
27761
|
+
clientId,
|
|
27762
|
+
flow: "CUSTOM_AUTH",
|
|
27763
|
+
username: approval.username
|
|
27764
|
+
});
|
|
27765
|
+
if (result.kind === "challenge") {
|
|
27766
|
+
const name = result.challenge.challengeName;
|
|
27767
|
+
if (name !== CUSTOM_CHALLENGE) throw unsupportedStep(name);
|
|
27768
|
+
result = await respondToCustomChallenge(identity, {
|
|
27769
|
+
clientId,
|
|
27770
|
+
challenge: result.challenge,
|
|
27771
|
+
username: approval.username,
|
|
27772
|
+
answer: approval.secret
|
|
27773
|
+
});
|
|
27774
|
+
}
|
|
27775
|
+
if (result.kind === "challenge" && result.challenge.challengeName === MFA_CHALLENGE) {
|
|
27776
|
+
result = await answerCode(
|
|
27777
|
+
identity,
|
|
27778
|
+
clientId,
|
|
27779
|
+
result.challenge,
|
|
27780
|
+
approval.username,
|
|
27781
|
+
io
|
|
27782
|
+
);
|
|
27783
|
+
}
|
|
27784
|
+
if (result.kind === "challenge") {
|
|
27785
|
+
throw unsupportedStep(result.challenge.challengeName);
|
|
27786
|
+
}
|
|
27787
|
+
return result.tokens;
|
|
27788
|
+
} catch (error62) {
|
|
27789
|
+
throw pageError(error62, config2.webUrl);
|
|
27790
|
+
}
|
|
27791
|
+
}
|
|
27792
|
+
async function answerCode(identity, clientId, challenge, username, io) {
|
|
27793
|
+
const terminal = terminalOf(io);
|
|
27794
|
+
try {
|
|
27795
|
+
if (!terminal.isTTY) throw new ConfigError(MFA_NEEDS_TERMINAL_MESSAGE);
|
|
27796
|
+
const code2 = await answer(terminal, "Code:");
|
|
27797
|
+
return await respondToAuthChallenge(identity, {
|
|
27798
|
+
clientId,
|
|
27799
|
+
challenge,
|
|
27800
|
+
email: username,
|
|
27801
|
+
code: code2
|
|
27802
|
+
});
|
|
27803
|
+
} finally {
|
|
27804
|
+
terminal.close();
|
|
27805
|
+
}
|
|
27806
|
+
}
|
|
27807
|
+
async function finishLogin(tokens, endpoint, config2, options, context, io) {
|
|
27075
27808
|
if (tokens.refreshToken === void 0) {
|
|
27076
27809
|
throw new RemoteError("sign-in didn't return a refresh token");
|
|
27077
27810
|
}
|
|
27078
|
-
const now =
|
|
27811
|
+
const now = clockOf2(io)();
|
|
27079
27812
|
const me = await readProfile(
|
|
27080
27813
|
new ApiClient({
|
|
27081
27814
|
endpoint,
|
|
27082
27815
|
authorizer: bearerAuthorizer(tokens.accessToken),
|
|
27083
|
-
|
|
27816
|
+
debug: context.debug,
|
|
27817
|
+
fetch: io.fetch
|
|
27084
27818
|
}),
|
|
27085
27819
|
config2.webUrl
|
|
27086
27820
|
);
|
|
@@ -27097,25 +27831,36 @@ async function runLogin(options, context, io) {
|
|
|
27097
27831
|
sub: me.sub,
|
|
27098
27832
|
email: me.email
|
|
27099
27833
|
});
|
|
27834
|
+
const saved = saveEndpoint(context, endpoint, io.env);
|
|
27100
27835
|
if (options.json === true) {
|
|
27101
|
-
writeJson(io.stdout, { ...me.body, endpoint });
|
|
27836
|
+
writeJson(io.stdout, { ...me.body, endpoint, saved: saved ?? null });
|
|
27102
27837
|
return 0;
|
|
27103
27838
|
}
|
|
27104
27839
|
writeLine(io.stdout, `Logged in to ${endpoint} as ${me.email}.`);
|
|
27840
|
+
if (saved !== void 0) {
|
|
27841
|
+
writeLine(io.stdout, `Saved ${endpoint} as your endpoint in ${saved}.`);
|
|
27842
|
+
}
|
|
27105
27843
|
return 0;
|
|
27106
27844
|
}
|
|
27845
|
+
function saveEndpoint(context, endpoint, env) {
|
|
27846
|
+
if (context.endpointSource !== "--endpoint" || isDefaultEndpoint(endpoint)) {
|
|
27847
|
+
return void 0;
|
|
27848
|
+
}
|
|
27849
|
+
return rememberEndpoint(env, endpoint);
|
|
27850
|
+
}
|
|
27107
27851
|
async function readProfile(client, webUrl) {
|
|
27108
27852
|
try {
|
|
27109
27853
|
return await client.me();
|
|
27110
27854
|
} catch (error62) {
|
|
27111
27855
|
if (error62 instanceof RemoteError && NO_PROFILE_STATUSES.has(error62.status ?? 0)) {
|
|
27112
|
-
throw new ConfigError(`finish your account at ${
|
|
27856
|
+
throw new ConfigError(`finish your account at ${trimSlash2(webUrl)}${LOGIN_PAGE}`);
|
|
27113
27857
|
}
|
|
27114
27858
|
throw error62;
|
|
27115
27859
|
}
|
|
27116
27860
|
}
|
|
27117
27861
|
async function runLogout(context, io) {
|
|
27118
|
-
|
|
27862
|
+
requireServer("logout", context);
|
|
27863
|
+
const endpoint = await resolveEndpoint(context, io);
|
|
27119
27864
|
const credential = findCredential(io.env, endpoint);
|
|
27120
27865
|
if (credential === void 0) {
|
|
27121
27866
|
writeLine(io.stdout, `Not logged in to ${endpoint}.`);
|
|
@@ -27151,8 +27896,8 @@ async function runWhoami(options, context, io) {
|
|
|
27151
27896
|
writeLine(io.stdout, LOCAL_IDENTITY_MESSAGE);
|
|
27152
27897
|
return 0;
|
|
27153
27898
|
}
|
|
27899
|
+
const client = await openClient(context, io);
|
|
27154
27900
|
const endpoint = requireEndpoint(context);
|
|
27155
|
-
const client = openClient(context, io);
|
|
27156
27901
|
const me = await client.me();
|
|
27157
27902
|
const org = me.kind === "token" || client.org === void 0 ? void 0 : await client.organization();
|
|
27158
27903
|
if (options.json === true) {
|
|
@@ -27169,7 +27914,7 @@ async function runWhoami(options, context, io) {
|
|
|
27169
27914
|
debug: context.debug,
|
|
27170
27915
|
fetch: io.fetch
|
|
27171
27916
|
});
|
|
27172
|
-
const webUrl = config2.mode === "cognito" ?
|
|
27917
|
+
const webUrl = config2.mode === "cognito" ? trimSlash2(config2.webUrl) : endpoint;
|
|
27173
27918
|
lines.push("", `Create one at ${webUrl}${ORGS_PAGE2}.`);
|
|
27174
27919
|
}
|
|
27175
27920
|
for (const line of lines) writeLine(io.stdout, line);
|
|
@@ -27209,18 +27954,40 @@ function whoamiLines(endpoint, me, org) {
|
|
|
27209
27954
|
return lines;
|
|
27210
27955
|
}
|
|
27211
27956
|
|
|
27957
|
+
// src/commands/project.ts
|
|
27958
|
+
var DELETE_NEEDS_YES = "project delete needs --yes when stdin is not a terminal";
|
|
27959
|
+
var DELETE_MISMATCH = "the name did not match: nothing was deleted";
|
|
27960
|
+
async function confirm(org, project, io) {
|
|
27961
|
+
const terminal = io.terminal ?? nodeTerminal(io.stdin, io.stderr);
|
|
27962
|
+
if (!terminal.isTTY) throw new UsageError(DELETE_NEEDS_YES);
|
|
27963
|
+
let answer2;
|
|
27964
|
+
try {
|
|
27965
|
+
answer2 = await terminal.prompt(`Type the project name to delete ${org}/${project}:`);
|
|
27966
|
+
} finally {
|
|
27967
|
+
terminal.close();
|
|
27968
|
+
}
|
|
27969
|
+
if (foldProjectName(answer2.trim()) !== project) throw new UsageError(DELETE_MISMATCH);
|
|
27970
|
+
}
|
|
27971
|
+
async function runProjectDelete(name, options, context, io) {
|
|
27972
|
+
requireServer("project delete", context);
|
|
27973
|
+
const project = parseProjectName(name);
|
|
27974
|
+
const org = requireOrg(context);
|
|
27975
|
+
if (options.yes !== true) await confirm(org, project, io);
|
|
27976
|
+
await (await openOrgClient(context, io)).deleteProject(project);
|
|
27977
|
+
if (options.json === true) {
|
|
27978
|
+
writeJson(io.stdout, { org, project, deleted: true });
|
|
27979
|
+
return 0;
|
|
27980
|
+
}
|
|
27981
|
+
writeLine(io.stdout, `Deleted project ${org}/${project}.`);
|
|
27982
|
+
return 0;
|
|
27983
|
+
}
|
|
27984
|
+
|
|
27212
27985
|
// src/commands/promote.ts
|
|
27213
|
-
var PROMOTE_NEEDS_SERVER = "promote needs a server: pass --endpoint <url>, set QUIBBLE_ENDPOINT, or add one to .quibblerc";
|
|
27214
|
-
var PROMOTE_NEEDS_SERVER_LOCAL = "promote needs a server: drop --local";
|
|
27215
27986
|
async function runPromote(tag, options, context, io) {
|
|
27216
|
-
|
|
27217
|
-
throw new ConfigError(
|
|
27218
|
-
context.endpoint === void 0 ? PROMOTE_NEEDS_SERVER : PROMOTE_NEEDS_SERVER_LOCAL
|
|
27219
|
-
);
|
|
27220
|
-
}
|
|
27987
|
+
requireServer("promote", context);
|
|
27221
27988
|
const project = requireProject(context);
|
|
27222
27989
|
const promotion = parseTagPromotion({ tag });
|
|
27223
|
-
const result = await openOrgClient(context, io).promoteTag(project, promotion.tag);
|
|
27990
|
+
const result = await (await openOrgClient(context, io)).promoteTag(project, promotion.tag);
|
|
27224
27991
|
if (options.json === true) {
|
|
27225
27992
|
writeJson(io.stdout, {
|
|
27226
27993
|
project,
|
|
@@ -27245,7 +28012,7 @@ var NO_TAG_MESSAGE = "retag needs at least one --tag";
|
|
|
27245
28012
|
async function runRetag(value, options, context, io) {
|
|
27246
28013
|
const tags = options.tag ?? [];
|
|
27247
28014
|
if (tags.length === 0) throw new UsageError(NO_TAG_MESSAGE);
|
|
27248
|
-
const { store, client, local } = openBackend(context, io);
|
|
28015
|
+
const { store, client, local } = await openBackend(context, io);
|
|
27249
28016
|
const [id] = await resolveIds([value], { store, local, context });
|
|
27250
28017
|
if (id === void 0) throw new NotFoundError(`no complaint with id ${value}`);
|
|
27251
28018
|
const allowedTags = client === void 0 ? await localAllowedTags(store, id, context) : void 0;
|
|
@@ -27274,9 +28041,9 @@ async function localAllowedTags(store, id, context) {
|
|
|
27274
28041
|
// src/commands/retry.ts
|
|
27275
28042
|
async function runRetry(options, context, io) {
|
|
27276
28043
|
requireServer("retry", context);
|
|
28044
|
+
const client = await openClient(context, io, { queue: false });
|
|
27277
28045
|
const endpoint = requireEndpoint(context);
|
|
27278
28046
|
const path = queuePath(io.env);
|
|
27279
|
-
const client = openClient(context, io, { queue: false });
|
|
27280
28047
|
const result = await flushQueue({
|
|
27281
28048
|
path,
|
|
27282
28049
|
endpoint,
|
|
@@ -27348,7 +28115,7 @@ function showLines(complaint, groupCount, remote) {
|
|
|
27348
28115
|
return [...lines, "", printableLines(complaint.message)];
|
|
27349
28116
|
}
|
|
27350
28117
|
async function runShow(value, options, context, io) {
|
|
27351
|
-
const { store, local } = openBackend(context, io);
|
|
28118
|
+
const { store, local } = await openBackend(context, io);
|
|
27352
28119
|
const [id] = await resolveIds([value], { store, local, context });
|
|
27353
28120
|
const complaint = id === void 0 ? void 0 : await getComplaint(store, id);
|
|
27354
28121
|
if (id === void 0 || complaint === void 0) {
|
|
@@ -27403,7 +28170,7 @@ async function runStatusChange(status, values, options, context, io) {
|
|
|
27403
28170
|
};
|
|
27404
28171
|
parseStatusChange(draft);
|
|
27405
28172
|
const given = await readIdArguments(values, io);
|
|
27406
|
-
const backend = openBackend(context, io);
|
|
28173
|
+
const backend = await openBackend(context, io);
|
|
27407
28174
|
const extra = options.duplicateOf === void 0 ? [] : [options.duplicateOf];
|
|
27408
28175
|
const resolved = await resolveIds([...given, ...extra], {
|
|
27409
28176
|
store: backend.store,
|
|
@@ -27442,10 +28209,10 @@ async function runStatusChange(status, values, options, context, io) {
|
|
|
27442
28209
|
writeLine(io.stdout, successLine(status, result.complaint));
|
|
27443
28210
|
continue;
|
|
27444
28211
|
}
|
|
27445
|
-
const
|
|
27446
|
-
const message =
|
|
28212
|
+
const code2 = result.error?.code ?? NOT_FOUND;
|
|
28213
|
+
const message = code2 === NOT_FOUND ? noComplaint(result.id) : result.error?.message;
|
|
27447
28214
|
writeLine(io.stderr, `quibble: ${result.id}: ${message ?? "the change failed"}`);
|
|
27448
|
-
failures.push({ remote: backend.store instanceof RemoteStore &&
|
|
28215
|
+
failures.push({ remote: backend.store instanceof RemoteStore && code2 !== NOT_FOUND });
|
|
27449
28216
|
}
|
|
27450
28217
|
if (options.json === true) writeJson(io.stdout, results);
|
|
27451
28218
|
if (error62 !== void 0) throw error62;
|
|
@@ -27499,7 +28266,7 @@ async function runSummary(options, context, io) {
|
|
|
27499
28266
|
throw new UsageError("--json and --format cannot be used together");
|
|
27500
28267
|
}
|
|
27501
28268
|
const format = readFormat(options.format);
|
|
27502
|
-
const { store, client } = openBackend(context, io);
|
|
28269
|
+
const { store, client } = await openBackend(context, io);
|
|
27503
28270
|
const reporter = options.mine === true ? await resolveMine(context, client, io.env) : void 0;
|
|
27504
28271
|
const filter = parseListFilter({
|
|
27505
28272
|
...resolveScope(options, context),
|
|
@@ -27609,11 +28376,127 @@ function markdownLines(filter, summary, note) {
|
|
|
27609
28376
|
return lines;
|
|
27610
28377
|
}
|
|
27611
28378
|
|
|
28379
|
+
// src/commands/sync.ts
|
|
28380
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "node:fs";
|
|
28381
|
+
import { join as join8 } from "node:path";
|
|
28382
|
+
import { Writable as Writable2 } from "node:stream";
|
|
28383
|
+
var SYNC_TIMEOUT_MS = 3e3;
|
|
28384
|
+
var SECTION_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
28385
|
+
function readOptional3(path) {
|
|
28386
|
+
try {
|
|
28387
|
+
return readFileSync6(path, "utf8");
|
|
28388
|
+
} catch (error62) {
|
|
28389
|
+
if (isNotFound(error62)) return void 0;
|
|
28390
|
+
throw error62;
|
|
28391
|
+
}
|
|
28392
|
+
}
|
|
28393
|
+
function holders(root) {
|
|
28394
|
+
const found = [];
|
|
28395
|
+
for (const name of SECTION_FILES) {
|
|
28396
|
+
const path = join8(root, name);
|
|
28397
|
+
const text = readOptional3(path);
|
|
28398
|
+
if (text !== void 0 && findSection(text) !== void 0) {
|
|
28399
|
+
found.push({ path, text, skill: false });
|
|
28400
|
+
}
|
|
28401
|
+
}
|
|
28402
|
+
const skill = join8(root, SKILL_PATH);
|
|
28403
|
+
if (existsSync3(skill)) {
|
|
28404
|
+
found.push({ path: skill, text: readOptional3(skill) ?? "", skill: true });
|
|
28405
|
+
}
|
|
28406
|
+
return found;
|
|
28407
|
+
}
|
|
28408
|
+
function agentOf(lines, path) {
|
|
28409
|
+
const named = agentOfText(lines);
|
|
28410
|
+
if (named !== void 0) {
|
|
28411
|
+
try {
|
|
28412
|
+
return parseAgentName(named);
|
|
28413
|
+
} catch {
|
|
28414
|
+
}
|
|
28415
|
+
}
|
|
28416
|
+
return agentForFile(path);
|
|
28417
|
+
}
|
|
28418
|
+
function rewrite(holder, tags, scope) {
|
|
28419
|
+
if (holder.skill) {
|
|
28420
|
+
const agent2 = agentOf(holder.text.split(/\r?\n/), holder.path);
|
|
28421
|
+
return withLineBreak(skillFile(agentSnippet(tags, { ...scope, agent: agent2 })), holder.text);
|
|
28422
|
+
}
|
|
28423
|
+
const section = findSection(holder.text);
|
|
28424
|
+
if (section === void 0) return holder.text;
|
|
28425
|
+
const agent = agentOf(sectionText(section), holder.path);
|
|
28426
|
+
return replaceSection(section, agentSnippet(tags, { ...scope, agent }));
|
|
28427
|
+
}
|
|
28428
|
+
function sink() {
|
|
28429
|
+
return new Writable2({
|
|
28430
|
+
write(_chunk, _encoding, done) {
|
|
28431
|
+
done();
|
|
28432
|
+
}
|
|
28433
|
+
});
|
|
28434
|
+
}
|
|
28435
|
+
async function runSync(options, contextOf, io) {
|
|
28436
|
+
if (options.quiet !== true) return sync(options, contextOf(), io);
|
|
28437
|
+
const silent = { ...io, stdout: sink(), stderr: sink() };
|
|
28438
|
+
try {
|
|
28439
|
+
const context = contextOf();
|
|
28440
|
+
context.timeoutMs = SYNC_TIMEOUT_MS;
|
|
28441
|
+
const result = await syncFiles({ hook: options.hook }, context, silent);
|
|
28442
|
+
if (result.files.some((file2) => file2.updated)) {
|
|
28443
|
+
writeLine(io.stdout, refreshedLine(result.project, result.tags));
|
|
28444
|
+
}
|
|
28445
|
+
} catch {
|
|
28446
|
+
}
|
|
28447
|
+
return 0;
|
|
28448
|
+
}
|
|
28449
|
+
async function sync(options, context, io) {
|
|
28450
|
+
const result = await syncFiles(options, context, io);
|
|
28451
|
+
if (options.json === true) {
|
|
28452
|
+
writeJson(io.stdout, {
|
|
28453
|
+
project: result.project,
|
|
28454
|
+
tags: result.tags ?? null,
|
|
28455
|
+
files: result.files,
|
|
28456
|
+
hook: result.hook ?? null
|
|
28457
|
+
});
|
|
28458
|
+
return 0;
|
|
28459
|
+
}
|
|
28460
|
+
for (const file2 of result.files) {
|
|
28461
|
+
writeLine(
|
|
28462
|
+
io.stdout,
|
|
28463
|
+
file2.updated ? `Updated ${file2.path}` : `${file2.path} is up to date`
|
|
28464
|
+
);
|
|
28465
|
+
}
|
|
28466
|
+
return 0;
|
|
28467
|
+
}
|
|
28468
|
+
async function syncFiles(options, context, io) {
|
|
28469
|
+
const project = requireProject(context);
|
|
28470
|
+
const root = context.projectRoot ?? io.cwd;
|
|
28471
|
+
let hook;
|
|
28472
|
+
if (options.hook === true) {
|
|
28473
|
+
const plan = planHook(root);
|
|
28474
|
+
const line = applyHook(plan);
|
|
28475
|
+
hook = plan.path;
|
|
28476
|
+
if (options.json !== true) writeLine(io.stdout, line);
|
|
28477
|
+
}
|
|
28478
|
+
const found = holders(root);
|
|
28479
|
+
if (found.length === 0) {
|
|
28480
|
+
throw new UsageError(
|
|
28481
|
+
`no Quibble snippet under ${root}: run quibble snippet --append CLAUDE.md`
|
|
28482
|
+
);
|
|
28483
|
+
}
|
|
28484
|
+
const { tags, scope } = await snippetSource(project, context, io, { queue: false });
|
|
28485
|
+
const files = [];
|
|
28486
|
+
for (const holder of found) {
|
|
28487
|
+
const next = rewrite(holder, tags, scope);
|
|
28488
|
+
const updated = next !== holder.text;
|
|
28489
|
+
if (updated) writeFileSync6(holder.path, next, "utf8");
|
|
28490
|
+
files.push({ path: holder.path, updated });
|
|
28491
|
+
}
|
|
28492
|
+
return { project, tags, files, hook };
|
|
28493
|
+
}
|
|
28494
|
+
|
|
27612
28495
|
// src/commands/tags.ts
|
|
27613
28496
|
async function runTags(options, context, io) {
|
|
27614
28497
|
const project = requireProject(context);
|
|
27615
28498
|
if (context.mode !== "remote") checkActAs(context);
|
|
27616
|
-
const tags = context.mode === "remote" ? (await openOrgClient(context, io).project(project)).tags : context.allowedTags;
|
|
28499
|
+
const tags = context.mode === "remote" ? (await (await openOrgClient(context, io)).project(project)).tags : context.allowedTags;
|
|
27617
28500
|
if (options.json === true) {
|
|
27618
28501
|
writeJson(io.stdout, { project, tags: tags ?? null });
|
|
27619
28502
|
return 0;
|
|
@@ -27634,7 +28517,7 @@ async function runTagsEdit(edit, given, options, context, io) {
|
|
|
27634
28517
|
const project = requireProject(context);
|
|
27635
28518
|
if (given.length === 0) throw new UsageError(`tags ${edit} needs at least one tag`);
|
|
27636
28519
|
const named = parseAllowedTags(given);
|
|
27637
|
-
const client = openOrgClient(context, io);
|
|
28520
|
+
const client = await openOrgClient(context, io);
|
|
27638
28521
|
let next;
|
|
27639
28522
|
if (edit === "set") {
|
|
27640
28523
|
next = named;
|
|
@@ -27695,7 +28578,7 @@ async function runTokenCreate(name, options, context, io, now = /* @__PURE__ */
|
|
|
27695
28578
|
{ name, ...expiresAt === void 0 ? {} : { expiresAt } },
|
|
27696
28579
|
now
|
|
27697
28580
|
);
|
|
27698
|
-
const client = openOrgClient(context, io);
|
|
28581
|
+
const client = await openOrgClient(context, io);
|
|
27699
28582
|
const minted = await client.createToken(project, body);
|
|
27700
28583
|
if (options.json === true) {
|
|
27701
28584
|
writeJson(io.stdout, minted.body);
|
|
@@ -27705,13 +28588,13 @@ async function runTokenCreate(name, options, context, io, now = /* @__PURE__ */
|
|
|
27705
28588
|
io.stdout,
|
|
27706
28589
|
`Created token ${minted.token.name} for ${org}/${project}. It is shown once:`
|
|
27707
28590
|
);
|
|
27708
|
-
writeLine(io.stdout, `QUIBBLE_ENDPOINT=${
|
|
28591
|
+
writeLine(io.stdout, `QUIBBLE_ENDPOINT=${trimSlash2(client.endpoint)}`);
|
|
27709
28592
|
writeLine(io.stdout, `QUIBBLE_TOKEN=${minted.plaintext}`);
|
|
27710
28593
|
return 0;
|
|
27711
28594
|
}
|
|
27712
28595
|
async function runTokenList(options, context, io, now = /* @__PURE__ */ new Date()) {
|
|
27713
28596
|
const { project } = scopeOf(context);
|
|
27714
|
-
const answer2 = await openOrgClient(context, io).tokens(project);
|
|
28597
|
+
const answer2 = await (await openOrgClient(context, io)).tokens(project);
|
|
27715
28598
|
if (options.json === true) {
|
|
27716
28599
|
writeJson(io.stdout, answer2.body);
|
|
27717
28600
|
return 0;
|
|
@@ -27750,7 +28633,7 @@ function findToken(tokens, value) {
|
|
|
27750
28633
|
}
|
|
27751
28634
|
async function runTokenRevoke(value, options, context, io) {
|
|
27752
28635
|
const { project, org } = scopeOf(context);
|
|
27753
|
-
const client = openOrgClient(context, io);
|
|
28636
|
+
const client = await openOrgClient(context, io);
|
|
27754
28637
|
const token = findToken((await client.tokens(project)).tokens, value);
|
|
27755
28638
|
await client.revokeToken(project, token.id);
|
|
27756
28639
|
if (options.json === true) {
|
|
@@ -27785,6 +28668,7 @@ var TAGS_EDITS = [
|
|
|
27785
28668
|
var ID_HELP = `complaint id, or a unique prefix of at least ${SHORT_ID_MIN} characters`;
|
|
27786
28669
|
var REASON_HELP = `${REASONS.slice(0, -1).join(", ")} or ${REASONS.at(-1) ?? ""}`;
|
|
27787
28670
|
function packageVersion() {
|
|
28671
|
+
if (typeof QUIBBLE_BUILD_VERSION === "string") return QUIBBLE_BUILD_VERSION;
|
|
27788
28672
|
const require2 = createRequire(import.meta.url);
|
|
27789
28673
|
const manifest = require2("../package.json");
|
|
27790
28674
|
return typeof manifest.version === "string" ? manifest.version : "0.0.0";
|
|
@@ -27798,7 +28682,7 @@ function rejectEmptyOptions(options) {
|
|
|
27798
28682
|
}
|
|
27799
28683
|
}
|
|
27800
28684
|
function withGlobalOptions(command) {
|
|
27801
|
-
return command.option("--local", "use the local store
|
|
28685
|
+
return command.option("--local", "use the local store, whatever the endpoint says").option("--endpoint <url>", "API URL, web URL, host, or local").option("--org <slug>", "organization to use in remote mode").option("--json", "print JSON instead of text");
|
|
27802
28686
|
}
|
|
27803
28687
|
function globals(program2, command) {
|
|
27804
28688
|
const outer = program2.opts();
|
|
@@ -27836,19 +28720,34 @@ function buildProgram(io, setCode) {
|
|
|
27836
28720
|
}).exitOverride();
|
|
27837
28721
|
withGlobalOptions(program2);
|
|
27838
28722
|
withGlobalOptions(
|
|
27839
|
-
program2.command("init").description("write a .quibblerc in the current directory").option("--project <name>", "project name (default: the directory name)").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("--force", "overwrite an existing .quibblerc or skill file")
|
|
28723
|
+
program2.command("init").description("write a .quibblerc in the current directory").option("--project <name>", "project name (default: the directory name)").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("--force", "overwrite an existing .quibblerc or skill file")
|
|
27840
28724
|
).action(async function() {
|
|
27841
28725
|
const options = { ...this.opts(), ...globals(program2, this) };
|
|
27842
28726
|
rejectEmptyOptions(options);
|
|
27843
28727
|
setCode(await runInit(options, io));
|
|
27844
28728
|
});
|
|
27845
28729
|
withGlobalOptions(
|
|
27846
|
-
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("--agent <name>", "agent named in the snippet").option("--project <name>", "project name (overrides .quibblerc)").option("--force", "overwrite an existing skill file")
|
|
28730
|
+
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")
|
|
27847
28731
|
).action(async function() {
|
|
27848
28732
|
const options = { ...this.opts(), ...globals(program2, this) };
|
|
27849
28733
|
rejectEmptyOptions(options);
|
|
27850
28734
|
setCode(await runSnippet(options, contextFor(options, io), io));
|
|
27851
28735
|
});
|
|
28736
|
+
withGlobalOptions(
|
|
28737
|
+
program2.command("sync").description("rewrite the agent snippet in place with the current tags").option("--hook", "also add the quibble sync hook to .claude/settings.json").option("--quiet", "print nothing but a changed tag list, and always exit 0").option("--project <name>", "project name (overrides .quibblerc)")
|
|
28738
|
+
).action(async function() {
|
|
28739
|
+
const options = { ...this.opts(), ...globals(program2, this) };
|
|
28740
|
+
setCode(
|
|
28741
|
+
await runSync(
|
|
28742
|
+
options,
|
|
28743
|
+
() => {
|
|
28744
|
+
rejectEmptyOptions(options);
|
|
28745
|
+
return contextFor(options, io);
|
|
28746
|
+
},
|
|
28747
|
+
io
|
|
28748
|
+
)
|
|
28749
|
+
);
|
|
28750
|
+
});
|
|
27852
28751
|
withGlobalOptions(
|
|
27853
28752
|
program2.command("complain").description("file a complaint").argument("[message]", "what went wrong; omit it or pass `-` to read stdin").option("-s, --severity <level>", "low, medium or high").option("-t, --tag <tag>", "tag (repeatable)", collect).option("--agent <name>", "name of the agent filing it").option("--reporter <name>", "person you are filing for (local mode only)").option("--project <name>", "project name (overrides .quibblerc)").option("--cmd <command>", "the command that failed")
|
|
27854
28753
|
).action(async function(message) {
|
|
@@ -27944,6 +28843,14 @@ function buildProgram(io, setCode) {
|
|
|
27944
28843
|
rejectEmptyOptions(options);
|
|
27945
28844
|
setCode(await runTokenRevoke(value, options, contextFor(options, io), io));
|
|
27946
28845
|
});
|
|
28846
|
+
const projectCommand = program2.command("project").description("manage the projects of the organization (remote only)");
|
|
28847
|
+
withGlobalOptions(
|
|
28848
|
+
projectCommand.command("delete").description("delete a project and hide its complaints").argument("<name>", "project name").option("--yes", "skip the question")
|
|
28849
|
+
).action(async function(name) {
|
|
28850
|
+
const options = { ...this.opts(), ...globals(program2, this) };
|
|
28851
|
+
rejectEmptyOptions(options);
|
|
28852
|
+
setCode(await runProjectDelete(name, options, contextFor(options, io), io));
|
|
28853
|
+
});
|
|
27947
28854
|
withGlobalOptions(
|
|
27948
28855
|
program2.command("retry").description("send the complaints queued while offline")
|
|
27949
28856
|
).action(async function() {
|
|
@@ -27966,11 +28873,17 @@ function buildProgram(io, setCode) {
|
|
|
27966
28873
|
setCode(await runOrgs(options, contextFor(options, io), io));
|
|
27967
28874
|
});
|
|
27968
28875
|
withGlobalOptions(
|
|
27969
|
-
program2.command("login").description("log in to the endpoint")
|
|
28876
|
+
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")
|
|
27970
28877
|
).action(async function() {
|
|
27971
28878
|
const options = { ...this.opts(), ...globals(program2, this) };
|
|
27972
28879
|
rejectEmptyOptions(options);
|
|
27973
|
-
setCode(
|
|
28880
|
+
setCode(
|
|
28881
|
+
await runLogin(
|
|
28882
|
+
{ ...options, version: packageVersion() },
|
|
28883
|
+
contextFor(options, io),
|
|
28884
|
+
io
|
|
28885
|
+
)
|
|
28886
|
+
);
|
|
27974
28887
|
});
|
|
27975
28888
|
withGlobalOptions(
|
|
27976
28889
|
program2.command("logout").description("log out of the endpoint")
|
|
@@ -27988,10 +28901,10 @@ function buildProgram(io, setCode) {
|
|
|
27988
28901
|
});
|
|
27989
28902
|
withGlobalOptions(
|
|
27990
28903
|
program2.command("config").description("print the resolved configuration")
|
|
27991
|
-
).action(function() {
|
|
28904
|
+
).action(async function() {
|
|
27992
28905
|
const options = { ...this.opts(), ...globals(program2, this) };
|
|
27993
28906
|
rejectEmptyOptions(options);
|
|
27994
|
-
setCode(runConfig(options, contextFor(options, io), io));
|
|
28907
|
+
setCode(await runConfig(options, contextFor(options, io), io));
|
|
27995
28908
|
});
|
|
27996
28909
|
return program2;
|
|
27997
28910
|
}
|
|
@@ -27999,9 +28912,10 @@ function stripErrorPrefix(message) {
|
|
|
27999
28912
|
return message.replace(/^error:\s*/i, "");
|
|
28000
28913
|
}
|
|
28001
28914
|
async function main(argv, io) {
|
|
28002
|
-
|
|
28915
|
+
resetDiscovery();
|
|
28916
|
+
let code2 = 0;
|
|
28003
28917
|
const program2 = buildProgram(io, (value) => {
|
|
28004
|
-
|
|
28918
|
+
code2 = value;
|
|
28005
28919
|
});
|
|
28006
28920
|
if (argv.length === 0) {
|
|
28007
28921
|
io.stdout.write(program2.helpInformation());
|
|
@@ -28009,7 +28923,7 @@ async function main(argv, io) {
|
|
|
28009
28923
|
}
|
|
28010
28924
|
try {
|
|
28011
28925
|
await program2.parseAsync(argv, { from: "user" });
|
|
28012
|
-
return
|
|
28926
|
+
return code2;
|
|
28013
28927
|
} catch (error62) {
|
|
28014
28928
|
if (error62 instanceof CommanderError) {
|
|
28015
28929
|
if (error62.code === "commander.help" || error62.code === "commander.helpDisplayed") {
|
|
@@ -28031,10 +28945,18 @@ for (const stream of [process2.stdout, process2.stderr]) {
|
|
|
28031
28945
|
throw error62;
|
|
28032
28946
|
});
|
|
28033
28947
|
}
|
|
28034
|
-
|
|
28948
|
+
function flushed(stream) {
|
|
28949
|
+
return new Promise((resolve4) => {
|
|
28950
|
+
stream.write("", () => resolve4());
|
|
28951
|
+
});
|
|
28952
|
+
}
|
|
28953
|
+
var code = await main(process2.argv.slice(2), {
|
|
28035
28954
|
stdin: process2.stdin,
|
|
28036
28955
|
stdout: process2.stdout,
|
|
28037
28956
|
stderr: process2.stderr,
|
|
28038
28957
|
env: process2.env,
|
|
28039
28958
|
cwd: process2.cwd()
|
|
28040
28959
|
});
|
|
28960
|
+
process2.exitCode = code;
|
|
28961
|
+
await Promise.all([flushed(process2.stdout), flushed(process2.stderr)]);
|
|
28962
|
+
process2.exit(code);
|