turnzero 0.17.0 → 0.18.1-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
@@ -22,7 +22,7 @@ Install it once with `npm install -g turnzero`, then run `turnzero login` once i
22
22
  - `provision` gets new development credentials for your application and writes them to its environment file.
23
23
  - `run` starts your application on your computer with that file's settings.
24
24
  - `scaffold` writes a new service into your project from a template, installs it, and runs its tests.
25
- - `login` logs this computer in through your browser, and `logout` revokes its token.
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 tool, where you ask your tool to.
27
27
  - `token request` and `token claim` get a token for another computer or for an unattended run.
28
28
 
@@ -192,12 +192,13 @@ var databaseCredentialNameOf = (application) => `database-${application}`;
192
192
  var LOCAL_RUN_SETTING = Object.freeze({
193
193
  databaseConnection: "APP_DATABASE_URL",
194
194
  databaseConnectionLimit: "APP_DATABASE_CONNECTION_LIMIT",
195
- platformCredential: "TURNZERO_CLOUD_TOKEN",
195
+ platformCredential: "TURNZERO_CLOUD_APP_TOKEN",
196
196
  environment: "APP_ENVIRONMENT",
197
197
  api: "TURNZERO_CLOUD_API",
198
198
  gatewayUrl: "TURNZERO_CLOUD_GATEWAY_URL",
199
199
  application: "TURNZERO_CLOUD_APPLICATION"
200
200
  });
201
+ var RETIRED_PLATFORM_CREDENTIAL_SETTING = "TURNZERO_CLOUD_TOKEN";
201
202
  var LOCAL_RUN_ENVIRONMENT = "local";
202
203
  var LOCAL_RUN_CREDENTIAL_SETTINGS = Object.freeze([LOCAL_RUN_SETTING.platformCredential, LOCAL_RUN_SETTING.databaseConnection]);
203
204
  var ENVIRONMENT_FILE_NAME = ".env";
@@ -247,6 +248,7 @@ var CODE_VERIFIER_MEMBER = "code_verifier";
247
248
  var AUTHORIZED_COMPUTER_MEMBER = "authorized_computer";
248
249
  var AUTHORIZE_SUBCOMMAND = "authorize";
249
250
  var LOGOUT_SUBCOMMAND = "logout";
251
+ var WHOAMI_SUBCOMMAND = "whoami";
250
252
  var TOKEN_SUBCOMMAND = "token";
251
253
  var TOKEN_REQUEST_ACT = "request";
252
254
  var TOKEN_CLAIM_ACT = "claim";
@@ -350,6 +352,13 @@ var settingsOf = (text) => {
350
352
  }
351
353
  return { settings: [...settings] };
352
354
  };
355
+ var settingNameOf = (line) => {
356
+ const opened = line.startsWith(BYTE_ORDER_MARK) ? line.slice(BYTE_ORDER_MARK.length) : line;
357
+ const read2 = opened.endsWith("\r") ? opened.slice(0, -1) : opened;
358
+ if (SKIPPED_LINE.test(read2) || read2.includes("\0")) return null;
359
+ return SETTING_LINE.exec(read2)?.[1] ?? null;
360
+ };
361
+ var withoutReadSetting = (lines, name) => lines.filter((line) => settingNameOf(line) !== name);
353
362
 
354
363
  // src/wire_version.ts
355
364
  var WIRE_VERSION = /^(\d{4}-\d{2}-\d{2})\.(\d+)$/;
