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 +1 -1
- package/dist/turnzero-cloud.mjs +165 -18
- package/package.json +1 -1
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
|
|
package/dist/turnzero-cloud.mjs
CHANGED
|
@@ -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: "
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
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",
|