cleargate 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/MANIFEST.json +2 -2
- package/dist/cli.cjs +644 -95
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +643 -95
- package/dist/cli.js.map +1 -1
- package/dist/templates/cleargate-planning/MANIFEST.json +2 -2
- package/package.json +1 -1
- package/templates/cleargate-planning/MANIFEST.json +2 -2
package/dist/cli.cjs
CHANGED
|
@@ -627,7 +627,7 @@ var import_commander = require("commander");
|
|
|
627
627
|
// package.json
|
|
628
628
|
var package_default = {
|
|
629
629
|
name: "cleargate",
|
|
630
|
-
version: "0.
|
|
630
|
+
version: "0.4.0",
|
|
631
631
|
private: false,
|
|
632
632
|
type: "module",
|
|
633
633
|
description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, four-agent loop (architect/developer/qa/reporter), Karpathy-style awareness wiki.",
|
|
@@ -936,13 +936,295 @@ init_cjs_shims();
|
|
|
936
936
|
var os3 = __toESM(require("os"), 1);
|
|
937
937
|
init_config();
|
|
938
938
|
init_factory();
|
|
939
|
+
|
|
940
|
+
// src/auth/identity-flow.ts
|
|
941
|
+
init_cjs_shims();
|
|
942
|
+
var readline = __toESM(require("readline"), 1);
|
|
943
|
+
var IdentityFlowError = class extends Error {
|
|
944
|
+
constructor(code, message) {
|
|
945
|
+
super(message ?? code);
|
|
946
|
+
this.code = code;
|
|
947
|
+
this.name = "IdentityFlowError";
|
|
948
|
+
}
|
|
949
|
+
code;
|
|
950
|
+
};
|
|
951
|
+
var DeviceFlowError = class extends Error {
|
|
952
|
+
constructor(code, message) {
|
|
953
|
+
super(message ?? code);
|
|
954
|
+
this.code = code;
|
|
955
|
+
this.name = "DeviceFlowError";
|
|
956
|
+
}
|
|
957
|
+
code;
|
|
958
|
+
};
|
|
959
|
+
async function pickProvider(opts) {
|
|
960
|
+
const available = opts.available ?? ["github", "email"];
|
|
961
|
+
if (opts.flag !== void 0) {
|
|
962
|
+
const flagLower = opts.flag.toLowerCase();
|
|
963
|
+
if (!available.includes(flagLower)) {
|
|
964
|
+
throw new IdentityFlowError(
|
|
965
|
+
"provider_unknown",
|
|
966
|
+
`cleargate: unknown provider '${opts.flag}'. Available: ${available.join(", ")}`
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
return flagLower;
|
|
970
|
+
}
|
|
971
|
+
if (!opts.isTTY) {
|
|
972
|
+
throw new IdentityFlowError(
|
|
973
|
+
"provider_required",
|
|
974
|
+
"cleargate: --auth required in non-interactive mode"
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
if (available.length === 1) {
|
|
978
|
+
return available[0];
|
|
979
|
+
}
|
|
980
|
+
return promptPicker(available, opts);
|
|
981
|
+
}
|
|
982
|
+
var PROVIDER_LABELS = {
|
|
983
|
+
github: "GitHub OAuth",
|
|
984
|
+
email: "Email magic-link"
|
|
985
|
+
};
|
|
986
|
+
async function promptPicker(options, { stdin, stdout } = {}) {
|
|
987
|
+
const write = stdout ?? ((s) => process.stdout.write(s));
|
|
988
|
+
write("How would you like to verify your email?\n");
|
|
989
|
+
options.forEach((p, i) => {
|
|
990
|
+
write(` ${i + 1}. ${PROVIDER_LABELS[p]}
|
|
991
|
+
`);
|
|
992
|
+
});
|
|
993
|
+
write(`Choice [1-${options.length}]: `);
|
|
994
|
+
const inputStream = stdin ?? process.stdin;
|
|
995
|
+
return new Promise((resolve14, reject) => {
|
|
996
|
+
let settled = false;
|
|
997
|
+
const rl = readline.createInterface({
|
|
998
|
+
input: inputStream,
|
|
999
|
+
output: void 0,
|
|
1000
|
+
terminal: false
|
|
1001
|
+
});
|
|
1002
|
+
rl.once("line", (line) => {
|
|
1003
|
+
settled = true;
|
|
1004
|
+
rl.close();
|
|
1005
|
+
const idx = parseInt(line.trim(), 10) - 1;
|
|
1006
|
+
if (isNaN(idx) || idx < 0 || idx >= options.length) {
|
|
1007
|
+
reject(
|
|
1008
|
+
new IdentityFlowError(
|
|
1009
|
+
"invalid_choice",
|
|
1010
|
+
`cleargate: invalid choice '${line.trim()}'. Enter a number between 1 and ${options.length}.`
|
|
1011
|
+
)
|
|
1012
|
+
);
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
resolve14(options[idx]);
|
|
1016
|
+
});
|
|
1017
|
+
rl.once("error", (err) => {
|
|
1018
|
+
if (!settled) {
|
|
1019
|
+
settled = true;
|
|
1020
|
+
reject(err);
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
rl.once("close", () => {
|
|
1024
|
+
if (!settled) {
|
|
1025
|
+
settled = true;
|
|
1026
|
+
reject(new IdentityFlowError("provider_required", "cleargate: no provider selected"));
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
function defaultSleep(ms) {
|
|
1032
|
+
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
1033
|
+
}
|
|
1034
|
+
async function startDeviceFlow(opts) {
|
|
1035
|
+
const sleepFn = opts.sleepFn ?? defaultSleep;
|
|
1036
|
+
let currentIntervalMs = opts.intervalOverrideMs !== void 0 ? opts.intervalOverrideMs : Math.max(opts.interval, 5) * 1e3;
|
|
1037
|
+
const expiresAtMs = Date.now() + opts.expiresIn * 1e3;
|
|
1038
|
+
const deadline = expiresAtMs + (opts.deadlineGraceMs ?? 1e4);
|
|
1039
|
+
while (Date.now() < deadline) {
|
|
1040
|
+
await sleepFn(currentIntervalMs);
|
|
1041
|
+
let pollRes;
|
|
1042
|
+
try {
|
|
1043
|
+
pollRes = await opts.fetchPoll(opts.deviceCode);
|
|
1044
|
+
} catch {
|
|
1045
|
+
throw new DeviceFlowError("unreachable");
|
|
1046
|
+
}
|
|
1047
|
+
if (pollRes.status === 403) {
|
|
1048
|
+
let body2 = {};
|
|
1049
|
+
try {
|
|
1050
|
+
body2 = await pollRes.json();
|
|
1051
|
+
} catch {
|
|
1052
|
+
}
|
|
1053
|
+
if (body2["error"] === "access_denied") {
|
|
1054
|
+
throw new DeviceFlowError("access_denied");
|
|
1055
|
+
}
|
|
1056
|
+
throw new DeviceFlowError("not_admin");
|
|
1057
|
+
}
|
|
1058
|
+
if (pollRes.status === 410) {
|
|
1059
|
+
throw new DeviceFlowError("expired_token");
|
|
1060
|
+
}
|
|
1061
|
+
if (!pollRes.status || pollRes.status < 200 || pollRes.status >= 300) {
|
|
1062
|
+
if (pollRes.status >= 500 || pollRes.status < 100) {
|
|
1063
|
+
throw new DeviceFlowError("server_error");
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
let body;
|
|
1067
|
+
try {
|
|
1068
|
+
body = await pollRes.json();
|
|
1069
|
+
} catch {
|
|
1070
|
+
throw new DeviceFlowError("server_error");
|
|
1071
|
+
}
|
|
1072
|
+
const errorField = body["error"];
|
|
1073
|
+
if (typeof errorField === "string") {
|
|
1074
|
+
if (errorField === "authorization_pending") {
|
|
1075
|
+
continue;
|
|
1076
|
+
}
|
|
1077
|
+
if (errorField === "slow_down") {
|
|
1078
|
+
const retryAfter = body["interval"];
|
|
1079
|
+
if (typeof retryAfter === "number") {
|
|
1080
|
+
const bumped = retryAfter * 1e3;
|
|
1081
|
+
if (bumped > currentIntervalMs) {
|
|
1082
|
+
currentIntervalMs = bumped;
|
|
1083
|
+
}
|
|
1084
|
+
} else {
|
|
1085
|
+
currentIntervalMs += 5e3;
|
|
1086
|
+
}
|
|
1087
|
+
continue;
|
|
1088
|
+
}
|
|
1089
|
+
if (errorField === "access_denied") {
|
|
1090
|
+
throw new DeviceFlowError("access_denied");
|
|
1091
|
+
}
|
|
1092
|
+
if (errorField === "expired_token") {
|
|
1093
|
+
throw new DeviceFlowError("expired_token");
|
|
1094
|
+
}
|
|
1095
|
+
throw new DeviceFlowError("server_error");
|
|
1096
|
+
}
|
|
1097
|
+
if (body["pending"] === true) {
|
|
1098
|
+
const shouldApplyBump = opts.sleepFn !== void 0 || opts.intervalOverrideMs === void 0;
|
|
1099
|
+
if (shouldApplyBump && typeof body["retry_after"] === "number") {
|
|
1100
|
+
const bumped = body["retry_after"] * 1e3;
|
|
1101
|
+
if (bumped > currentIntervalMs) {
|
|
1102
|
+
currentIntervalMs = bumped;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
if (typeof body["access_token"] === "string") {
|
|
1108
|
+
return { accessToken: body["access_token"] };
|
|
1109
|
+
}
|
|
1110
|
+
if (typeof body["admin_token"] === "string") {
|
|
1111
|
+
return { accessToken: body["admin_token"] };
|
|
1112
|
+
}
|
|
1113
|
+
throw new DeviceFlowError("server_error");
|
|
1114
|
+
}
|
|
1115
|
+
throw new DeviceFlowError("timeout");
|
|
1116
|
+
}
|
|
1117
|
+
function mapProviderError(httpStatus, errorCode, retryAfterSeconds) {
|
|
1118
|
+
if (httpStatus === 400) {
|
|
1119
|
+
switch (errorCode) {
|
|
1120
|
+
case "provider_not_allowed":
|
|
1121
|
+
return {
|
|
1122
|
+
message: "cleargate: this invite requires a different provider \u2014 re-run with `--auth <pinned>`",
|
|
1123
|
+
exitCode: 9,
|
|
1124
|
+
retryable: true
|
|
1125
|
+
};
|
|
1126
|
+
case "provider_unknown":
|
|
1127
|
+
return {
|
|
1128
|
+
message: "cleargate: server does not have that provider registered \u2014 contact the project admin",
|
|
1129
|
+
exitCode: 9,
|
|
1130
|
+
retryable: false
|
|
1131
|
+
};
|
|
1132
|
+
case "identity_proof_required":
|
|
1133
|
+
return {
|
|
1134
|
+
message: "cleargate: this CLI is out of date \u2014 please upgrade and retry (`npm i -g cleargate@latest`)",
|
|
1135
|
+
exitCode: 11,
|
|
1136
|
+
retryable: false
|
|
1137
|
+
};
|
|
1138
|
+
default:
|
|
1139
|
+
return {
|
|
1140
|
+
message: "cleargate: invalid request to server (please file a bug)",
|
|
1141
|
+
exitCode: 7,
|
|
1142
|
+
retryable: false
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
if (httpStatus === 403 && errorCode === "email_mismatch") {
|
|
1147
|
+
return {
|
|
1148
|
+
message: "cleargate: verified email does not match the invitee \u2014 ask your admin to re-issue the invite",
|
|
1149
|
+
exitCode: 10,
|
|
1150
|
+
retryable: false
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
if (httpStatus === 404) {
|
|
1154
|
+
return {
|
|
1155
|
+
message: "cleargate: invite not found",
|
|
1156
|
+
exitCode: 4,
|
|
1157
|
+
retryable: false
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
if (httpStatus === 410) {
|
|
1161
|
+
switch (errorCode) {
|
|
1162
|
+
case "invite_expired":
|
|
1163
|
+
return {
|
|
1164
|
+
message: "cleargate: invite expired. Request a new invite",
|
|
1165
|
+
exitCode: 3,
|
|
1166
|
+
retryable: false
|
|
1167
|
+
};
|
|
1168
|
+
case "invite_already_consumed":
|
|
1169
|
+
return {
|
|
1170
|
+
message: "cleargate: invite already consumed. Request a new invite",
|
|
1171
|
+
exitCode: 3,
|
|
1172
|
+
retryable: false
|
|
1173
|
+
};
|
|
1174
|
+
case "challenge_expired":
|
|
1175
|
+
return {
|
|
1176
|
+
message: "cleargate: code expired. Re-run `cleargate join <url>` to start over",
|
|
1177
|
+
exitCode: 3,
|
|
1178
|
+
retryable: false
|
|
1179
|
+
};
|
|
1180
|
+
default:
|
|
1181
|
+
return {
|
|
1182
|
+
message: "cleargate: invite no longer valid. Request a new invite",
|
|
1183
|
+
exitCode: 3,
|
|
1184
|
+
retryable: false
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
if (httpStatus === 429) {
|
|
1189
|
+
const retryHint = retryAfterSeconds !== void 0 ? `${retryAfterSeconds}` : "900";
|
|
1190
|
+
return {
|
|
1191
|
+
message: `cleargate: too many requests. Retry after ${retryHint}s`,
|
|
1192
|
+
exitCode: 8,
|
|
1193
|
+
retryable: true
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
if (httpStatus === 502 && errorCode === "provider_error") {
|
|
1197
|
+
return {
|
|
1198
|
+
message: "cleargate: code didn't match. Try again, or restart with `cleargate join <url>`",
|
|
1199
|
+
exitCode: 12,
|
|
1200
|
+
retryable: true
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
if (httpStatus >= 500) {
|
|
1204
|
+
return {
|
|
1205
|
+
message: `cleargate: server error ${httpStatus}`,
|
|
1206
|
+
exitCode: 6,
|
|
1207
|
+
retryable: false
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
return {
|
|
1211
|
+
message: `cleargate: unexpected error ${httpStatus} ${errorCode}`,
|
|
1212
|
+
exitCode: 7,
|
|
1213
|
+
retryable: false
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
// src/commands/join.ts
|
|
1218
|
+
var readline2 = __toESM(require("readline"), 1);
|
|
939
1219
|
var UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
1220
|
+
var GITHUB_DEVICE_FLOW_URL = "https://github.com/login/oauth/access_token";
|
|
940
1221
|
async function joinHandler(opts) {
|
|
941
1222
|
const fetchFn = opts.fetch ?? globalThis.fetch;
|
|
942
1223
|
const stdout = opts.stdout ?? ((s) => process.stdout.write(s));
|
|
943
1224
|
const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
|
|
944
1225
|
const exit = opts.exit ?? ((c) => process.exit(c));
|
|
945
1226
|
const hostname3 = opts.hostname ?? (() => os3.hostname());
|
|
1227
|
+
const isTTY = opts.isTTY ?? process.stdin.isTTY === true;
|
|
946
1228
|
let token;
|
|
947
1229
|
let baseUrl;
|
|
948
1230
|
try {
|
|
@@ -973,60 +1255,309 @@ async function joinHandler(opts) {
|
|
|
973
1255
|
exit(5);
|
|
974
1256
|
return;
|
|
975
1257
|
}
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
} catch (err) {
|
|
980
|
-
stderr(
|
|
981
|
-
`cleargate: cannot reach ${baseUrl} (${err instanceof Error ? err.message : String(err)}).
|
|
982
|
-
`
|
|
983
|
-
);
|
|
984
|
-
exit(2);
|
|
1258
|
+
if (opts.nonInteractive && !opts.auth) {
|
|
1259
|
+
stderr("cleargate: --auth required in non-interactive mode\n");
|
|
1260
|
+
exit(1);
|
|
985
1261
|
return;
|
|
986
1262
|
}
|
|
987
|
-
if (
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
stderr("cleargate: invite expired. Request a new invite.\n");
|
|
991
|
-
} else {
|
|
992
|
-
stderr("cleargate: invite already consumed. Request a new invite.\n");
|
|
993
|
-
}
|
|
994
|
-
exit(3);
|
|
1263
|
+
if (opts.nonInteractive && opts.auth === "email" && !opts.code) {
|
|
1264
|
+
stderr("cleargate: --code required for email provider in non-interactive mode\n");
|
|
1265
|
+
exit(1);
|
|
995
1266
|
return;
|
|
996
1267
|
}
|
|
997
|
-
if (
|
|
998
|
-
stderr("cleargate:
|
|
999
|
-
exit(
|
|
1268
|
+
if (opts.nonInteractive && opts.auth === "github") {
|
|
1269
|
+
stderr("cleargate: GitHub auth requires browser interaction; use `--auth email` for non-interactive flows\n");
|
|
1270
|
+
exit(1);
|
|
1000
1271
|
return;
|
|
1001
1272
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1273
|
+
let provider;
|
|
1274
|
+
try {
|
|
1275
|
+
provider = await pickProvider({
|
|
1276
|
+
flag: opts.auth,
|
|
1277
|
+
isTTY: !opts.nonInteractive && isTTY,
|
|
1278
|
+
available: ["github", "email"],
|
|
1279
|
+
stdin: opts.stdin,
|
|
1280
|
+
stdout
|
|
1281
|
+
});
|
|
1282
|
+
} catch (err) {
|
|
1283
|
+
if (err instanceof IdentityFlowError) {
|
|
1284
|
+
stderr(`${err.message}
|
|
1005
1285
|
`);
|
|
1006
|
-
|
|
1007
|
-
|
|
1286
|
+
exit(1);
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
throw err;
|
|
1008
1290
|
}
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1291
|
+
let challengeRes;
|
|
1292
|
+
try {
|
|
1293
|
+
challengeRes = await fetchFn(`${baseUrl}/join/${token}/challenge`, {
|
|
1294
|
+
method: "POST",
|
|
1295
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
1296
|
+
body: JSON.stringify({ provider })
|
|
1297
|
+
});
|
|
1298
|
+
} catch (err) {
|
|
1299
|
+
stderr(
|
|
1300
|
+
`cleargate: cannot reach ${baseUrl} (${err instanceof Error ? err.message : String(err)}).
|
|
1301
|
+
`
|
|
1302
|
+
);
|
|
1303
|
+
exit(2);
|
|
1013
1304
|
return;
|
|
1014
1305
|
}
|
|
1015
|
-
if (!
|
|
1016
|
-
|
|
1306
|
+
if (!challengeRes.ok) {
|
|
1307
|
+
const body = await challengeRes.json().catch(() => ({}));
|
|
1308
|
+
const { message, exitCode } = mapProviderError(
|
|
1309
|
+
challengeRes.status,
|
|
1310
|
+
body.error ?? "",
|
|
1311
|
+
parseRetryAfter(challengeRes)
|
|
1312
|
+
);
|
|
1313
|
+
stderr(`${message}
|
|
1017
1314
|
`);
|
|
1018
|
-
exit(
|
|
1315
|
+
exit(exitCode);
|
|
1019
1316
|
return;
|
|
1020
1317
|
}
|
|
1021
|
-
let
|
|
1318
|
+
let challengeBody;
|
|
1022
1319
|
try {
|
|
1023
|
-
|
|
1320
|
+
challengeBody = await challengeRes.json();
|
|
1024
1321
|
} catch {
|
|
1025
1322
|
stderr("cleargate: server returned non-JSON response.\n");
|
|
1026
1323
|
exit(7);
|
|
1027
1324
|
return;
|
|
1028
1325
|
}
|
|
1029
|
-
const
|
|
1326
|
+
const challengeId = challengeBody.challenge_id;
|
|
1327
|
+
const clientHints = challengeBody.client_hints;
|
|
1328
|
+
let completeRawBody;
|
|
1329
|
+
if (provider === "github") {
|
|
1330
|
+
const deviceCode = clientHints["device_code"];
|
|
1331
|
+
const userCode = clientHints["user_code"];
|
|
1332
|
+
const verificationUri = clientHints["verification_uri"];
|
|
1333
|
+
const expiresIn = typeof clientHints["expires_in"] === "number" ? clientHints["expires_in"] : 900;
|
|
1334
|
+
const interval = typeof clientHints["interval"] === "number" ? clientHints["interval"] : 5;
|
|
1335
|
+
stdout(`Open the following URL in your browser and enter the code:
|
|
1336
|
+
`);
|
|
1337
|
+
stdout(` URL: ${verificationUri}
|
|
1338
|
+
`);
|
|
1339
|
+
stdout(` Code: ${userCode}
|
|
1340
|
+
`);
|
|
1341
|
+
stdout(` (Code expires in ${Math.floor(expiresIn / 60)} minutes)
|
|
1342
|
+
`);
|
|
1343
|
+
stdout("Waiting for authorization...\n");
|
|
1344
|
+
let accessToken;
|
|
1345
|
+
try {
|
|
1346
|
+
const result = await startDeviceFlow({
|
|
1347
|
+
deviceCode,
|
|
1348
|
+
interval,
|
|
1349
|
+
expiresIn,
|
|
1350
|
+
fetchPoll: async (dc) => {
|
|
1351
|
+
const res = await fetchFn(GITHUB_DEVICE_FLOW_URL, {
|
|
1352
|
+
method: "POST",
|
|
1353
|
+
headers: {
|
|
1354
|
+
Accept: "application/json",
|
|
1355
|
+
"Content-Type": "application/json"
|
|
1356
|
+
},
|
|
1357
|
+
body: JSON.stringify({
|
|
1358
|
+
device_code: dc,
|
|
1359
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
1360
|
+
})
|
|
1361
|
+
});
|
|
1362
|
+
return {
|
|
1363
|
+
status: res.status,
|
|
1364
|
+
json: () => res.json()
|
|
1365
|
+
};
|
|
1366
|
+
},
|
|
1367
|
+
...opts.sleepFn !== void 0 ? { sleepFn: opts.sleepFn } : {},
|
|
1368
|
+
...opts.intervalOverrideMs !== void 0 ? { intervalOverrideMs: opts.intervalOverrideMs } : {}
|
|
1369
|
+
});
|
|
1370
|
+
accessToken = result.accessToken;
|
|
1371
|
+
} catch (err) {
|
|
1372
|
+
if (err instanceof DeviceFlowError) {
|
|
1373
|
+
switch (err.code) {
|
|
1374
|
+
case "access_denied":
|
|
1375
|
+
stderr("cleargate: access denied \u2014 you declined authorization in the browser.\n");
|
|
1376
|
+
exit(5);
|
|
1377
|
+
return;
|
|
1378
|
+
case "expired_token":
|
|
1379
|
+
stderr("cleargate: device code expired \u2014 please re-run `cleargate join <url>`.\n");
|
|
1380
|
+
exit(5);
|
|
1381
|
+
return;
|
|
1382
|
+
case "unreachable":
|
|
1383
|
+
stderr("cleargate: cannot reach GitHub. Check your connection and retry.\n");
|
|
1384
|
+
exit(2);
|
|
1385
|
+
return;
|
|
1386
|
+
default:
|
|
1387
|
+
stderr(`cleargate: GitHub device flow error: ${err.code}
|
|
1388
|
+
`);
|
|
1389
|
+
exit(6);
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
stderr("cleargate: unexpected error during GitHub device flow\n");
|
|
1394
|
+
exit(6);
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
let completeRes;
|
|
1398
|
+
try {
|
|
1399
|
+
completeRes = await fetchFn(`${baseUrl}/join/${token}/complete`, {
|
|
1400
|
+
method: "POST",
|
|
1401
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
1402
|
+
body: JSON.stringify({ challenge_id: challengeId, proof: { access_token: accessToken } })
|
|
1403
|
+
});
|
|
1404
|
+
} catch (err) {
|
|
1405
|
+
stderr(`cleargate: cannot reach ${baseUrl} (${err instanceof Error ? err.message : String(err)}).
|
|
1406
|
+
`);
|
|
1407
|
+
exit(2);
|
|
1408
|
+
return;
|
|
1409
|
+
}
|
|
1410
|
+
if (!completeRes.ok) {
|
|
1411
|
+
const body = await completeRes.json().catch(() => ({}));
|
|
1412
|
+
const { message, exitCode } = mapProviderError(
|
|
1413
|
+
completeRes.status,
|
|
1414
|
+
body.error ?? "",
|
|
1415
|
+
parseRetryAfter(completeRes)
|
|
1416
|
+
);
|
|
1417
|
+
stderr(`${message}
|
|
1418
|
+
`);
|
|
1419
|
+
exit(exitCode);
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
try {
|
|
1423
|
+
completeRawBody = await completeRes.json();
|
|
1424
|
+
} catch {
|
|
1425
|
+
stderr("cleargate: server returned non-JSON response.\n");
|
|
1426
|
+
exit(7);
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
} else {
|
|
1430
|
+
const sentTo = typeof clientHints["sent_to"] === "string" ? clientHints["sent_to"] : "(unknown)";
|
|
1431
|
+
const maxRetries = 3;
|
|
1432
|
+
stdout(`We sent a 6-digit code to ${sentTo}.
|
|
1433
|
+
`);
|
|
1434
|
+
if (opts.code !== void 0) {
|
|
1435
|
+
let completeRes;
|
|
1436
|
+
try {
|
|
1437
|
+
completeRes = await fetchFn(`${baseUrl}/join/${token}/complete`, {
|
|
1438
|
+
method: "POST",
|
|
1439
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
1440
|
+
body: JSON.stringify({ challenge_id: challengeId, proof: { code: opts.code } })
|
|
1441
|
+
});
|
|
1442
|
+
} catch (err) {
|
|
1443
|
+
stderr(`cleargate: cannot reach ${baseUrl} (${err instanceof Error ? err.message : String(err)}).
|
|
1444
|
+
`);
|
|
1445
|
+
exit(2);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
if (!completeRes.ok) {
|
|
1449
|
+
const body = await completeRes.json().catch(() => ({}));
|
|
1450
|
+
const { message, exitCode } = mapProviderError(
|
|
1451
|
+
completeRes.status,
|
|
1452
|
+
body.error ?? "",
|
|
1453
|
+
parseRetryAfter(completeRes)
|
|
1454
|
+
);
|
|
1455
|
+
stderr(`${message}
|
|
1456
|
+
`);
|
|
1457
|
+
exit(exitCode);
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
1460
|
+
try {
|
|
1461
|
+
completeRawBody = await completeRes.json();
|
|
1462
|
+
} catch {
|
|
1463
|
+
stderr("cleargate: server returned non-JSON response.\n");
|
|
1464
|
+
exit(7);
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
} else {
|
|
1468
|
+
let readNextLine2 = function() {
|
|
1469
|
+
if (lineQueue.length > 0) return Promise.resolve(lineQueue.shift());
|
|
1470
|
+
if (rlClosed) return Promise.resolve("");
|
|
1471
|
+
return new Promise((resolve14) => {
|
|
1472
|
+
lineWaiters.push(resolve14);
|
|
1473
|
+
});
|
|
1474
|
+
};
|
|
1475
|
+
var readNextLine = readNextLine2;
|
|
1476
|
+
const inputStream = opts.stdin ?? process.stdin;
|
|
1477
|
+
const rl = readline2.createInterface({
|
|
1478
|
+
input: inputStream,
|
|
1479
|
+
output: void 0,
|
|
1480
|
+
terminal: false
|
|
1481
|
+
});
|
|
1482
|
+
const lineQueue = [];
|
|
1483
|
+
const lineWaiters = [];
|
|
1484
|
+
let rlClosed = false;
|
|
1485
|
+
rl.on("line", (line) => {
|
|
1486
|
+
const waiter = lineWaiters.shift();
|
|
1487
|
+
if (waiter) {
|
|
1488
|
+
waiter(line);
|
|
1489
|
+
} else {
|
|
1490
|
+
lineQueue.push(line);
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
rl.once("close", () => {
|
|
1494
|
+
rlClosed = true;
|
|
1495
|
+
for (const waiter of lineWaiters.splice(0)) {
|
|
1496
|
+
waiter("");
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
let succeeded = false;
|
|
1500
|
+
try {
|
|
1501
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1502
|
+
stdout("Enter code: ");
|
|
1503
|
+
const otpCode = (await readNextLine2()).trim();
|
|
1504
|
+
let completeRes;
|
|
1505
|
+
try {
|
|
1506
|
+
completeRes = await fetchFn(`${baseUrl}/join/${token}/complete`, {
|
|
1507
|
+
method: "POST",
|
|
1508
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
1509
|
+
body: JSON.stringify({ challenge_id: challengeId, proof: { code: otpCode } })
|
|
1510
|
+
});
|
|
1511
|
+
} catch (err) {
|
|
1512
|
+
stderr(`cleargate: cannot reach ${baseUrl} (${err instanceof Error ? err.message : String(err)}).
|
|
1513
|
+
`);
|
|
1514
|
+
exit(2);
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
if (completeRes.ok) {
|
|
1518
|
+
try {
|
|
1519
|
+
completeRawBody = await completeRes.json();
|
|
1520
|
+
} catch {
|
|
1521
|
+
stderr("cleargate: server returned non-JSON response.\n");
|
|
1522
|
+
exit(7);
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
succeeded = true;
|
|
1526
|
+
break;
|
|
1527
|
+
}
|
|
1528
|
+
const body = await completeRes.json().catch(() => ({}));
|
|
1529
|
+
const errorCode = body.error ?? "";
|
|
1530
|
+
if (completeRes.status === 410 || errorCode === "challenge_expired") {
|
|
1531
|
+
const { message, exitCode } = mapProviderError(completeRes.status, errorCode, parseRetryAfter(completeRes));
|
|
1532
|
+
stderr(`${message}
|
|
1533
|
+
`);
|
|
1534
|
+
exit(exitCode);
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
if (completeRes.status === 403 || completeRes.status >= 400 && completeRes.status < 500 && errorCode !== "provider_error") {
|
|
1538
|
+
const { message, exitCode } = mapProviderError(completeRes.status, errorCode, parseRetryAfter(completeRes));
|
|
1539
|
+
stderr(`${message}
|
|
1540
|
+
`);
|
|
1541
|
+
exit(exitCode);
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
1544
|
+
if (attempt < maxRetries) {
|
|
1545
|
+
stderr(`cleargate: code didn't match. ${maxRetries - attempt} attempt${maxRetries - attempt === 1 ? "" : "s"} remaining.
|
|
1546
|
+
`);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
} finally {
|
|
1550
|
+
rl.close();
|
|
1551
|
+
}
|
|
1552
|
+
if (!succeeded) {
|
|
1553
|
+
stderr(`cleargate: code didn't match after ${maxRetries} tries. Run \`cleargate join <url>\` again to get a new code.
|
|
1554
|
+
`);
|
|
1555
|
+
exit(12);
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
const b = completeRawBody;
|
|
1030
1561
|
if (typeof b.refresh_token !== "string" || typeof b.project_name !== "string") {
|
|
1031
1562
|
stderr("cleargate: server returned unexpected response shape.\n");
|
|
1032
1563
|
exit(7);
|
|
@@ -1049,6 +1580,12 @@ async function joinHandler(opts) {
|
|
|
1049
1580
|
exit(99);
|
|
1050
1581
|
}
|
|
1051
1582
|
}
|
|
1583
|
+
function parseRetryAfter(res) {
|
|
1584
|
+
const hdr = res.headers?.get?.("retry-after");
|
|
1585
|
+
if (!hdr) return void 0;
|
|
1586
|
+
const n = parseInt(hdr, 10);
|
|
1587
|
+
return isNaN(n) ? void 0 : n;
|
|
1588
|
+
}
|
|
1052
1589
|
|
|
1053
1590
|
// src/commands/stamp.ts
|
|
1054
1591
|
init_cjs_shims();
|
|
@@ -2258,13 +2795,13 @@ async function readDriftState(projectRoot) {
|
|
|
2258
2795
|
|
|
2259
2796
|
// src/lib/prompts.ts
|
|
2260
2797
|
init_cjs_shims();
|
|
2261
|
-
var
|
|
2798
|
+
var readline3 = __toESM(require("readline"), 1);
|
|
2262
2799
|
async function promptYesNo(question, defaultYes, opts) {
|
|
2263
2800
|
const stdoutFn = opts?.stdout ?? ((s) => process.stdout.write(s));
|
|
2264
2801
|
stdoutFn(question + "\n");
|
|
2265
2802
|
const inputStream = opts?.stdin ?? process.stdin;
|
|
2266
2803
|
return new Promise((resolve14) => {
|
|
2267
|
-
const rl =
|
|
2804
|
+
const rl = readline3.createInterface({
|
|
2268
2805
|
input: inputStream,
|
|
2269
2806
|
output: void 0,
|
|
2270
2807
|
// we handle output ourselves
|
|
@@ -2295,7 +2832,7 @@ async function promptEmail(question, defaultValue, opts) {
|
|
|
2295
2832
|
stdoutFn(question + "\n");
|
|
2296
2833
|
const inputStream = opts?.stdin ?? process.stdin;
|
|
2297
2834
|
return new Promise((resolve14) => {
|
|
2298
|
-
const rl =
|
|
2835
|
+
const rl = readline3.createInterface({
|
|
2299
2836
|
input: inputStream,
|
|
2300
2837
|
output: void 0,
|
|
2301
2838
|
// we handle output ourselves
|
|
@@ -3654,7 +4191,7 @@ ${row}
|
|
|
3654
4191
|
init_cjs_shims();
|
|
3655
4192
|
var fs21 = __toESM(require("fs"), 1);
|
|
3656
4193
|
var path23 = __toESM(require("path"), 1);
|
|
3657
|
-
var
|
|
4194
|
+
var readline4 = __toESM(require("readline"), 1);
|
|
3658
4195
|
var TERMINAL = /* @__PURE__ */ new Set(["Completed", "Done", "Abandoned", "Closed", "Resolved"]);
|
|
3659
4196
|
async function wikiAuditStatusHandler(opts = {}) {
|
|
3660
4197
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -3793,7 +4330,7 @@ async function wikiAuditStatusHandler(opts = {}) {
|
|
|
3793
4330
|
answer = await opts.promptReader();
|
|
3794
4331
|
} else {
|
|
3795
4332
|
answer = await new Promise((resolve14) => {
|
|
3796
|
-
const rl =
|
|
4333
|
+
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
3797
4334
|
rl.question(`apply ${fixable.length} changes? [y/N] `, (ans) => {
|
|
3798
4335
|
rl.close();
|
|
3799
4336
|
resolve14(ans);
|
|
@@ -8507,9 +9044,6 @@ var fs35 = __toESM(require("fs"), 1);
|
|
|
8507
9044
|
var path46 = __toESM(require("path"), 1);
|
|
8508
9045
|
var os7 = __toESM(require("os"), 1);
|
|
8509
9046
|
var DEFAULT_MCP_URL = "http://localhost:3000";
|
|
8510
|
-
function sleep(ms) {
|
|
8511
|
-
return new Promise((resolve14) => setTimeout(resolve14, ms));
|
|
8512
|
-
}
|
|
8513
9047
|
function resolveMcpUrl(mcpUrlFlag, env) {
|
|
8514
9048
|
return (mcpUrlFlag ?? (env ?? process.env)["CLEARGATE_MCP_URL"] ?? DEFAULT_MCP_URL).replace(/\/$/, "");
|
|
8515
9049
|
}
|
|
@@ -8530,7 +9064,6 @@ async function adminLoginHandler(opts = {}) {
|
|
|
8530
9064
|
const stdout = opts.stdout ?? ((msg) => process.stdout.write(msg + "\n"));
|
|
8531
9065
|
const stderr = opts.stderr ?? ((msg) => process.stderr.write(msg + "\n"));
|
|
8532
9066
|
const exitFn = opts.exit ?? ((code) => process.exit(code));
|
|
8533
|
-
const sleepFn = opts.sleepFn ?? sleep;
|
|
8534
9067
|
const mcpBase = resolveMcpUrl(opts.mcpUrl, opts.env);
|
|
8535
9068
|
let startData;
|
|
8536
9069
|
try {
|
|
@@ -8557,79 +9090,95 @@ async function adminLoginHandler(opts = {}) {
|
|
|
8557
9090
|
stdout(` Code: ${startData.user_code}`);
|
|
8558
9091
|
stdout(` (Code expires in ${Math.floor(startData.expires_in / 60)} minutes)`);
|
|
8559
9092
|
stdout("Waiting for authorization...");
|
|
8560
|
-
let
|
|
8561
|
-
const
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
body
|
|
8572
|
-
|
|
8573
|
-
|
|
8574
|
-
stderr(`cleargate: error: network error while polling (${err instanceof Error ? err.message : String(err)})`);
|
|
8575
|
-
return exitFn(3);
|
|
8576
|
-
}
|
|
8577
|
-
if (pollRes.status === 403) {
|
|
8578
|
-
const body = await pollRes.json().catch(() => ({}));
|
|
8579
|
-
if (body.error === "access_denied") {
|
|
8580
|
-
stderr("cleargate: error: access denied \u2014 you declined authorization in the browser.");
|
|
8581
|
-
return exitFn(5);
|
|
8582
|
-
}
|
|
8583
|
-
stderr("cleargate: error: your GitHub account is not authorized as an admin user.");
|
|
8584
|
-
return exitFn(4);
|
|
8585
|
-
}
|
|
8586
|
-
if (pollRes.status === 410) {
|
|
8587
|
-
stderr("cleargate: error: device code expired \u2014 please run `cleargate admin login` again.");
|
|
8588
|
-
return exitFn(5);
|
|
8589
|
-
}
|
|
8590
|
-
if (!pollRes.ok) {
|
|
8591
|
-
const body = await pollRes.json().catch(() => ({}));
|
|
8592
|
-
stderr(`cleargate: error: unexpected server response ${pollRes.status}: ${body.error ?? "unknown"}`);
|
|
8593
|
-
return exitFn(6);
|
|
8594
|
-
}
|
|
8595
|
-
const pollBody = await pollRes.json();
|
|
8596
|
-
if (pollBody.pending) {
|
|
8597
|
-
const shouldApplyBump = opts.sleepFn !== void 0 || opts.intervalOverrideMs === void 0;
|
|
8598
|
-
if (shouldApplyBump && pollBody.retry_after !== void 0) {
|
|
8599
|
-
const bumped = pollBody.retry_after * 1e3;
|
|
8600
|
-
if (bumped > currentInterval) {
|
|
8601
|
-
currentInterval = bumped;
|
|
9093
|
+
let capturedSuccessBody = null;
|
|
9094
|
+
const fetchPollCapture = async (deviceCode) => {
|
|
9095
|
+
const res = await fetchFn(`${mcpBase}/admin-api/v1/auth/device/poll`, {
|
|
9096
|
+
method: "POST",
|
|
9097
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
9098
|
+
body: JSON.stringify({ device_code: deviceCode })
|
|
9099
|
+
});
|
|
9100
|
+
const originalJson = res.json.bind(res);
|
|
9101
|
+
return {
|
|
9102
|
+
status: res.status,
|
|
9103
|
+
json: async () => {
|
|
9104
|
+
const body = await originalJson();
|
|
9105
|
+
if (body["pending"] === false) {
|
|
9106
|
+
capturedSuccessBody = body;
|
|
8602
9107
|
}
|
|
9108
|
+
return body;
|
|
9109
|
+
}
|
|
9110
|
+
};
|
|
9111
|
+
};
|
|
9112
|
+
try {
|
|
9113
|
+
await startDeviceFlow({
|
|
9114
|
+
deviceCode: startData.device_code,
|
|
9115
|
+
interval: startData.interval,
|
|
9116
|
+
expiresIn: startData.expires_in,
|
|
9117
|
+
fetchPoll: fetchPollCapture,
|
|
9118
|
+
// Only pass sleepFn if the caller explicitly injected one (test seam).
|
|
9119
|
+
// When sleepFn is omitted, startDeviceFlow uses its own defaultSleep.
|
|
9120
|
+
// This preserves the original bump-suppression logic:
|
|
9121
|
+
// shouldApplyBump = (sleepFn provided) || (intervalOverrideMs not set).
|
|
9122
|
+
...opts.sleepFn !== void 0 ? { sleepFn: opts.sleepFn } : {},
|
|
9123
|
+
...opts.intervalOverrideMs !== void 0 ? { intervalOverrideMs: opts.intervalOverrideMs } : {},
|
|
9124
|
+
deadlineGraceMs: 1e4
|
|
9125
|
+
});
|
|
9126
|
+
} catch (err) {
|
|
9127
|
+
if (err instanceof DeviceFlowError) {
|
|
9128
|
+
switch (err.code) {
|
|
9129
|
+
case "access_denied":
|
|
9130
|
+
stderr("cleargate: error: access denied \u2014 you declined authorization in the browser.");
|
|
9131
|
+
return exitFn(5);
|
|
9132
|
+
case "not_admin":
|
|
9133
|
+
stderr("cleargate: error: your GitHub account is not authorized as an admin user.");
|
|
9134
|
+
return exitFn(4);
|
|
9135
|
+
case "expired_token":
|
|
9136
|
+
stderr("cleargate: error: device code expired \u2014 please run `cleargate admin login` again.");
|
|
9137
|
+
return exitFn(5);
|
|
9138
|
+
case "timeout":
|
|
9139
|
+
stderr("cleargate: error: timed out waiting for authorization. Please try again.");
|
|
9140
|
+
return exitFn(5);
|
|
9141
|
+
case "unreachable":
|
|
9142
|
+
stderr(`cleargate: error: network error while polling`);
|
|
9143
|
+
return exitFn(3);
|
|
9144
|
+
default:
|
|
9145
|
+
stderr(`cleargate: error: unexpected server response`);
|
|
9146
|
+
return exitFn(6);
|
|
8603
9147
|
}
|
|
8604
|
-
continue;
|
|
8605
9148
|
}
|
|
8606
|
-
|
|
8607
|
-
|
|
9149
|
+
stderr(`cleargate: error: unexpected error during device flow`);
|
|
9150
|
+
return exitFn(6);
|
|
8608
9151
|
}
|
|
8609
|
-
if (!
|
|
9152
|
+
if (!capturedSuccessBody) {
|
|
8610
9153
|
stderr("cleargate: error: timed out waiting for authorization. Please try again.");
|
|
8611
9154
|
return exitFn(5);
|
|
8612
9155
|
}
|
|
9156
|
+
const successBody = capturedSuccessBody;
|
|
8613
9157
|
const authFilePath = resolveAuthFilePath(opts);
|
|
8614
9158
|
try {
|
|
8615
|
-
writeAdminAuth(authFilePath,
|
|
9159
|
+
writeAdminAuth(authFilePath, successBody.admin_token);
|
|
8616
9160
|
} catch (err) {
|
|
8617
9161
|
stderr(`cleargate: error: failed to write ${authFilePath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
8618
9162
|
return exitFn(99);
|
|
8619
9163
|
}
|
|
8620
|
-
stdout(`Logged in successfully. Token expires ${
|
|
9164
|
+
stdout(`Logged in successfully. Token expires ${successBody.expires_at}.`);
|
|
8621
9165
|
stdout(`Credentials saved to ${authFilePath} (chmod 600).`);
|
|
8622
9166
|
}
|
|
8623
9167
|
|
|
8624
9168
|
// src/cli.ts
|
|
8625
9169
|
var program = new import_commander.Command();
|
|
8626
9170
|
program.name("cleargate").description("ClearGate CLI \u2014 connects AI agent teams to the ClearGate MCP server").version(package_default.version, "-V, --version").option("--profile <name>", "configuration profile to use", "default").option("--mcp-url <url>", "MCP server URL (overrides config file and env)").showHelpAfterError("(use `cleargate --help`)");
|
|
8627
|
-
program.command("join <invite-url>").description("join a ClearGate workspace using an invite URL").action(async (inviteUrl, _opts, command) => {
|
|
9171
|
+
program.command("join <invite-url>").description("join a ClearGate workspace using an invite URL").option("--auth <provider>", "identity provider: github | email").option("--non-interactive", "fail instead of prompting (CI mode)").option("--code <code>", "OTP code for non-interactive email auth").action(async (inviteUrl, _opts, command) => {
|
|
8628
9172
|
const globals = command.parent.opts();
|
|
9173
|
+
const cmdOpts = command.opts();
|
|
8629
9174
|
await joinHandler({
|
|
8630
9175
|
inviteUrl,
|
|
8631
9176
|
profile: globals.profile,
|
|
8632
|
-
mcpUrlFlag: globals.mcpUrl
|
|
9177
|
+
mcpUrlFlag: globals.mcpUrl,
|
|
9178
|
+
// FLASHCARD #cli #commander #optional-key: only set keys when defined
|
|
9179
|
+
...cmdOpts.auth !== void 0 ? { auth: cmdOpts.auth } : {},
|
|
9180
|
+
...cmdOpts.nonInteractive === true ? { nonInteractive: true } : {},
|
|
9181
|
+
...cmdOpts.code !== void 0 ? { code: cmdOpts.code } : {}
|
|
8633
9182
|
});
|
|
8634
9183
|
});
|
|
8635
9184
|
program.command("init").description("initialise a repo with ClearGate scaffold (CLAUDE.md block, hook config, agents, templates)").option("--force", "overwrite existing files that differ from the bundled payload").option("--yes", "non-interactive: accept all defaults without prompting").action(async (opts) => {
|