turnzero 0.23.0-next.1 → 0.24.0-next.1

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 CHANGED
@@ -25,6 +25,7 @@ Install it once with `npm install -g turnzero`, then run `turnzero login` once i
25
25
  - `login` logs this computer in through your browser, `whoami` names the account its token acts as, and `logout` revokes its token.
26
26
  - `authorize` gives this computer a token through your signed-in tool, which then tells you it did.
27
27
  - `token request` and `token claim` get a token for another computer or for an unattended run.
28
+ - `session` signs a test end user of your application in and writes the session's cookie into a file that curl reads.
28
29
 
29
30
  Every subcommand's options, statuses, and output are on the reference page: https://turnzero.ai/cloud/reference/turnzero-command/
30
31
 
@@ -258,6 +258,15 @@ var AUTHORIZED_COMPUTER_MEMBER = "authorized_computer";
258
258
  var AUTHORIZE_SUBCOMMAND = "authorize";
259
259
  var LOGOUT_SUBCOMMAND = "logout";
260
260
  var WHOAMI_SUBCOMMAND = "whoami";
261
+ var SESSION_SUBCOMMAND = "session";
262
+ var SESSION_OPTION = {
263
+ application: "--application",
264
+ environment: "--environment",
265
+ endUser: "--end-user",
266
+ out: "--out",
267
+ origin: "--origin",
268
+ force: "--force"
269
+ };
261
270
  var TOKEN_SUBCOMMAND = "token";
262
271
  var TOKEN_REQUEST_ACT = "request";
263
272
  var TOKEN_CLAIM_ACT = "claim";