@@ -1125,7 +1134,33 @@ var exchangeUnknownSentence = (kind, origin = DEFAULT_COMMAND_ORIGIN, login2 = n
1125
1134
  var noPendingSentence = (origin, kind) => `No request is pending for ${origin} on this computer, so the token code was not presented and nothing was sent: the verifier it needs is kept only on the computer whose ${firstActNamed(kind)} printed its challenge. Run ${firstActNamed(kind, origin)} on this computer, call ${MINT_TOKEN_ACTION} from your tool with the challenge it prints, and run the line it answers.`;
1126
1135
  var markerDisagreesSentence = (kind, origin = DEFAULT_COMMAND_ORIGIN) => kind === "authorized computer" ? `The exchange minted a token that is not an authorized computer's, and ${AUTHORIZE_SUBCOMMAND} keeps only an authorized computer's, so nothing of its value was printed or kept. Call ${MINT_TOKEN_ACTION} from your tool again naming ${AUTHORIZED_COMPUTER_MEMBER}: true, as ${AUTHORIZE_SUBCOMMAND} prints it, and run the line it answers.` : `The exchange minted an authorized computer's token, and ${TOKEN_SUBCOMMAND} ${TOKEN_CLAIM_ACT} prints only a token that is not one, so nothing of its value was printed or kept. Call ${MINT_TOKEN_ACTION} from your tool again without ${AUTHORIZED_COMPUTER_MEMBER}, as ${TOKEN_SUBCOMMAND} ${TOKEN_REQUEST_ACT} prints it, and run the line it answers; to authorize this computer, run ${AUTHORIZE_SUBCOMMAND}${originWordsOf(origin)}.`;
1127
1136
  var mintedRevocationLines = (revocation, minted) => "lines" in revocation ? [...revocation.lines, standsSentence(`The token it minted, ${minted.id},`, minted.id, minted.expiresAt)] : [`The token it minted, ${minted.id}, is revoked.`];
1128
- var authorizedLineOf = (origin, entry) => `${AUTHORIZED_LINE_HEAD}${origin}, label ${entry.label !== null ? JSON.stringify(entry.label) : "none"}, its token expiring ${entry.expires_at !== null ? `at ${entry.expires_at}` : "never"}`;
1137
+ var authorizedLineOf = (origin, entry, account = null) => `${AUTHORIZED_LINE_HEAD}${origin}${account !== null ? ` as ${accountWordsOf(account)}` : ""}, label ${entry.label !== null ? JSON.stringify(entry.label) : "none"}, its token expiring ${entry.expires_at !== null ? `at ${entry.expires_at}` : "never"}`;
1138
+ var READ_ACCOUNT_ACTION = "read_account";
1139
+ var ACCOUNT_READ_ACT = "account read";
1140
+ var accountWordsOf = (account) => account.address !== null ? oneLine(account.address) : `account ${oneLine(account.id)}`;
1141
+ var ACCOUNT_MEMBER_MAX = 320;
1142
+ var accountOf = (answer) => {
1143
+ const account = answer?.account;
1144
+ if (account === null || typeof account !== "object" || Array.isArray(account)) return null;
1145
+ const { id, address } = account;
1146
+ if (typeof id !== "string" || id === "" || id.length > ACCOUNT_MEMBER_MAX) return null;
1147
+ return { id, address: typeof address === "string" && address !== "" && address.length <= ACCOUNT_MEMBER_MAX ? address : null };
1148
+ };
1149
+ async function readAccountUnder(io2, origin, token2, userAgent) {
1150
+ const answered = await postAction(io2, origin, READ_ACCOUNT_ACTION, token2, userAgent, {});
1151
+ if (answered === null) return { lines: [`The ${ACCOUNT_READ_ACT} drew no answer.`], refused: false };
1152
+ if (answered.redirected) return { lines: [`The ${ACCOUNT_READ_ACT} was answered ${answeredHow(answered.status)}, which the command never follows.`], refused: false };
1153
+ if (answered.status >= PLATFORM_FAILURE_STATUS) return { lines: failureLines(ACCOUNT_READ_ACT, answered.status, planeRefusalOf(answered.text)), refused: false };
1154
+ if (answered.status >= 200 && answered.status <= 299) {
1155
+ const account = accountOf(planeObjectOf(answered.text));
1156
+ return account !== null ? { account } : { lines: [`The ${ACCOUNT_READ_ACT} was answered HTTP ${answered.status} with no account the command can read.`], refused: false };
1157
+ }
1158
+ const refusal = planeRefusalOf(answered.text);
1159
+ if (refusal === null) return { lines: [`The ${ACCOUNT_READ_ACT} was answered HTTP ${answered.status}, not by the platform.`], refused: false };
1160
+ return { lines: refusalLines(ACCOUNT_READ_ACT, answered.status, refusal), refused: true };
1161
+ }
1162
+ var whoamiLineFor = (io2, origin) => printedLineOf(io2, commandLineOf(origin, WHOAMI_SUBCOMMAND, origin !== DEFAULT_COMMAND_ORIGIN ? { origin } : {}), origin);
1163
+ var accountUnreadSentence = (whoami2) => `The account this computer's token acts as was not read, and the token is kept: run ${whoami2} to read it.`;
1129
1164
  var CLAIMED_LINE_HEAD = "claimed: ";
1130
1165
  async function completeAct(io2, origin, kind, code, userAgent, say, deliver = () => void 0, login2 = null) {
1131
1166
  const place = credentialFileOf(io2);
@@ -1214,7 +1249,7 @@ async function completeAct(io2, origin, kind, code, userAgent, say, deliver = ()
1214
1249
  return COMMAND_STATUS.failed;
1215
1250
  }
1216
1251
  if (kind === "general") return claimed(io2, place, tag, origin, minted, say, deliver);
1217
- return await kept(io2, place, tag, origin, minted, userAgent, say);
1252
+ return await kept(io2, place, tag, origin, minted, userAgent, say, login2 !== null);
1218
1253
  } finally {
1219
1254
  release();
1220
1255
  dropCredentialWriting(io2, place, tag);
@@ -1229,7 +1264,7 @@ function claimed(io2, place, tag, origin, minted, say, deliver) {
1229
1264
  if (!("written" in ended)) say(`The request pending for ${origin} stands in the credential file, which could not be written now: it ends at its expiry, or when ${firstActNamed("general", origin)} replaces it.`);
1230
1265
  return COMMAND_STATUS.done;
1231
1266
  }
1232
- async function kept(io2, place, tag, origin, minted, userAgent, say) {
1267
+ async function kept(io2, place, tag, origin, minted, userAgent, say, named = false) {
1233
1268
  const now = io2.clock();
1234
1269
  const entry = { token: minted.value, token_id: minted.id, label: minted.label, expires_at: minted.expiresAt, authorized_at: now.toISOString() };
1235
1270
  const written = updateCredentialFile(io2, place, tag, withAuthorized(origin, entry));
@@ -1240,7 +1275,9 @@ async function kept(io2, place, tag, origin, minted, userAgent, say) {
1240
1275
  for (const line of mintedRevocationLines(await revokeUnder(io2, origin, minted.value, minted.id, userAgent), minted)) say(line);
1241
1276
  return COMMAND_STATUS.failed;
1242
1277
  }
1243
- say(authorizedLineOf(origin, entry));
1278
+ const account = named ? await readAccountUnder(io2, origin, minted.value, userAgent) : null;
1279
+ say(authorizedLineOf(origin, entry, account !== null && "account" in account ? account.account : null));
1280
+ if (account !== null && "lines" in account) say(accountUnreadSentence(whoamiLineFor(io2, origin)));
1244
1281
  const before = written.before.origins.get(origin);
1245
1282
  if (before === void 0 || before.token === entry.token || before.expires_at !== null && Date.parse(before.expires_at) <= now.getTime()) return COMMAND_STATUS.done;
1246
1283
  io2.hide(before.token, REDACTED_TOKEN);
@@ -2948,7 +2985,19 @@ import { readdirSync, readFileSync as readFileSync3, statSync as statSync2 } fro
2948
2985
  import { join as join4 } from "node:path";
2949
2986
  import * as zlib from "node:zlib";
2950
2987
  import { deflateRawSync, inflateRawSync as inflateRawSync2 } from "node:zlib";
2951
- var LEFT_OUT_NAMES = Object.freeze(["node_modules", ".git"]);
2988
+ var AI_TOOL_NAMES = Object.freeze([
2989
+ ".claude",
2990
+ ".mcp.json",
2991
+ ".codex",
2992
+ ".cursor",
2993
+ ".gemini",
2994
+ ".continue",
2995
+ ".roo",
2996
+ ".kiro",
2997
+ ".amazonq",
2998
+ ".aider.conf.yml"
2999
+ ]);
3000
+ var LEFT_OUT_NAMES = Object.freeze(["node_modules", ".git", ...AI_TOOL_NAMES]);
2952
3001
  var environmentFileName = (name) => {
2953
3002
  const lower = name.toLowerCase();
2954
3003
  return lower === ".env" || lower.startsWith(".env.");
@@ -2986,7 +3035,7 @@ var walkFolder = (root, limit, leftOut) => {
2986
3035
  return found;
2987
3036
  };
2988
3037
  var linkRefusalSentence = (name) => `${shownNoteName(name)} is a symbolic link, which the deploy does not follow: replace it with the file or folder it points to, or remove it.`;
2989
- var entryBoundSentence = () => `The folder holds more than ${ARTIFACT_ENTRIES_MAX} files after node_modules, .git, and .env files are left out, the most a deploy reads. Deploy a smaller folder, building it into fewer files first.`;
3038
+ var entryBoundSentence = () => `The folder holds more than ${ARTIFACT_ENTRIES_MAX} files after node_modules, .git, .env files, and the AI coding tools' own files are left out, the most a deploy reads. Deploy a smaller folder, building it into fewer files first.`;
2990
3039
  var zippedBytesOf = (zip, place) => {
2991
3040
  const start = place.offset + 30 + Buffer.byteLength(place.name, "utf8");
2992
3041
  const end = start + place.compressedSize;
@@ -4402,13 +4451,17 @@ var applicationEnvironmentOf = (shell, settings, port, caseless = ENVIRONMENT_NA
4402
4451
  return { env, overridden: overridden.filter((name) => !COMMAND_CREDENTIAL_VARIABLES.some((variable) => same(name, variable))) };
4403
4452
  };
4404
4453
  var CONNECTION_PASSWORD = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#@:]*:([^/?#@]*)@/;
4454
+ var HIDDEN_SETTINGS = Object.freeze([
4455
+ ...LOCAL_RUN_CREDENTIAL_SETTINGS.map((setting) => [setting, setting]),
4456
+ [RETIRED_PLATFORM_CREDENTIAL_SETTING, LOCAL_RUN_SETTING.platformCredential]
4457
+ ]);
4405
4458
  var hiddenTextsOf = (received, caseless = ENVIRONMENT_NAMES_CASELESS) => {
4406
4459
  const hidden = [];
4407
4460
  const hide = (text, name) => {
4408
4461
  if (text !== "" && !hidden.some(([held]) => held === text)) hidden.push([text, settingMark(name)]);
4409
4462
  };
4410
4463
  const same = (held, name) => caseless ? held.toUpperCase() === name.toUpperCase() : held === name;
4411
- for (const setting of LOCAL_RUN_CREDENTIAL_SETTINGS) for (const [name, value] of received) if (same(name, setting)) hide(value, setting);
4464
+ for (const [setting, shown2] of HIDDEN_SETTINGS) for (const [name, value] of received) if (same(name, setting)) hide(value, shown2);
4412
4465
  for (const [name, value] of received) {
4413
4466
  const written = CONNECTION_PASSWORD.exec(value)?.[1];
4414
4467
  if (written === void 0 || written === "") continue;
@@ -4531,7 +4584,7 @@ async function localRun(call, io2) {
4531
4584
  }
4532
4585
  var RUN_ROW = Object.freeze({
4533
4586
  name: RUN_SUBCOMMAND,
4534
- summary: `Starts the application on this machine as the platform starts it, with npm start, and hands it the shell's environment with every setting of the environment file laid over it, the file's line winning. It passes the application's output through, with the value of a credential setting, and a connection string that holds a password, replaced by the setting's name between angle brackets. It reads no credential and sends nothing. ${RUN_BACKGROUND_SENTENCE}`,
4587
+ summary: `Starts the application on this machine as the platform starts it, with npm start, and hands it the shell's environment with every setting of the environment file laid over it, the file's line winning. It passes the application's output through, with the value of a credential setting, and a connection string that holds a password, replaced by the setting's name between angle brackets, and a value under ${RETIRED_PLATFORM_CREDENTIAL_SETTING}, the platform credential's earlier name, by <${LOCAL_RUN_SETTING.platformCredential}>. It reads no credential and sends nothing. ${RUN_BACKGROUND_SENTENCE}`,
4535
4588
  options: RUN_OPTIONS,
4536
4589
  credential: null,
4537
4590
  statuses: {
@@ -5216,7 +5269,7 @@ var openBrowser = async (io2, address) => {
5216
5269
  var addressLeadSentence = (origin) => `To log this computer in at ${origin}, approve the login on this page:`;
5217
5270
  var minutesWords = (minutes) => `${minutes} minute${minutes === 1 ? "" : "s"}`;
5218
5271
  var minutesLeftOf = (expiresAt, now) => Math.max(1, Math.ceil((Date.parse(expiresAt) - now.getTime()) / 6e4));
5219
- var openingSentence = (minutes) => `Opening it in your browser. This command waits up to ${minutesWords(minutes)} for the approval.`;
5272
+ var openingSentence = (minutes) => `Opening it in your default browser. If you use another one, copy the address above into it. This command waits up to ${minutesWords(minutes)} for the approval.`;
5220
5273
  var noBrowserSentence = "Open the address in a browser on any computer, approve the login, and paste here the code the page shows.";
5221
5274
  var unboundSentence = "No port on this computer could be bound for the browser to answer, so paste here the code the page shows once you approve.";
5222
5275
  var unopenedSentence = "The browser could not be opened here: open the address yourself, approve the login, and paste here the code the page shows if it shows one.";
@@ -5420,10 +5473,10 @@ var LOGIN_OPTIONS = Object.freeze([
5420
5473
  var LOGIN_AFTER_OWN_ERROR = "the login may not have completed; where a token was minted, list_tokens from your tool shows it by its label, and revoke_token ends it; run login again to log this computer in";
5421
5474
  var LOGIN_ROW = Object.freeze({
5422
5475
  name: LOGIN_SUBCOMMAND,
5423
- summary: `Logs this computer in at the platform's address: it opens the platform's computer login page (${COMPUTER_LOGIN_PATH}) in your browser, where you approve the login under your sign-in, and keeps the token the platform mints in this computer's credential file, then prints authorized:. Where the browser cannot answer, it asks for the code the page shows. It prints no token, code, or verifier.`,
5476
+ summary: `Logs this computer in at the platform's address: it opens the platform's computer login page (${COMPUTER_LOGIN_PATH}) in your default browser, where you approve the login under your sign-in, and keeps the token the platform mints in this computer's credential file. It then reads the account the token acts as and prints authorized: with the address, the account's sign-in address, or its id where it has none, the token's label, and its expiry. Where the browser cannot answer, it asks for the code the page shows. It prints no token, code, or verifier.`,
5424
5477
  options: LOGIN_OPTIONS,
5425
5478
  credential: null,
5426
- reads: "nothing from standard input; with the browser it waits for the page to answer on a port of this computer, and with --no-browser it reads the code the page shows from the terminal, hidden",
5479
+ reads: "nothing from standard input; with the browser it waits for the page to answer on a port of this computer, and with --no-browser it reads the code the page shows from the terminal, hidden; once the token is kept, it reads the account through read_account under it",
5427
5480
  statuses: {
5428
5481
  done: "this computer logged in",
5429
5482
  failed: "a token was minted and not kept, or the token this computer held before was not revoked: the printed lines name revoke_token",
@@ -5532,6 +5585,92 @@ var LOGOUT_ROW = Object.freeze({
5532
5585
  run: logout
5533
5586
  });
5534
5587
 
5588
+ // src/turnzero_cloud_command/whoami.ts
5589
+ var NOTHING_SENT_OR_READ = "Nothing was sent and nothing was read.";
5590
+ var WHOAMI_OPTIONS = Object.freeze([
5591
+ {
5592
+ name: DEPLOY_OPTION.origin,
5593
+ value: "<origin>",
5594
+ required: false,
5595
+ description: `The platform's address whose token this computer reads. Where absent, the address ${ORIGIN_VARIABLE} holds, or ${DEFAULT_COMMAND_ORIGIN} where it is unset.`
5596
+ }
5597
+ ]);
5598
+ var WHOAMI_LINE_HEADS = Object.freeze({ origin: "origin: ", account: "account: ", token: "token: " });
5599
+ var whoamiLinesOf = (origin, account, entry) => [
5600
+ `${WHOAMI_LINE_HEADS.origin}${origin}`,
5601
+ `${WHOAMI_LINE_HEADS.account}${account.address !== null ? `${oneLine(account.address)}, id ${oneLine(account.id)}` : `id ${oneLine(account.id)}, with no sign-in address answered`}`,
5602
+ `${WHOAMI_LINE_HEADS.token}label ${entry.label !== null ? JSON.stringify(entry.label) : "none"}, expiring ${entry.expires_at !== null ? `at ${entry.expires_at}` : "never"}`
5603
+ ];
5604
+ var noComputerTokenSentence = (origin, expiredAt) => expiredAt === null ? `This computer's credential file holds no token for ${origin}, so nothing was sent.` : `This computer's token for ${origin} in its credential file expired at ${expiredAt}, so nothing was sent.`;
5605
+ var whoamiAfterLoginSentence = (whoami2) => `Once login has printed authorized:, run ${whoami2} again.`;
5606
+ async function whoami(call, io2) {
5607
+ const origin = entryOriginOf(io2.env, call.named[DEPLOY_OPTION.origin], NOTHING_SENT_OR_READ);
5608
+ if ("refusal" in origin) {
5609
+ io2.print(origin.refusal);
5610
+ return COMMAND_STATUS.nothing_started;
5611
+ }
5612
+ const agent = userAgentOf(io2.env);
5613
+ if ("refusal" in agent) {
5614
+ io2.print(agent.refusal);
5615
+ return COMMAND_STATUS.nothing_started;
5616
+ }
5617
+ const place = credentialFileOf(io2);
5618
+ const read2 = readMovingCredentialFile(io2, place, io2.print);
5619
+ if (!("state" in read2)) {
5620
+ io2.print(`${readRefusalOf(read2, place.path)} ${NOTHING_SENT_OR_READ}`);
5621
+ return COMMAND_STATUS.nothing_started;
5622
+ }
5623
+ hideStandingTokens(io2, read2.state);
5624
+ const entry = read2.state.origins.get(origin.origin);
5625
+ const expired2 = entry !== void 0 && entry.expires_at !== null && Date.parse(entry.expires_at) <= io2.clock().getTime();
5626
+ if (entry === void 0 || expired2) {
5627
+ io2.print(noComputerTokenSentence(origin.origin, entry === void 0 ? null : entry.expires_at));
5628
+ const reached = authorizationOriginOf(io2.env, call.named[DEPLOY_OPTION.origin], NOTHING_SENT_OR_READ);
5629
+ if ("refusal" in reached) {
5630
+ io2.print(reached.refusal);
5631
+ return COMMAND_STATUS.nothing_started;
5632
+ }
5633
+ io2.print(LOGIN_ROUTE_OPENER);
5634
+ for (const line of loginLinesFor(io2, reached.origin)) io2.print(line);
5635
+ io2.print(whoamiAfterLoginSentence(whoamiLineFor(io2, reached.origin)));
5636
+ return COMMAND_STATUS.nothing_started;
5637
+ }
5638
+ const older = await planeWireRefusal(io2, origin.origin);
5639
+ if (older !== null) {
5640
+ io2.print(older);
5641
+ return COMMAND_STATUS.nothing_started;
5642
+ }
5643
+ const answered = await readAccountUnder(io2, origin.origin, entry.token, agent.userAgent);
5644
+ if ("account" in answered) {
5645
+ for (const line of whoamiLinesOf(origin.origin, answered.account, entry)) io2.print(line);
5646
+ return COMMAND_STATUS.done;
5647
+ }
5648
+ for (const line of answered.lines) io2.print(line);
5649
+ if (answered.refused) {
5650
+ io2.print(fileReadSentence(origin.origin));
5651
+ return COMMAND_STATUS.failed;
5652
+ }
5653
+ io2.print(`Which account this computer's token acts as is unknown: run ${whoamiLineFor(io2, origin.origin)} again.`);
5654
+ return COMMAND_STATUS.unknown;
5655
+ }
5656
+ var WHOAMI_ROW = Object.freeze({
5657
+ name: WHOAMI_SUBCOMMAND,
5658
+ summary: `Names the account this computer's token acts as at the platform's address: it reads the token the credential file keeps for the address and makes one ${READ_ACCOUNT_ACTION} call under it. It prints the address, the account's sign-in address and id, and the token's label and expiry. With no token for the address, it sends nothing and prints the login route. It prints no token.`,
5659
+ options: WHOAMI_OPTIONS,
5660
+ credential: null,
5661
+ reads: "this computer's token for the origin from its credential file, never a minted token from TURNZERO_CLOUD_MINTED_TOKEN, and sends it only to that origin",
5662
+ statuses: {
5663
+ done: "the account was read",
5664
+ failed: "the platform refused the read, where it no longer accepts the token among other causes: the printed lines name login",
5665
+ unknown: "the read drew no answer, or an answer that is not a refusal",
5666
+ nothing_started: "nothing was read: an option or the credential file was refused, the platform was too old, or no live token stands for the address, and the printed lines give the login route"
5667
+ },
5668
+ variables: [[ORIGIN_VARIABLE, `the platform's address whose token is read where the line names no ${DEPLOY_OPTION.origin}.`]],
5669
+ note: [`${WHOAMI_SUBCOMMAND} reads which account a login or an authorize on this computer kept a token for, so you can tell it from your tool's own connection. The credential file is ${CREDENTIAL_FILE_WORDS}.`],
5670
+ afterOwnError: "nothing was changed, since whoami only reads; run it again",
5671
+ run: whoami
5672
+ });
5673
+
5535
5674
  // src/turnzero_cloud_command/upgrade_check.ts
5536
5675
  import { posix as posix3, win32 as win324 } from "node:path";
5537
5676
  var UPGRADE_CHECK_SUBCOMMANDS = Object.freeze([DEPLOY_SUBCOMMAND, CHECK_SUBCOMMAND, LOGIN_SUBCOMMAND]);
@@ -5951,6 +6090,7 @@ async function remintAndWrite(io2, { application, envFile, origin }, userAgent,
5951
6090
  if (facts !== null && url !== null) lines = withSetting(lines, facts.setting, url);
5952
6091
  if (url !== null && limit !== null) lines = withSetting(lines, LOCAL_RUN_SETTING.databaseConnectionLimit, String(limit));
5953
6092
  if (url !== null && limit === null) lines = withoutSetting(lines, LOCAL_RUN_SETTING.databaseConnectionLimit);
6093
+ lines = withoutReadSetting(lines, RETIRED_PLATFORM_CREDENTIAL_SETTING);
5954
6094
  lines = withSetting(lines, LOCAL_RUN_SETTING.platformCredential, credential);
5955
6095
  lines = withSetting(lines, LOCAL_RUN_SETTING.environment, LOCAL_RUN_ENVIRONMENT);
5956
6096
  lines = withSetting(lines, LOCAL_RUN_SETTING.api, origin);
@@ -5988,7 +6128,7 @@ var PROVISION_ROW = Object.freeze({
5988
6128
  LOCAL_RUN_SETTING.environment,
5989
6129
  LOCAL_RUN_SETTING.api,
5990
6130
  LOCAL_RUN_SETTING.gatewayUrl
5991
- ].join(", ")}, and ${LOCAL_RUN_SETTING.application}, the first two only where the application has a development database. It prints no credential value.`,
6131
+ ].join(", ")}, and ${LOCAL_RUN_SETTING.application}, the first two only where the application has a development database. It removes a ${RETIRED_PLATFORM_CREDENTIAL_SETTING} line, the platform credential's earlier name. It prints no credential value.`,
5992
6132
  options: PROVISION_OPTIONS,
5993
6133
  credential: PROVISION_CREDENTIAL,
5994
6134
  statuses: {
@@ -6884,10 +7024,14 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
6884
7024
  "// The live transport, which only the process entry opens: the platform's",
6885
7025
  "// origin and this copy's credential, from the settings it receives. A local",
6886
7026
  "// run receives the development credential, so the same transport works there.",
7027
+ "// Where the credential is not set, it sends nothing and says which line writes it.",
6887
7028
  "export function liveTransport() {",
6888
7029
  " const origin = process.env.TURNZERO_CLOUD_GATEWAY_URL ?? process.env.TURNZERO_CLOUD_API;",
6889
- " return (path, init = {}) =>",
6890
- " fetch(origin + path, { ...init, headers: { ...init.headers, authorization: `Bearer ${process.env.TURNZERO_CLOUD_TOKEN}` } });",
7030
+ " return (path, init = {}) => {",
7031
+ " const credential = process.env.TURNZERO_CLOUD_APP_TOKEN;",
7032
+ " if (!credential) throw new Error('TURNZERO_CLOUD_APP_TOKEN is not set, so no request was sent. Run the provision line again, then start the app again.');",
7033
+ " return fetch(origin + path, { ...init, headers: { ...init.headers, authorization: `Bearer ${credential}` } });",
7034
+ " };",
6891
7035
  "}",
6892
7036
  "",
6893
7037
  "// The asker over any transport: the live one, or a test double's. It answers",
@@ -7098,12 +7242,14 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
7098
7242
  "// The live client, which only the process entry opens. A deployed copy",
7099
7243
  "// receives all three settings. A local run has no TURNZERO_CLOUD_REALM_KEYS,",
7100
7244
  "// so the client is built without keys there and verifies through the",
7101
- "// platform's route.",
7245
+ "// platform's route. Where the credential is not set, the start stops and",
7246
+ "// says which line writes it.",
7102
7247
  "export function openLiveAccounts() {",
7103
7248
  " const keys = process.env.TURNZERO_CLOUD_REALM_KEYS;",
7249
+ " if (!process.env.TURNZERO_CLOUD_APP_TOKEN) throw new Error('TURNZERO_CLOUD_APP_TOKEN is not set, so no session can be verified. Run the provision line again, then start the app again.');",
7104
7250
  " return makeVerifyClient({",
7105
7251
  " baseUrl: process.env.TURNZERO_CLOUD_API,",
7106
- " credential: process.env.TURNZERO_CLOUD_TOKEN,",
7252
+ " credential: process.env.TURNZERO_CLOUD_APP_TOKEN,",
7107
7253
  " ...(keys ? { realmKeys: keys } : {}),",
7108
7254
  " });",
7109
7255
  "}",
@@ -8648,7 +8794,8 @@ var SUBCOMMANDS = Object.freeze([
8648
8794
  AUTHORIZE_ROW,
8649
8795
  LOGOUT_ROW,
8650
8796
  TOKEN_ROW,
8651
- LOGIN_ROW
8797
+ LOGIN_ROW,
8798
+ WHOAMI_ROW
8652
8799
  ]);
8653
8800
  var SERVED = LINE_RUNNER;
8654
8801
  var NPX_FORM_SENTENCE = `Where the command is not installed, install it with ${installLineOf(DEFAULT_COMMAND_ORIGIN)}, or run the same line through npx: write ${commandRunnerOf("latest")} for ${LINE_RUNNER}, with next in place of latest on Turn Zero's development platform.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turnzero",
3
- "version": "0.17.0",
3
+ "version": "0.18.1-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",