@@ -767,9 +776,9 @@ var readMovingCredentialFile = (io2, place, say) => {
767
776
  // src/turnzero_cloud_command/grant.ts
768
777
  var GRANT_READ_BOUND_MS = 1e4;
769
778
  var GRANT_INPUT_BYTES_MAX = 4096;
770
- var readStandardInputEnded = (input, boundMs = GRANT_READ_BOUND_MS) => new Promise((resolve12) => {
779
+ var readStandardInputEnded = (input, boundMs = GRANT_READ_BOUND_MS) => new Promise((resolve13) => {
771
780
  if (input.isTTY) {
772
- resolve12({ text: "", bytes: 0, ending: "terminal" });
781
+ resolve13({ text: "", bytes: 0, ending: "terminal" });
773
782
  return;
774
783
  }
775
784
  const chunks = [];
@@ -791,7 +800,7 @@ var readStandardInputEnded = (input, boundMs = GRANT_READ_BOUND_MS) => new Promi
791
800
  input.removeListener("close", onEnd);
792
801
  input.pause();
793
802
  input.destroy?.();
794
- resolve12({ text: Buffer.concat(chunks).toString("utf8"), bytes, ending });
803
+ resolve13({ text: Buffer.concat(chunks).toString("utf8"), bytes, ending });
795
804
  };
796
805
  const onEnd = () => finish("end");
797
806
  const onError = () => finish("error");
@@ -3233,7 +3242,7 @@ var headCommitOf = (folder, reads = FILE_SYSTEM_READS) => {
3233
3242
 
3234
3243
  // src/turnzero_cloud_command/options.ts
3235
3244
  var afterOwnErrorOf = (row, call, io2) => typeof row.afterOwnError === "function" ? row.afterOwnError(call, io2) : row.afterOwnError;
3236
- var PATH_OPTION_NAMES = Object.freeze([DEPLOY_OPTION.path, SECRET_OPTION.valueFile, PROVISION_OPTION.envFile]);
3245
+ var PATH_OPTION_NAMES = Object.freeze([DEPLOY_OPTION.path, SECRET_OPTION.valueFile, PROVISION_OPTION.envFile, SESSION_OPTION.out]);
3237
3246
  var DEPLOY_OPTIONS = Object.freeze([
3238
3247
  {
3239
3248
  name: DEPLOY_OPTION.application,
@@ -4303,7 +4312,7 @@ var noTerminalSentence = (option, call) => `${option} reads the value from a ter
4303
4312
  var readPromptedValue = (io2, option, fileOption, call) => {
4304
4313
  const terminal = io2.terminal();
4305
4314
  if (terminal === null) return Promise.resolve({ refusal: noTerminalSentence(option, call) });
4306
- return new Promise((resolve12) => {
4315
+ return new Promise((resolve13) => {
4307
4316
  let typed = "";
4308
4317
  let done = false;
4309
4318
  let cancel = () => void 0;
@@ -4313,7 +4322,7 @@ var readPromptedValue = (io2, option, fileOption, call) => {
4313
4322
  cancel();
4314
4323
  terminal.write("\n");
4315
4324
  terminal.close();
4316
- resolve12(read2);
4325
+ resolve13(read2);
4317
4326
  };
4318
4327
  cancel = io2.schedule(
4319
4328
  PROMPT_BOUND_SECONDS * 1e3,
@@ -5650,8 +5659,8 @@ var openerRunOf = (io2, address) => {
5650
5659
  };
5651
5660
  var openBrowser = async (io2, address) => {
5652
5661
  let cancel = () => void 0;
5653
- const waited = new Promise((resolve12) => {
5654
- cancel = io2.schedule(OPENER_WAIT_MS, () => resolve12("waiting"));
5662
+ const waited = new Promise((resolve13) => {
5663
+ cancel = io2.schedule(OPENER_WAIT_MS, () => resolve13("waiting"));
5655
5664
  });
5656
5665
  const ended = await Promise.race([io2.child(openerRunOf(io2, address)), waited]);
5657
5666
  cancel();
@@ -5731,13 +5740,13 @@ async function login(call, io2) {
5731
5740
  const endMs = Math.max(1e3, Date.parse(pending.expires_at) - io2.clock().getTime());
5732
5741
  const minutes = minutesLeftOf(pending.expires_at, io2.clock());
5733
5742
  let settle = () => void 0;
5734
- const arrived = new Promise((resolve12) => {
5735
- settle = resolve12;
5743
+ const arrived = new Promise((resolve13) => {
5744
+ settle = resolve13;
5736
5745
  });
5737
5746
  let taken = false;
5738
5747
  let outcome = () => void 0;
5739
- const outcomeKnown = new Promise((resolve12) => {
5740
- outcome = resolve12;
5748
+ const outcomeKnown = new Promise((resolve13) => {
5749
+ outcome = resolve13;
5741
5750
  });
5742
5751
  const take = (arrival2) => {
5743
5752
  if (taken) return false;
@@ -8973,6 +8982,326 @@ var SCAFFOLD_ROW = Object.freeze({
8973
8982
  run: scaffold
8974
8983
  });
8975
8984
 
8985
+ // src/turnzero_cloud_command/session.ts
8986
+ import { dirname as dirname7, isAbsolute as isAbsolute6, join as join11, relative as relative7, resolve as resolve12, sep as sep4 } from "node:path";
8987
+ var SIGN_IN_TEST_END_USER_ACTION = "sign_in_test_end_user";
8988
+ var SESSION_DELIVERY = "command";
8989
+ var NOTHING_SENT_OR_WRITTEN = "Nothing was sent and nothing was written.";
8990
+ var SESSION_ACT = "sign-in";
8991
+ var SESSION_CALL_BOUND_SECONDS = 60;
8992
+ var REDACTED_SESSION_COOKIE = "<session cookie>";
8993
+ var COOKIE_JAR_HEADER = "# Netscape HTTP Cookie File";
8994
+ var HTTP_ONLY_PREFIX = "#HttpOnly_";
8995
+ var SUGGESTED_JAR_NAME = "turnzero-session-cookies.txt";
8996
+ var SESSION_LINE_HEAD = "session: ";
8997
+ var SESSION_UNKNOWN_AGAIN = "Whether the end user was signed in is unknown, and nothing was written: run the line again.";
8998
+ var SESSION_RATE_CAPPED = "Nothing was written. Run the line again after the wait the answer names.";
8999
+ var SESSION_SIGNAL_ENDED = "The run was ended before the cookie jar was written, so nothing was written: run the line again.";
9000
+ var SESSION_AFTER_LOGIN = `Once login has printed authorized:, run this session line again. ${NOTHING_SENT_OR_WRITTEN}`;
9001
+ var SESSION_OPTIONS = Object.freeze([
9002
+ {
9003
+ name: SESSION_OPTION.application,
9004
+ value: "<application id>",
9005
+ required: true,
9006
+ description: `The application whose test end user signs in, by its id, as the line a ${SIGN_IN_TEST_END_USER_ACTION} call from your tool names it.`
9007
+ },
9008
+ {
9009
+ name: SESSION_OPTION.environment,
9010
+ value: "<environment>",
9011
+ required: true,
9012
+ description: `The environment the end user signs in to, ${COMMAND_ENVIRONMENTS.join(" or ")}.`
9013
+ },
9014
+ {
9015
+ name: SESSION_OPTION.endUser,
9016
+ value: "<end user id or label>",
9017
+ required: true,
9018
+ description: "The test end user, by the id or the label create_test_end_users answered."
9019
+ },
9020
+ {
9021
+ name: SESSION_OPTION.out,
9022
+ value: "<path>",
9023
+ required: true,
9024
+ description: `The cookie jar file the session's cookie is written into, which curl -b reads. Its folder must stand. A file that stands there is refused unless the line names ${SESSION_OPTION.force}, and a path inside the folder the command runs in is refused where that folder holds package.json, since a deploy from it would upload the jar.`
9025
+ },
9026
+ {
9027
+ name: SESSION_OPTION.origin,
9028
+ value: "<origin>",
9029
+ required: false,
9030
+ description: `The platform's address. Where absent, the address ${ORIGIN_VARIABLE} holds, or ${DEFAULT_COMMAND_ORIGIN} where it is unset. Under ${MINTED_TOKEN_VARIABLE} the token is sent only to the address ${ORIGIN_VARIABLE} holds, or to ${DEFAULT_COMMAND_ORIGIN} where it is unset. Otherwise it names the address whose token the command reads from this computer's credential file, and that token is sent to that address alone.`
9031
+ },
9032
+ {
9033
+ name: SESSION_OPTION.force,
9034
+ value: "",
9035
+ required: false,
9036
+ flag: true,
9037
+ description: `Replaces the file ${SESSION_OPTION.out} names where one stands.`
9038
+ }
9039
+ ]);
9040
+ var SESSION_CREDENTIAL = Object.freeze({
9041
+ grant: "minted token",
9042
+ aGrant: "a minted token",
9043
+ call: `the ${SIGN_IN_TEST_END_USER_ACTION} call`,
9044
+ mintedToken: true,
9045
+ piped: false,
9046
+ places: `the command reads no standard input: it reads a minted token from ${MINTED_TOKEN_VARIABLE} where it is set, as a tool connected by a minted token sets it from where its token is kept, or a job with no tool from its secret store, and otherwise this computer's token for the platform's address from its credential file, which ${LOGIN_SUBCOMMAND} and ${AUTHORIZE_SUBCOMMAND} write.`
9047
+ });
9048
+ var SESSION_READS = `no standard input: a minted token from ${MINTED_TOKEN_VARIABLE} where it is set, and otherwise this computer's token for the platform's address from its credential file, which ${LOGIN_SUBCOMMAND} and ${AUTHORIZE_SUBCOMMAND} write, sent only to that address; with neither, it prints the variable route first and then, where the address is admitted, the login route`;
9049
+ var missingOptionSentence = (option) => `${SESSION_SUBCOMMAND} needs ${option.name} ${option.value}, as the line a ${SIGN_IN_TEST_END_USER_ACTION} call answers names it. ${NOTHING_SENT_OR_WRITTEN}`;
9050
+ var ENVIRONMENT_REFUSAL = `${SESSION_OPTION.environment} names the environment the end user signs in to, ${COMMAND_ENVIRONMENTS.join(" or ")}. ${NOTHING_SENT_OR_WRITTEN}`;
9051
+ var OUT_STANDS_REFUSAL = `A file stands where ${SESSION_OPTION.out} names the cookie jar, and ${SESSION_SUBCOMMAND} replaces one only where the line names ${SESSION_OPTION.force}. Name a new file, or add ${SESSION_OPTION.force} to replace it. ${NOTHING_SENT_OR_WRITTEN}`;
9052
+ var OUT_FOLDER_REFUSAL = `${SESSION_OPTION.out} names a folder: name a file for the cookie jar, in that folder or another. ${NOTHING_SENT_OR_WRITTEN}`;
9053
+ var OUT_PARENT_REFUSAL = `The folder ${SESSION_OPTION.out} names the cookie jar in does not stand: make it, or name a file in a folder that stands. ${NOTHING_SENT_OR_WRITTEN}`;
9054
+ var outInsideApplicationRefusal = (outside2) => `${SESSION_OPTION.out} names a file inside the folder the command runs in, which holds package.json, so a ${DEPLOY_SUBCOMMAND} from that folder would upload the session's cookie with the application. Name a file outside that folder${outside2 !== null ? `, such as ${outside2}` : ""}. ${NOTHING_SENT_OR_WRITTEN}`;
9055
+ var jarUntriedRefusal = (code) => `The cookie jar could not be written (${code}): the folder ${SESSION_OPTION.out} names a file in must admit a write. ${NOTHING_SENT_OR_WRITTEN}`;
9056
+ var jarUnwrittenSentence = (code) => `The end user was signed in, and the cookie jar could not be written (${code}), so the session's cookie was not kept: run the line again.`;
9057
+ var SESSION_NO_ANSWER = "The sign-in call drew no answer.";
9058
+ var sessionRedirected = (status) => {
9059
+ const how = answeredHow(status);
9060
+ return `The sign-in call was answered ${status >= 100 ? `${how}, a redirect` : how}, which the command never follows, so the credential was sent to no second address.`;
9061
+ };
9062
+ var sessionAnswerUnusable = (status) => `The sign-in call was answered HTTP ${status} with no answer the command can use: status signed_in, hostname, end_user, expires_at, and cookie, each in its form. Nothing of the answer was printed or written.`;
9063
+ var insideFolder = (folder, path) => {
9064
+ const between = relative7(folder, path);
9065
+ return between === "" || between !== ".." && !between.startsWith(`..${sep4}`) && !isAbsolute6(between);
9066
+ };
9067
+ var sessionOptionsOf = (call, cwd) => {
9068
+ for (const option of SESSION_OPTIONS) {
9069
+ if (option.required && (call.named[option.name] ?? "").trim() === "") return { refusal: missingOptionSentence(option) };
9070
+ }
9071
+ const environment = call.named[SESSION_OPTION.environment];
9072
+ if (!COMMAND_ENVIRONMENTS.includes(environment)) return { refusal: ENVIRONMENT_REFUSAL };
9073
+ const origin = commandOriginOf(call.named[SESSION_OPTION.origin] ?? DEFAULT_COMMAND_ORIGIN);
9074
+ if ("refusal" in origin) return origin;
9075
+ const outGiven = call.named[SESSION_OPTION.out];
9076
+ return {
9077
+ application: call.named[SESSION_OPTION.application],
9078
+ environment,
9079
+ endUser: call.named[SESSION_OPTION.endUser],
9080
+ outGiven,
9081
+ out: resolve12(cwd, outGiven),
9082
+ force: call.flags.has(SESSION_OPTION.force),
9083
+ origin: origin.origin
9084
+ };
9085
+ };
9086
+ var outRefusalOf = (io2, options) => {
9087
+ const standing = io2.stat(options.out);
9088
+ if (standing !== null && standing.folder) return OUT_FOLDER_REFUSAL;
9089
+ if (standing !== null && !options.force) return OUT_STANDS_REFUSAL;
9090
+ const cwd = resolve12(io2.cwd);
9091
+ const manifest = io2.stat(join11(cwd, "package.json"));
9092
+ if (manifest !== null && !manifest.folder && insideFolder(cwd, options.out)) {
9093
+ const above = dirname7(cwd);
9094
+ return outInsideApplicationRefusal(above !== cwd ? join11(above, SUGGESTED_JAR_NAME) : null);
9095
+ }
9096
+ const folder = io2.stat(dirname7(options.out));
9097
+ if (folder === null || !folder.folder) return OUT_PARENT_REFUSAL;
9098
+ return null;
9099
+ };
9100
+ var HOSTNAME_FORM = /^[A-Za-z0-9](?:[A-Za-z0-9.-]{0,251}[A-Za-z0-9])?$/;
9101
+ var COOKIE_NAME_FORM = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
9102
+ var COOKIE_VALUE_FORM = /^[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]+$/;
9103
+ var COOKIE_PATH_FORM = /^\/[\x21-\x3a\x3c-\x7e]*$/;
9104
+ var UTC_INSTANT_FORM = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/;
9105
+ var shownText = (value) => typeof value === "string" && value !== "" && value.length <= 256 && oneLine(value) === value;
9106
+ var sessionAnswerOf = (answer) => {
9107
+ const { status, hostname: hostname3, end_user: endUser, expires_at: expiresAt, cookie } = answer;
9108
+ if (status !== "signed_in") return null;
9109
+ if (typeof hostname3 !== "string" || !HOSTNAME_FORM.test(hostname3)) return null;
9110
+ if (endUser === null || typeof endUser !== "object" || Array.isArray(endUser)) return null;
9111
+ const { id, label } = endUser;
9112
+ if (!shownText(id) || label !== null && label !== void 0 && !shownText(label)) return null;
9113
+ if (typeof expiresAt !== "string" || !UTC_INSTANT_FORM.test(expiresAt) || !Number.isFinite(Date.parse(expiresAt))) return null;
9114
+ if (cookie === null || typeof cookie !== "object" || Array.isArray(cookie)) return null;
9115
+ const { name, value, path, secure, http_only: httpOnly } = cookie;
9116
+ if (typeof name !== "string" || !COOKIE_NAME_FORM.test(name)) return null;
9117
+ if (typeof value !== "string" || !COOKIE_VALUE_FORM.test(value)) return null;
9118
+ if (typeof path !== "string" || !COOKIE_PATH_FORM.test(path)) return null;
9119
+ if (typeof secure !== "boolean" || typeof httpOnly !== "boolean") return null;
9120
+ return { hostname: hostname3, endUser: { id, label: typeof label === "string" ? label : null }, expiresAt, cookie: { name, value, path, secure, httpOnly } };
9121
+ };
9122
+ var cookieJarTextOf = (session2) => `${COOKIE_JAR_HEADER}
9123
+ ${[
9124
+ `${session2.cookie.httpOnly ? HTTP_ONLY_PREFIX : ""}${session2.hostname}`,
9125
+ "FALSE",
9126
+ session2.cookie.path,
9127
+ session2.cookie.secure ? "TRUE" : "FALSE",
9128
+ String(Math.floor(Date.parse(session2.expiresAt) / 1e3)),
9129
+ session2.cookie.name,
9130
+ session2.cookie.value
9131
+ ].join(" ")}
9132
+ `;
9133
+ var sessionLineOf = (session2, outGiven) => `${SESSION_LINE_HEAD}${session2.hostname} for ${oneLine(session2.endUser.label ?? session2.endUser.id)} until ${session2.expiresAt}; cookie ${session2.cookie.name} written to ${outGiven} (curl -b ${outGiven})`;
9134
+ var hideCookieOf = (io2, answer) => {
9135
+ const cookie = answer?.cookie;
9136
+ if (cookie === null || typeof cookie !== "object" || Array.isArray(cookie)) return;
9137
+ const value = cookie.value;
9138
+ if (typeof value === "string" && value !== "") io2.hide(value, REDACTED_SESSION_COOKIE);
9139
+ };
9140
+ var postSignIn = async (io2, origin, bearer, userAgent, body) => {
9141
+ const controller = new AbortController();
9142
+ const cancel = io2.schedule(SESSION_CALL_BOUND_SECONDS * 1e3, () => controller.abort());
9143
+ try {
9144
+ const response = await io2.fetch(`${origin}${actionPathOf(SIGN_IN_TEST_END_USER_ACTION)}`, {
9145
+ method: "POST",
9146
+ headers: { authorization: `Bearer ${bearer}`, "user-agent": userAgent, "content-type": "application/json" },
9147
+ body: JSON.stringify(body),
9148
+ redirect: "manual",
9149
+ signal: controller.signal
9150
+ });
9151
+ return { status: response.status, redirected: redirected(response), text: await response.text() };
9152
+ } catch {
9153
+ return null;
9154
+ } finally {
9155
+ cancel();
9156
+ }
9157
+ };
9158
+ async function callSignIn(io2, options, origin, bearer, userAgent, refused) {
9159
+ const unknown = { status: COMMAND_STATUS.unknown };
9160
+ const answered = await postSignIn(io2, origin, bearer, userAgent, {
9161
+ application: options.application,
9162
+ environment: options.environment,
9163
+ end_user: options.endUser,
9164
+ delivery: SESSION_DELIVERY
9165
+ });
9166
+ if (answered === null) {
9167
+ io2.print(SESSION_NO_ANSWER);
9168
+ io2.print(SESSION_UNKNOWN_AGAIN);
9169
+ return unknown;
9170
+ }
9171
+ const parsed = planeObjectOf(answered.text);
9172
+ hideCookieOf(io2, parsed);
9173
+ if (answered.redirected) {
9174
+ io2.print(sessionRedirected(answered.status));
9175
+ io2.print(SESSION_UNKNOWN_AGAIN);
9176
+ return unknown;
9177
+ }
9178
+ if (answered.status >= PLATFORM_FAILURE_STATUS) {
9179
+ for (const line of failureLines(SESSION_ACT, answered.status, planeRefusalOf(answered.text))) io2.print(line);
9180
+ io2.print(SESSION_UNKNOWN_AGAIN);
9181
+ return unknown;
9182
+ }
9183
+ if (answered.status < 200 || answered.status > 299) {
9184
+ const refusal = markedRefusalOf(answered.status, answered.text);
9185
+ if (refusal === null) {
9186
+ io2.print(notThePlatformLine("sign-in call", answered.status));
9187
+ io2.print(SESSION_UNKNOWN_AGAIN);
9188
+ return unknown;
9189
+ }
9190
+ for (const line of refusalLines(SESSION_ACT, answered.status, refusal)) io2.print(line);
9191
+ if (answered.status === 429 && refusal.error === RATE_CAPPED) {
9192
+ io2.print(SESSION_RATE_CAPPED);
9193
+ } else {
9194
+ const sentence = refused();
9195
+ if (sentence !== null) io2.print(sentence);
9196
+ }
9197
+ return { status: COMMAND_STATUS.failed };
9198
+ }
9199
+ const usable = parsed === null ? null : sessionAnswerOf(parsed);
9200
+ if (usable === null) {
9201
+ io2.print(sessionAnswerUnusable(answered.status));
9202
+ io2.print(SESSION_UNKNOWN_AGAIN);
9203
+ return unknown;
9204
+ }
9205
+ return usable;
9206
+ }
9207
+ async function session(call, io2) {
9208
+ const options = sessionOptionsOf(call, io2.cwd);
9209
+ if ("refusal" in options) {
9210
+ io2.print(options.refusal);
9211
+ return COMMAND_STATUS.nothing_started;
9212
+ }
9213
+ const agent = userAgentOf(io2.env);
9214
+ if ("refusal" in agent) {
9215
+ io2.print(agent.refusal);
9216
+ return COMMAND_STATUS.nothing_started;
9217
+ }
9218
+ const out = outRefusalOf(io2, options);
9219
+ if (out !== null) {
9220
+ io2.print(out);
9221
+ return COMMAND_STATUS.nothing_started;
9222
+ }
9223
+ const named = call.named[SESSION_OPTION.origin];
9224
+ const credential = await readCredential(io2, SESSION_CREDENTIAL, "", { named, closing: NOTHING_SENT_OR_WRITTEN });
9225
+ if ("unauthorized" in credential) {
9226
+ io2.print(noTokenSentence(credential.unauthorized.origin, credential.unauthorized.expiredAt));
9227
+ io2.print(VARIABLE_ROUTE_SENTENCES);
9228
+ const reached = authorizationOriginOf(io2.env, named, NOTHING_SENT_OR_WRITTEN);
9229
+ if ("refusal" in reached) {
9230
+ io2.print(reached.refusal);
9231
+ return COMMAND_STATUS.nothing_started;
9232
+ }
9233
+ const unwritable = loginRouteRefusalOf(io2, NOTHING_SENT_OR_WRITTEN);
9234
+ if (unwritable !== null) {
9235
+ io2.print(unwritable);
9236
+ return COMMAND_STATUS.nothing_started;
9237
+ }
9238
+ for (const line of loginRouteLines(io2, reached.origin, false)) io2.print(line);
9239
+ io2.print(SESSION_AFTER_LOGIN);
9240
+ return COMMAND_STATUS.nothing_started;
9241
+ }
9242
+ if ("refusal" in credential) {
9243
+ io2.print(credential.refusal);
9244
+ return COMMAND_STATUS.nothing_started;
9245
+ }
9246
+ io2.hide(credential.credential, REDACTED_TOKEN);
9247
+ const sent = credentialOriginOf(io2.env, credential, { named, closing: NOTHING_SENT_OR_WRITTEN });
9248
+ if ("refusal" in sent) {
9249
+ io2.print(sent.refusal);
9250
+ return COMMAND_STATUS.nothing_started;
9251
+ }
9252
+ const origin = sent.origin;
9253
+ const refused = () => credential.source === "credential_file" ? fileReadSentence(origin) : variableReadSentence(origin, fileStandingOf(io2, origin, io2.print));
9254
+ const tag = runTagOf();
9255
+ const tried = tryWritingFile(io2, options.out, "owner only", tag);
9256
+ if ("unwritten" in tried) {
9257
+ dropWritingFile(io2, options.out, tag);
9258
+ io2.print(jarUntriedRefusal(tried.unwritten));
9259
+ return COMMAND_STATUS.nothing_started;
9260
+ }
9261
+ const release = io2.whenEnded(() => {
9262
+ io2.print(SESSION_SIGNAL_ENDED);
9263
+ dropWritingFile(io2, options.out, tag);
9264
+ });
9265
+ try {
9266
+ const older = await planeWireRefusal(io2, origin);
9267
+ if (older !== null) {
9268
+ io2.print(older);
9269
+ return COMMAND_STATUS.nothing_started;
9270
+ }
9271
+ const answer = await callSignIn(io2, options, origin, credential.credential, agent.userAgent, refused);
9272
+ if ("status" in answer) return answer.status;
9273
+ const written = writeThenRename(io2, options.out, cookieJarTextOf(answer), "owner only", tag);
9274
+ if ("unwritten" in written) {
9275
+ io2.print(jarUnwrittenSentence(written.unwritten));
9276
+ return COMMAND_STATUS.failed;
9277
+ }
9278
+ io2.print(sessionLineOf(answer, options.outGiven));
9279
+ return COMMAND_STATUS.done;
9280
+ } finally {
9281
+ dropWritingFile(io2, options.out, tag);
9282
+ release();
9283
+ }
9284
+ }
9285
+ var SESSION_ROW = Object.freeze({
9286
+ name: SESSION_SUBCOMMAND,
9287
+ summary: `Signs a test end user in and writes the session's cookie into a file curl -b reads, through one ${SIGN_IN_TEST_END_USER_ACTION} call under the credential ${DEPLOY_SUBCOMMAND} reads. It prints one line naming the hostname, the end user, the expiry, and the file, never the cookie's value. With no credential, it sends nothing and prints the two routes to one.`,
9288
+ options: SESSION_OPTIONS,
9289
+ credential: SESSION_CREDENTIAL,
9290
+ reads: SESSION_READS,
9291
+ statuses: {
9292
+ done: `the end user was signed in and the session's cookie written to the file ${SESSION_OPTION.out} names`,
9293
+ failed: "the platform refused the sign-in, the printed lines naming the platform's error and detail, or the end user was signed in and the cookie jar could not be written, so the session's cookie was not kept",
9294
+ unknown: "the sign-in call drew no answer, was redirected, failed, or drew an answer that is neither a refusal nor a session, and nothing was written",
9295
+ nothing_started: `nothing was sent, or nothing was written: an option, the file ${SESSION_OPTION.out} names, or the credential was refused, no credential was read and the two routes to one were printed, the platform was too old, or the jar's folder admitted no write`
9296
+ },
9297
+ note: [
9298
+ `A session line is the one your tool's ${SIGN_IN_TEST_END_USER_ACTION} call answers where the platform serves that action, and it carries no credential. Run it once: it signs the test end user in and writes the session's cookie into the file ${SESSION_OPTION.out} names, in the cookie file form curl -b reads, so the session's value never passes through your tool. Requests then carry the cookie with curl -b and that path.`,
9299
+ `${SESSION_SUBCOMMAND} writes the cookie jar under another name beside it, owner-only, and renames it into place, and it removes that file where the run ends before the rename. It writes over a file that stands only where the line names ${SESSION_OPTION.force}. It refuses a path inside the folder the command runs in where that folder holds package.json, since a deploy from it would upload the jar.`
9300
+ ],
9301
+ afterOwnError: `whether the end user was signed in is unknown, and a file ${SESSION_OPTION.out} names may now stand, so run the line again, adding ${SESSION_OPTION.force} where it does`,
9302
+ run: session
9303
+ });
9304
+
8976
9305
  // src/turnzero_cloud_command/token.ts
8977
9306
  var TOKEN_OPTIONS = Object.freeze([
8978
9307
  {
@@ -9050,9 +9379,9 @@ import { chmodSync, closeSync, openSync, readFileSync as readFileSync5, realpath
9050
9379
  import { mkdirSync, readdirSync as readdirSync2, renameSync, rmSync, statSync as statSync4, write as writeAt } from "node:fs";
9051
9380
  import { createServer } from "node:http";
9052
9381
  import { constants as constants2 } from "node:os";
9053
- import { sep as sep4 } from "node:path";
9382
+ import { sep as sep5 } from "node:path";
9054
9383
  import { ReadStream, isatty } from "node:tty";
9055
- var listenLoopback = (answer) => new Promise((resolve12) => {
9384
+ var listenLoopback = (answer) => new Promise((resolve13) => {
9056
9385
  const server = createServer((request, response) => {
9057
9386
  const url = new URL(request.url ?? "/", "http://127.0.0.1");
9058
9387
  const query = {};
@@ -9069,16 +9398,16 @@ var listenLoopback = (answer) => new Promise((resolve12) => {
9069
9398
  }
9070
9399
  );
9071
9400
  });
9072
- server.once("error", () => resolve12(null));
9401
+ server.once("error", () => resolve13(null));
9073
9402
  server.listen(0, "127.0.0.1", () => {
9074
9403
  const address = server.address();
9075
9404
  if (address === null || typeof address === "string") {
9076
9405
  server.close();
9077
- resolve12(null);
9406
+ resolve13(null);
9078
9407
  return;
9079
9408
  }
9080
9409
  let closed = false;
9081
- resolve12({
9410
+ resolve13({
9082
9411
  port: address.port,
9083
9412
  close: () => {
9084
9413
  if (closed) return;
@@ -9090,7 +9419,7 @@ var listenLoopback = (answer) => new Promise((resolve12) => {
9090
9419
  });
9091
9420
  });
9092
9421
  });
9093
- var ranThroughNpxOf = (entry) => entry !== void 0 && entry.includes(`${sep4}_npx${sep4}`);
9422
+ var ranThroughNpxOf = (entry) => entry !== void 0 && entry.includes(`${sep5}_npx${sep5}`);
9094
9423
  var TERMINAL = process.platform === "win32" ? { input: "\\\\.\\CONIN$", output: "\\\\.\\CONOUT$", inputFlags: "r+" } : { input: "/dev/tty", output: "/dev/tty", inputFlags: "r" };
9095
9424
  var terminalFor = (outputIsTerminal, open) => outputIsTerminal ? open() : null;
9096
9425
  var openTerminal = () => {
@@ -9171,7 +9500,7 @@ var holdSignals = (signals, inputIsTerminal, passesOn, passOn, onInterrupt) => {
9171
9500
  for (const [signal, handler] of held) signals.off(signal, handler);
9172
9501
  };
9173
9502
  };
9174
- var childRunnerOf = (signals, inputIsTerminal, passesOn = SIGNALS_PASS_ON, groups = CHILD_GROUPS) => (run) => new Promise((resolve12) => {
9503
+ var childRunnerOf = (signals, inputIsTerminal, passesOn = SIGNALS_PASS_ON, groups = CHILD_GROUPS) => (run) => new Promise((resolve13) => {
9175
9504
  const output = run.output;
9176
9505
  let interrupted = false;
9177
9506
  const group = run.foreground === true && passesOn && groups;
@@ -9188,8 +9517,8 @@ var childRunnerOf = (signals, inputIsTerminal, passesOn = SIGNALS_PASS_ON, group
9188
9517
  child.stdout?.setEncoding("utf8").on("data", (chunk) => output(chunk, "stdout"));
9189
9518
  child.stderr?.setEncoding("utf8").on("data", (chunk) => output(chunk, "stderr"));
9190
9519
  }
9191
- child.once("error", (error) => resolve12({ status: null, signal: null, error: startErrorOf(error) }));
9192
- child.once("close", (status, signal) => resolve12({ status, signal, error: null, ...interrupted ? { interrupted: true } : {} }));
9520
+ child.once("error", (error) => resolve13({ status: null, signal: null, error: startErrorOf(error) }));
9521
+ child.once("close", (status, signal) => resolve13({ status, signal, error: null, ...interrupted ? { interrupted: true } : {} }));
9193
9522
  if (run.foreground) {
9194
9523
  const release = holdSignals(
9195
9524
  signals,
@@ -9214,7 +9543,7 @@ var childRunnerOf = (signals, inputIsTerminal, passesOn = SIGNALS_PASS_ON, group
9214
9543
  child.once("close", release);
9215
9544
  }
9216
9545
  } catch (error) {
9217
- resolve12({ status: null, signal: null, error: startErrorOf(error) });
9546
+ resolve13({ status: null, signal: null, error: startErrorOf(error) });
9218
9547
  }
9219
9548
  });
9220
9549
  var runChild = childRunnerOf(process, () => isatty(0));
@@ -9259,7 +9588,7 @@ var processIo = () => ({
9259
9588
  // hold a subcommand lets go after a pause has seen it (scaffold.ts;
9260
9589
  // PLD-L0-103). A bare timer of no length ends before the loop polls, and
9261
9590
  // the signal would be dropped with the hold.
9262
- sleep: (ms) => new Promise((resolve12) => setTimeout(() => setImmediate(resolve12), ms)),
9591
+ sleep: (ms) => new Promise((resolve13) => setTimeout(() => setImmediate(resolve13), ms)),
9263
9592
  schedule: (ms, fire) => {
9264
9593
  const timer = setTimeout(fire, ms);
9265
9594
  return () => clearTimeout(timer);
@@ -9291,10 +9620,10 @@ var processIo = () => ({
9291
9620
  `);
9292
9621
  }
9293
9622
  });
9294
- var writeWhole = (fd2, chunk) => new Promise((resolve12, reject) => {
9623
+ var writeWhole = (fd2, chunk) => new Promise((resolve13, reject) => {
9295
9624
  const from = (offset) => {
9296
9625
  if (offset >= chunk.length) {
9297
- resolve12();
9626
+ resolve13();
9298
9627
  return;
9299
9628
  }
9300
9629
  writeAt(fd2, chunk, offset, chunk.length - offset, null, (error, written) => error ? reject(error) : from(offset + written));
@@ -9374,7 +9703,8 @@ var SUBCOMMANDS = Object.freeze([
9374
9703
  LOGOUT_ROW,
9375
9704
  TOKEN_ROW,
9376
9705
  LOGIN_ROW,
9377
- WHOAMI_ROW
9706
+ WHOAMI_ROW,
9707
+ SESSION_ROW
9378
9708
  ]);
9379
9709
  var SERVED = LINE_RUNNER;
9380
9710
  var NPX_FORM_SENTENCE = `Where the command is not installed, install it with ${installLineOf(DEFAULT_COMMAND_ORIGIN)}. A person or a job may instead run the same line through npx: write ${commandRunnerOf("latest")} for ${LINE_RUNNER}. A tool where the command is not installed uses deploy_files within its caps, or the deploy skill's setup steps, never the npx line.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turnzero",
3
- "version": "0.23.0-next.1",
3
+ "version": "0.24.0-next.1",
4
4
  "type": "module",
5
5
  "description": "The Turn Zero Cloud command, which an AI coding tool runs on your machine to deploy an application, store a secret, upload a file, download an export, take a library entry, write a local run's settings, start the application locally, and scaffold a new service from a template.",
6
6
  "license": "Apache-2.0",