turnzero 0.18.2-next.1 → 0.18.3-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.
@@ -200,6 +200,7 @@ var LOCAL_RUN_SETTING = Object.freeze({
200
200
  });
201
201
  var RETIRED_PLATFORM_CREDENTIAL_SETTING = "TURNZERO_CLOUD_TOKEN";
202
202
  var LOCAL_RUN_ENVIRONMENT = "local";
203
+ var REMINT_FILE_INSTRUCTION_TAIL = " from it in the development environment file now.";
203
204
  var LOCAL_RUN_CREDENTIAL_SETTINGS = Object.freeze([LOCAL_RUN_SETTING.platformCredential, LOCAL_RUN_SETTING.databaseConnection]);
204
205
  var ENVIRONMENT_FILE_NAME = ".env";
205
206
  var provisionBareLineOf = (call) => commandLineOf(call.origin, PROVISION_SUBCOMMAND, {
@@ -504,15 +505,21 @@ var planeObjectOf = (text) => {
504
505
  return null;
505
506
  }
506
507
  };
508
+ var platformMarked = (text) => {
509
+ const version = planeObjectOf(text)?.contract_version;
510
+ return typeof version === "number" && Number.isInteger(version) && version > 0;
511
+ };
507
512
  var redirected = (answer) => answer.type === "opaqueredirect" || answer.status >= 300 && answer.status <= 399;
508
513
  var answeredHow = (status) => status >= 100 ? `HTTP ${status}` : "with a redirect";
509
514
  var ANSWER_KINDS = Object.freeze(["refused", "failed"]);
510
515
  var answerLineOf = (act, kind, status, error) => `${act} ${kind} (HTTP ${status}): ${error}`;
516
+ var PLATFORM_FAILURE_STATUS = 500;
517
+ var notThePlatformLine = (act, status) => `The ${act} was answered HTTP ${status}, not by the platform.`;
518
+ var markedRefusalOf = (status, text) => status < PLATFORM_FAILURE_STATUS && !platformMarked(text) ? null : planeRefusalOf(text);
511
519
  var refusalLines = (act, status, refusal) => [
512
520
  answerLineOf(act, "refused", status, refusal.error),
513
521
  ...refusal.detail !== null ? [oneLine(refusal.detail)] : []
514
522
  ];
515
- var PLATFORM_FAILURE_STATUS = 500;
516
523
  var failureLines = (act, status, said) => said === null ? [`The ${act} was answered HTTP ${status}, a failure of the platform's or of a server between.`] : [answerLineOf(act, "failed", status, said.error), ...said.detail !== null ? [oneLine(said.detail)] : []];
517
524
  var HEALTH_READ_PATH = "/healthz";
518
525
  var PLANE_CHECK_BOUND_SECONDS = 10;
@@ -812,7 +819,8 @@ var CREDENTIAL_SOURCES = Object.freeze(["standard_input", "minted_token_variable
812
819
  var credentialPlaces = (declared) => declared.places ?? `the command reads the grant from its standard input, as the line ${declared.call} answers pipes it in${declared.mintedToken ? `, and otherwise a minted token from ${MINTED_TOKEN_VARIABLE} where it is set, or this computer's token for the platform's address from its credential file, which login and authorize write` : declared.computerToken === true ? `, and otherwise this computer's token for the platform's address from its credential file, which login and authorize write, and never a minted token from ${MINTED_TOKEN_VARIABLE}` : ""}.`;
813
820
  var unsetVariableWayOn = (declared) => `To run a line you wrote yourself under this computer's token, unset ${MINTED_TOKEN_VARIABLE} and run it again; or pipe ${declared.aGrant}.`;
814
821
  var grantWaysOn = (declared) => `Pipe ${declared.aGrant}, as the line ${declared.call} answers pipes it, or, where you wrote the line yourself, pipe nothing to run it under this computer's token.`;
815
- var emptyGrantRefusal = (declared) => `The ${declared.grant} piped to the command was empty, so nothing was sent and nothing was started. ${grantWaysOn(declared)}`;
822
+ var emptyGrantRefusal = (declared) => emptyPipeRefusal(declared, grantWaysOn(declared));
823
+ var emptyPipeRefusal = (declared, wayOn) => `The ${declared.grant} piped to the command was empty, so nothing was sent and nothing was started. ${wayOn}`;
816
824
  var unendedReadRefusal = (declared, reading, nothingArrived = false) => unendedReadSentence(
817
825
  declared.grant,
818
826
  reading,
@@ -828,7 +836,9 @@ async function readCredential(io2, declared, again, line = { named: void 0, clos
828
836
  const unended = unendedReadingOf(read3);
829
837
  if (unended !== null) return { refusal: unendedReadRefusal(declared, unended, read3.bytes === 0) };
830
838
  const grant = read3.text.trim();
831
- if (declared.computerToken === true && grant === "" && read3.bytes > 0) return { refusal: emptyGrantRefusal(declared) };
839
+ if (declared.emptyPipeRefused === true && grant === "" && read3.bytes > 0) {
840
+ return { refusal: declared.computerToken === true ? emptyGrantRefusal(declared) : emptyPipeRefusal(declared, again) };
841
+ }
832
842
  if (grant !== "") {
833
843
  if (!UPLOAD_GRANT_FORM.test(grant)) {
834
844
  return { refusal: `What the command read as its ${declared.grant} is not ${declared.aGrant}, so nothing was sent and nothing was started. ${again}` };
@@ -1107,7 +1117,7 @@ async function revokeUnder(io2, origin, token2, id, userAgent) {
1107
1117
  if (answered.status >= 200 && answered.status <= 299) {
1108
1118
  return revokedRowOf(planeObjectOf(answered.text), id) ? { revoked: true } : { lines: [`${REVOKE_TOKEN_ACTION} was answered HTTP ${answered.status} with no revocation of token ${id} the command can read.`], known: false };
1109
1119
  }
1110
- const refusal = planeRefusalOf(answered.text);
1120
+ const refusal = markedRefusalOf(answered.status, answered.text);
1111
1121
  if (refusal === null) return { lines: [`${REVOKE_TOKEN_ACTION} was answered HTTP ${answered.status}, not by the platform.`], known: false };
1112
1122
  if (NO_LONGER_SERVES.includes(answered.status)) return { ended: true };
1113
1123
  return { lines: refusalLines(REVOKE_TOKEN_ACTION, answered.status, refusal), known: true };
@@ -1155,8 +1165,8 @@ async function readAccountUnder(io2, origin, token2, userAgent) {
1155
1165
  const account = accountOf(planeObjectOf(answered.text));
1156
1166
  return account !== null ? { account } : { lines: [`The ${ACCOUNT_READ_ACT} was answered HTTP ${answered.status} with no account the command can read.`], refused: false };
1157
1167
  }
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 };
1168
+ const refusal = markedRefusalOf(answered.status, answered.text);
1169
+ if (refusal === null) return { lines: [notThePlatformLine(ACCOUNT_READ_ACT, answered.status)], refused: false };
1160
1170
  return { lines: refusalLines(ACCOUNT_READ_ACT, answered.status, refusal), refused: true };
1161
1171
  }
1162
1172
  var whoamiLineFor = (io2, origin) => printedLineOf(io2, commandLineOf(origin, WHOAMI_SUBCOMMAND, origin !== DEFAULT_COMMAND_ORIGIN ? { origin } : {}), origin);
@@ -1225,9 +1235,9 @@ async function completeAct(io2, origin, kind, code, userAgent, say, deliver = ()
1225
1235
  const answer = planeObjectOf(answered.text);
1226
1236
  if (answer !== null && typeof answer.value === "string") io2.hide(answer.value, REDACTED_TOKEN);
1227
1237
  if (answered.status < 200 || answered.status > 299) {
1228
- const refusal = planeRefusalOf(answered.text);
1238
+ const refusal = markedRefusalOf(answered.status, answered.text);
1229
1239
  if (refusal === null) {
1230
- say(`The exchange was answered HTTP ${answered.status}, not by the platform.`);
1240
+ say(notThePlatformLine(EXCHANGE_ACT, answered.status));
1231
1241
  say(unknown);
1232
1242
  return COMMAND_STATUS.unknown;
1233
1243
  }
@@ -1573,6 +1583,79 @@ function strFromU8(dat, latin1) {
1573
1583
  }
1574
1584
  }
1575
1585
 
1586
+ // src/session_free_paths.ts
1587
+ var SESSION_FREE_SIGN_IN_PREFIX = "/__account/";
1588
+ var SESSION_FREE_ROUTER_PREFIX = "/__router/";
1589
+
1590
+ // src/client_files.ts
1591
+ var MiB = 1024 * 1024;
1592
+ var CLIENT_FOLDER = "client/";
1593
+ var CLIENT_FILES_MAX = 2e3;
1594
+ var CLIENT_FILE_MAX_BYTES = 25 * MiB;
1595
+ var CLIENT_FILES_TOTAL_MAX_BYTES = 50 * MiB;
1596
+ var CLIENT_NAME_MAX_CHARS = 512;
1597
+ var CLIENT_NAME_MAX_SEGMENTS = 32;
1598
+ var PRUNE_FILE_DELETE_BUDGET_MS = 20 * 6e4;
1599
+ var INDEX_CACHE_BYTES = 32 * MiB;
1600
+ var CONTENT_CACHE_BYTES = 128 * MiB;
1601
+ var CONTENT_CACHE_FILE_MAX_BYTES = 1 * MiB;
1602
+ var SHELL_FILES = Object.freeze([
1603
+ { name: "index.html", paths: ["/", "/index.html"] },
1604
+ { name: "app.js", paths: ["/app.js"] },
1605
+ { name: "styles.css", paths: ["/styles.css"] }
1606
+ ]);
1607
+ var SHELL_NAMES = new Set(SHELL_FILES.map((file) => file.name));
1608
+ var SHELL_PATHS = new Set(SHELL_FILES.flatMap((file) => file.paths));
1609
+ var CLIENT_CONTENT_TYPES = Object.freeze({
1610
+ ".html": "text/html",
1611
+ ".css": "text/css",
1612
+ ".js": "text/javascript",
1613
+ ".mjs": "text/javascript",
1614
+ ".json": "application/json",
1615
+ ".map": "application/json",
1616
+ ".svg": "image/svg+xml",
1617
+ ".png": "image/png",
1618
+ ".jpg": "image/jpeg",
1619
+ ".jpeg": "image/jpeg",
1620
+ ".gif": "image/gif",
1621
+ ".webp": "image/webp",
1622
+ ".avif": "image/avif",
1623
+ ".ico": "image/x-icon",
1624
+ ".woff": "font/woff",
1625
+ ".woff2": "font/woff2",
1626
+ ".ttf": "font/ttf",
1627
+ ".otf": "font/otf",
1628
+ ".wasm": "application/wasm",
1629
+ ".txt": "text/plain",
1630
+ ".xml": "application/xml",
1631
+ ".webmanifest": "application/manifest+json",
1632
+ ".pdf": "application/pdf",
1633
+ ".mp3": "audio/mpeg",
1634
+ ".ogg": "audio/ogg",
1635
+ ".oga": "audio/ogg",
1636
+ ".wav": "audio/wav",
1637
+ ".m4a": "audio/mp4",
1638
+ ".flac": "audio/flac",
1639
+ ".mp4": "video/mp4",
1640
+ ".webm": "video/webm",
1641
+ ".ogv": "video/ogg"
1642
+ });
1643
+ var RESERVED_TOP_FOLDERS = Object.freeze([SESSION_FREE_SIGN_IN_PREFIX, SESSION_FREE_ROUTER_PREFIX].map((prefix) => prefix.replace(/^\/|\/$/g, "").toLowerCase()));
1644
+ var ADMITTED_SEGMENT = /^[A-Za-z0-9\-_.~]+$/;
1645
+ function admittedClientName(path) {
1646
+ if (!isClientPath(path)) return null;
1647
+ const name = path.slice(CLIENT_FOLDER.length);
1648
+ if (name.length > CLIENT_NAME_MAX_CHARS) return null;
1649
+ const segments = name.split("/");
1650
+ if (segments.length > CLIENT_NAME_MAX_SEGMENTS) return null;
1651
+ for (const segment of segments) {
1652
+ if (!ADMITTED_SEGMENT.test(segment) || segment.startsWith(".") || segment.endsWith(".")) return null;
1653
+ }
1654
+ if (RESERVED_TOP_FOLDERS.includes(segments[0].toLowerCase())) return null;
1655
+ return name;
1656
+ }
1657
+ var isClientPath = (path) => path.startsWith(CLIENT_FOLDER) && path.length > CLIENT_FOLDER.length;
1658
+
1576
1659
  // src/turnzero_cloud_command/export_download.ts
1577
1660
  import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2 } from "node:path";
1578
1661
 
@@ -2218,7 +2301,7 @@ var readHealthPath = (path, entries) => {
2218
2301
  return answer("absent");
2219
2302
  };
2220
2303
  var shownHealthPath = (path) => shownNoteName(bareHealthPath(path));
2221
- var healthLineOf = (path) => `${WARNING_LINE_HEAD}${MANIFEST_FILE} names the health path ${shownHealthPath(path)}, and no other file of the zip holds its last segment, package files and documentation set aside. A deploy waits for that path to answer 200, so check that the application serves it. A route built in another form is not seen here.`;
2304
+ var healthLineOf = (path) => `${WARNING_LINE_HEAD}${MANIFEST_FILE} names the health path ${shownHealthPath(path)}, and no other file of the zip holds its last segment, package files and documentation set aside. A deploy waits for that path to answer 200 three times in a row, so check that the application serves it. A route built in another form is not seen here.`;
2222
2305
  var CREDENTIAL_FILE_NAMES = Object.freeze(["id_rsa", "id_dsa", "id_ecdsa", "id_ed25519", ".netrc", ".pgpass", ".htpasswd", "credentials.json"]);
2223
2306
  var CLIENT_SECRET_OPENING = "client_secret";
2224
2307
  var CLIENT_SECRET_ENDING = ".json";
@@ -2292,11 +2375,14 @@ var DeployRefusal = class extends Error {
2292
2375
  * row's outcome, a failed gate's `gate`, its evidence (PLD-L0-59;
2293
2376
  * PLD-L0-63); onto the wire, an `upload_hash_mismatch`'s
2294
2377
  * `received_sha256` and `expected_sha256` (deploy_upload.ts's readUpload;
2295
- * PLD-L0-86), which the upload form's read throws before any row; empty
2296
- * for every other refusal. Never `error` or `detail` themselves — failRun
2297
- * keeps those two first. The wire mapping (main.ts's actRefusal) reads
2298
- * status, code, and detail, and the two hashes for that one refusal
2299
- * alone; a refusal thrown after the 202 reaches the row, not the wire. */
2378
+ * PLD-L0-86), which the upload form's read throws before any row, and a
2379
+ * `deploy_files_too_large`'s `limit`, the cap it met (deploy_files.ts's
2380
+ * tooLarge; #4418; MAPI-26); empty for every other refusal. Never
2381
+ * `error` or `detail` themselves — failRun keeps those two first. The
2382
+ * wire mapping (main.ts's actRefusal) reads status, code, and detail, the
2383
+ * two hashes for `upload_hash_mismatch` alone, and `limit` for
2384
+ * `deploy_files_too_large` alone; a refusal thrown after the 202 reaches
2385
+ * the row, not the wire. */
2300
2386
  members;
2301
2387
  /** The fields the control plane's `version failed` line carries beside
2302
2388
  * the refusal for the operator alone (MAPI-10), never the row's or the
@@ -2584,7 +2670,7 @@ var isRecord = (value) => typeof value === "object" && value !== null && !Array.
2584
2670
  var ROOT_FILE_MAX_BYTES = 1024 * 1024;
2585
2671
  var MEMBER_FILE_MAX_BYTES = 64 * 1024;
2586
2672
  var MEMBER_FILES_READ_MAX = 50;
2587
- var WINDOWS_ZIP_COMMAND = "tar.exe -a -c -f app.zip --exclude node_modules --exclude .env --exclude '.env.*' --exclude .git -C app .";
2673
+ var WINDOWS_ZIP_COMMAND = "tar.exe -a -c -f app.zip --exclude node_modules --exclude .env --exclude '.env.*' --exclude .git --exclude .claude --exclude .mcp.json --exclude .codex --exclude .cursor --exclude .gemini --exclude .continue --exclude .roo --exclude .kiro --exclude .amazonq --exclude .aider.conf.yml -C app .";
2588
2674
  function readArtifact(bytes) {
2589
2675
  const stored = [];
2590
2676
  const names = [];
@@ -2766,11 +2852,48 @@ function manifestCopyDepthRefusal(reading) {
2766
2852
  throw new DeployRefusal(400, "artifact_layout_invalid", `The zip's manifest.json nests deeper than the ${MANIFEST_COPY_DEPTH_MAX} levels the deploy reads: the platform reads the manifest submit_manifest recorded, so flatten the zip's copy or leave it out, and deploy again (PLD-L0-63).`);
2767
2853
  }
2768
2854
  }
2855
+ function clientReadingOf(records) {
2856
+ const admitted = /* @__PURE__ */ new Map();
2857
+ const skipped = [];
2858
+ const skippedSeen = /* @__PURE__ */ new Set();
2859
+ for (const record of records) {
2860
+ const { path, folder } = entryNameOf(record.name);
2861
+ if (folder || !isClientPath(path)) continue;
2862
+ const name = admittedClientName(path);
2863
+ if (name !== null) admitted.set(name, record);
2864
+ else if (!skippedSeen.has(path)) {
2865
+ skippedSeen.add(path);
2866
+ skipped.push(path);
2867
+ }
2868
+ }
2869
+ return { admitted, skipped };
2870
+ }
2871
+ function clientBoundRefusal(reading) {
2872
+ const cite = " (SVC-L0-26).";
2873
+ if (reading.admitted.size > CLIENT_FILES_MAX) {
2874
+ return `The zip's client/ folder holds ${reading.admitted.size} files the publish admits, more than the ${CLIENT_FILES_MAX} files one version publishes: ship fewer client files, and deploy again${cite}`;
2875
+ }
2876
+ let total = 0;
2877
+ for (const [name, record] of reading.admitted) {
2878
+ const declared = declaredBytesOf(record);
2879
+ if (declared > CLIENT_FILE_MAX_BYTES) {
2880
+ return `The zip's client file ${shownEntry(`${CLIENT_FOLDER}${name}`)} declares ${declared} bytes, more than the 25 MiB (${CLIENT_FILE_MAX_BYTES} bytes) one client file holds: keep long media in a storage area, and deploy again${cite}`;
2881
+ }
2882
+ total += declared;
2883
+ }
2884
+ if (total > CLIENT_FILES_TOTAL_MAX_BYTES) {
2885
+ return `The zip's client files declare ${total} bytes in all, more than the 50 MiB (${CLIENT_FILES_TOTAL_MAX_BYTES} bytes) one version's client files hold: keep large media in a storage area, and deploy again${cite}`;
2886
+ }
2887
+ return null;
2888
+ }
2769
2889
  function checkArtifact(bytes) {
2770
2890
  const reading = readArtifact(bytes);
2771
2891
  const packageJson = artifactLayoutOf(reading);
2772
2892
  manifestCopyDepthRefusal(reading);
2773
- return { reading, packageJson };
2893
+ const client = clientReadingOf(reading.records);
2894
+ const bounded = clientBoundRefusal(client);
2895
+ if (bounded !== null) throw new DeployRefusal(400, "artifact_layout_invalid", bounded);
2896
+ return { reading, packageJson, client };
2774
2897
  }
2775
2898
  function healthPathReadingOf(bytes, reading, recorded) {
2776
2899
  const path = isRecord(recorded) && typeof recorded.health === "string" ? recorded.health : null;
@@ -3292,7 +3415,7 @@ async function prepareDeploy(io2, options, zipSha256Value, bearer, userAgent, re
3292
3415
  return nothingStarted;
3293
3416
  }
3294
3417
  if (answered.status < 200 || answered.status > 299) {
3295
- const refusal = planeRefusalOf(answered.text);
3418
+ const refusal = markedRefusalOf(answered.status, answered.text);
3296
3419
  if (refusal === null) {
3297
3420
  io2.print(prepareNotThePlatform(answered.status));
3298
3421
  io2.print(PREPARE_AGAIN);
@@ -3496,7 +3619,7 @@ async function deploy(named, io2, flags = /* @__PURE__ */ new Set()) {
3496
3619
  return PROGRESS_EXIT_STATUS.refused;
3497
3620
  }
3498
3621
  const answer = planeObjectOf(started.text);
3499
- if (answer === null) {
3622
+ if (answer === null || (started.status < 200 || started.status > 299) && !platformMarked(started.text)) {
3500
3623
  io2.print(`The start was answered HTTP ${started.status}, not by the platform, so the deploy may have started.`);
3501
3624
  io2.print(stop);
3502
3625
  return PROGRESS_EXIT_STATUS.refused;
@@ -3543,8 +3666,8 @@ async function deploy(named, io2, flags = /* @__PURE__ */ new Set()) {
3543
3666
  return PROGRESS_EXIT_STATUS.refused;
3544
3667
  }
3545
3668
  if (read2.status !== 200 || !read2.type.startsWith("text/plain")) {
3546
- const refusal = planeRefusalOf(read2.text);
3547
- const said = refusal !== null ? refusalLines(PROGRESS_READ_ACT, read2.status, refusal) : [`The progress read was answered HTTP ${read2.status}, not by the platform.`];
3669
+ const refusal = markedRefusalOf(read2.status, read2.text);
3670
+ const said = refusal !== null ? refusalLines(PROGRESS_READ_ACT, read2.status, refusal) : [notThePlatformLine(PROGRESS_READ_ACT, read2.status)];
3548
3671
  for (const line of said) io2.print(line);
3549
3672
  io2.print(stop);
3550
3673
  return PROGRESS_EXIT_STATUS.refused;
@@ -3713,7 +3836,7 @@ async function callCheck(io2, options, bearer, userAgent, refused = () => null)
3713
3836
  return nothingChecked;
3714
3837
  }
3715
3838
  if (answered.status < 200 || answered.status > 299) {
3716
- const refusal = planeRefusalOf(answered.text);
3839
+ const refusal = markedRefusalOf(answered.status, answered.text);
3717
3840
  if (refusal === null) {
3718
3841
  io2.print(checkNotThePlatform(answered.status));
3719
3842
  io2.print(CHECK_AGAIN);
@@ -3828,7 +3951,7 @@ var CHECK_ROW = Object.freeze({
3828
3951
  note: [
3829
3952
  `A check is one line, which carries no credential: run the line your tool's ${CHECK_ACTION} call answers, or ${CHECK_SUBCOMMAND} ${CHECK_OPTION.application} <application id>, from the application's folder, before a first deploy and after each fix. It reads its credential and the platform's address as ${DEPLOY_SUBCOMMAND} does, builds the zip ${DEPLOY_SUBCOMMAND} builds, and sends the platform the application alone, no byte of the zip and no hash. It uploads nothing and starts nothing.`,
3830
3953
  `After its zip line, ${CHECK_SUBCOMMAND} prints the further lines ${DEPLOY_SUBCOMMAND} prints, leaving out the health warning where the platform says the deploy would be a first one, since a refused: line then reads the recorded health path. Then it prints one refused: line for each refusal the platform answered for the application's state, then one for each refusal of the zip by the deploy's own checks, a line naming submit_manifest where the zip's manifest.json names another health path, and a last checked: line counting the refusals.`,
3831
- `A check is no admission: a later deploy reads every check again, since a deploy in flight, the day's deploys, and the hosting cell's room can change between the two.`
3954
+ `A passing check does not guarantee the deploy is accepted: a deploy in flight, the day's deploys, and the hosting cell's room can change before the deploy, which makes every check again.`
3832
3955
  ],
3833
3956
  afterOwnError: CHECK_AFTER_OWN_ERROR,
3834
3957
  run: (call, io2) => check(call.named, io2)
@@ -4026,7 +4149,8 @@ var SECRET_CREDENTIAL = Object.freeze({
4026
4149
  aGrant: "a secret grant",
4027
4150
  call: "the store_secret or rotate_secret call",
4028
4151
  mintedToken: false,
4029
- computerToken: true
4152
+ computerToken: true,
4153
+ emptyPipeRefused: true
4030
4154
  });
4031
4155
  var freshLineSteps = (act) => `${SECRET_ACTION[act]} from your tool with the name and its scope, naming no value, and run the line it answers as given.`;
4032
4156
  var secretAgain = (act) => `Call ${freshLineSteps(act)}`;
@@ -4176,7 +4300,7 @@ async function secret(call, io2) {
4176
4300
  }
4177
4301
  const answer = planeObjectOf(answered.text);
4178
4302
  if (answered.status < 200 || answered.status > 299) {
4179
- const refusal = planeRefusalOf(answered.text);
4303
+ const refusal = markedRefusalOf(answered.status, answered.text);
4180
4304
  if (refusal === null) {
4181
4305
  io2.print(`${action} was answered HTTP ${answered.status}, not by the platform.`);
4182
4306
  io2.print(unknown);
@@ -4394,10 +4518,12 @@ var RUN_OPTIONS = Object.freeze([
4394
4518
  }
4395
4519
  ]);
4396
4520
  var NO_APPLICATION_STARTED = "No application was started.";
4521
+ var RUN_STEP_BACKGROUND = "The line runs until the application ends, so start it as a background process and read its lines";
4397
4522
  var PORT_FORM = /^[1-9][0-9]{0,4}$/;
4398
4523
  var isPortNumber = (value) => PORT_FORM.test(value) && Number(value) <= 65535;
4399
4524
  var RUN_BACKGROUND_SENTENCE = "The command runs until the application ends, so a tool starts it as a background process and reads its lines.";
4400
- var startingLineOf = (folder) => `Starting npm start in ${folder}. ${RUN_BACKGROUND_SENTENCE}`;
4525
+ var stopSentenceOf = (processId, onWindows2 = NPM_NEEDS_SHELL) => `To stop it, send it an interrupt or run ${onWindows2 ? `taskkill /T /F /PID ${processId}` : `kill ${processId}`}, which ends the application with the command.`;
4526
+ var startingLineOf = (folder, processId, onWindows2 = NPM_NEEDS_SHELL) => `Starting npm start in ${folder}; the command's process id is ${processId}. ${RUN_BACKGROUND_SENTENCE} ${stopSentenceOf(processId, onWindows2)}`;
4401
4527
  var PORT_SOURCE_WORDS = Object.freeze({
4402
4528
  option: RUN_OPTION.port,
4403
4529
  file: "the environment file",
@@ -4557,7 +4683,7 @@ async function localRun(call, io2) {
4557
4683
  }
4558
4684
  const { env, overridden } = applicationEnvironmentOf(io2.env, read2.settings, options.port);
4559
4685
  for (const [text, shown2] of hiddenTextsOf(Object.entries(env))) io2.hide(text, shown2);
4560
- io2.print(startingLineOf(options.folder));
4686
+ io2.print(startingLineOf(options.folder, io2.processId ?? process.pid, io2.windows ?? NPM_NEEDS_SHELL));
4561
4687
  if (file.text === null) io2.print(`No environment file stands at ${options.envFile}, so the application receives the shell's environment alone.`);
4562
4688
  else if (read2.settings.length === 0) io2.print(`${options.envFile} sets no setting.`);
4563
4689
  else io2.print(`${options.envFile} sets ${read2.settings.length} setting(s): ${read2.settings.map(([name]) => name).join(", ")}.`);
@@ -4796,7 +4922,7 @@ var readOf = async (io2, origin, userAgent, body, what) => {
4796
4922
  }
4797
4923
  const answer = planeObjectOf(text);
4798
4924
  if (status >= 200 && status <= 299 && answer !== null) return { answer };
4799
- const refusal = planeRefusalOf(text);
4925
+ const refusal = markedRefusalOf(status, text);
4800
4926
  if (refusal === null) return { lines: [`${LIBRARY_READ_ACTION} was answered HTTP ${status}, not by the platform. ${NOTHING_WRITTEN2}`] };
4801
4927
  return { lines: [...refusalLines(LIBRARY_READ_ACTION, status, refusal), NOTHING_WRITTEN2] };
4802
4928
  };
@@ -5800,6 +5926,7 @@ var PROVISION_CREDENTIAL = Object.freeze({
5800
5926
  aGrant: "a secret grant",
5801
5927
  call: "the submit_manifest call naming local_run",
5802
5928
  mintedToken: true,
5929
+ emptyPipeRefused: true,
5803
5930
  bare: `with nothing piped and neither token, it keeps a verifier and prints the challenge and the submit_manifest call naming local_run and ${CODE_CHALLENGE_MEMBER}, and a grant piped is presented with that verifier`
5804
5931
  });
5805
5932
  var NOTHING_REMINTED = "Nothing was re-minted, and the environment file is as it stood.";
@@ -5835,7 +5962,19 @@ var outsideApplicationFolder = (io2, envFile) => APPLICATION_FOLDER_MARKS.every(
5835
5962
  return false;
5836
5963
  }
5837
5964
  });
5965
+ var PROVISION_NEXT_HEAD = "next: ";
5966
+ var nextRunLineOf = (io2, origin, envFile) => machineLineOf(
5967
+ commandLineOf(origin, RUN_SUBCOMMAND, {
5968
+ ...origin !== DEFAULT_COMMAND_ORIGIN ? { origin } : {},
5969
+ ...envFile !== void 0 ? { quoted: [[RUN_OPTION.envFile, envFile]] } : {}
5970
+ }),
5971
+ { windows: onWindows(io2), npx: ranThroughNpx(io2) },
5972
+ origin
5973
+ );
5974
+ var provisionNextLineOf = (line) => `${PROVISION_NEXT_HEAD}start the application on this machine. ${RUN_STEP_BACKGROUND}: ${line}`;
5838
5975
  var charactersOf = (value) => `<${value.length} characters>`;
5976
+ var REMINT_FILE_INSTRUCTION = new RegExp(`: set [A-Z][A-Z0-9_]*${REMINT_FILE_INSTRUCTION_TAIL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
5977
+ var writtenDetailOf = (detail, setting, envFile) => detail.replace(REMINT_FILE_INSTRUCTION, () => `, and this run wrote it into ${setting} in ${envFile}.`);
5839
5978
  var transportText = (error) => {
5840
5979
  const oneLine2 = (text) => text.replace(/\s+/g, " ").trim();
5841
5980
  const message = error instanceof Error ? error.message : String(error);
@@ -5899,7 +6038,7 @@ async function remint(io2, origin, credential, userAgent, application, name, ver
5899
6038
  }
5900
6039
  const answer = planeObjectOf(text);
5901
6040
  if (status < 200 || status > 299) {
5902
- const refusal = planeRefusalOf(text);
6041
+ const refusal = markedRefusalOf(status, text);
5903
6042
  if (refusal === null) return { kind: "unknown", lines: [`${PROVISION_ACTION} was answered HTTP ${status}, not by the platform.`] };
5904
6043
  return { kind: "refused", error: refusal.error, action: answer?.action, lines: refusalLines(PROVISION_ACTION, status, refusal) };
5905
6044
  }
@@ -6003,7 +6142,7 @@ async function provision(call, io2) {
6003
6142
  io2.print(older);
6004
6143
  return COMMAND_STATUS.nothing_started;
6005
6144
  }
6006
- return await remintAndWrite(io2, sent, agent.userAgent, { ...read2, verifier, again }, standing.text, reminted);
6145
+ return await remintAndWrite(io2, sent, agent.userAgent, { ...read2, verifier, again, next: nextRunLineOf(io2, sent.origin, namedEnvFile) }, standing.text, reminted);
6007
6146
  } finally {
6008
6147
  release();
6009
6148
  dropWritingFile(io2, options.envFile);
@@ -6113,22 +6252,24 @@ async function remintAndWrite(io2, { application, envFile, origin }, userAgent,
6113
6252
  if (facts !== null && url !== null) {
6114
6253
  io2.print(`${envFile}: ${facts.setting}=${charactersOf(url)}, ${limit !== null ? `${LOCAL_RUN_SETTING.databaseConnectionLimit}=${limit}, ` : ""}${plain}`);
6115
6254
  io2.print(`development database: ${oneLine(facts.roleName)} on ${facts.host}, database ${oneLine(facts.dbName)}${limit === null ? "" : `, a client pool of at most ${limit} ${limit === 1 ? "connection" : "connections"} per process`}`);
6116
- if (database.kind === "answered" && typeof database.answer.detail === "string") io2.print(oneLine(database.answer.detail));
6255
+ if (database.kind === "answered" && typeof database.answer.detail === "string") io2.print(oneLine(writtenDetailOf(database.answer.detail, facts.setting, envFile)));
6117
6256
  } else {
6118
6257
  io2.print(`${envFile}: ${plain}`);
6119
6258
  io2.print(
6120
6259
  `no development database stands for ${application}: ${LOCAL_RUN_SETTING.databaseConnection} and ${LOCAL_RUN_SETTING.databaseConnectionLimit} were not written; submit_manifest with the database kind provisions one.`
6121
6260
  );
6122
6261
  }
6123
- if (typeof platform.answer.detail === "string") io2.print(oneLine(platform.answer.detail));
6262
+ if (typeof platform.answer.detail === "string") io2.print(oneLine(writtenDetailOf(platform.answer.detail, LOCAL_RUN_SETTING.platformCredential, envFile)));
6124
6263
  if (outsideApplicationFolder(io2, envFile)) io2.print(outsideApplicationWarningOf(dirname5(envFile)));
6125
6264
  if (facts !== null && limitCarried !== null) {
6126
6265
  const cause = limitMember ? "the plan's database-connection-limit holds no usable quantity, which platform staff set with set_plan_quota" : "the platform that answered serves no such member yet";
6127
6266
  io2.print(
6128
6267
  `${envFile} holds ${facts.setting} and the platform settings this run re-minted, and no ${LOCAL_RUN_SETTING.databaseConnectionLimit}: ${PROVISION_ACTION}'s development_database answer carried ${limitCarried}, where the pool's maximum is a positive integer, so ${cause}. Until that changes the application's pool holds one connection; once it has, ${againSteps(read2.kind, read2.again)}.`
6129
6268
  );
6269
+ io2.print(provisionNextLineOf(read2.next));
6130
6270
  return COMMAND_STATUS.failed;
6131
6271
  }
6272
+ io2.print(provisionNextLineOf(read2.next));
6132
6273
  return COMMAND_STATUS.done;
6133
6274
  }
6134
6275
  var PROVISION_ROW = Object.freeze({
@@ -6151,7 +6292,7 @@ var PROVISION_ROW = Object.freeze({
6151
6292
  },
6152
6293
  note: [
6153
6294
  `Run ${PROVISION_BARE_LINE_WORDS} from the folder the application runs in. With nothing piped and no token for the address, it prints a challenge and the submit_manifest call naming local_run and ${CODE_CHALLENGE_MEMBER}; your tool makes that call and runs the line it answers once, as given, before its grant expires, which presents the grant with this computer's verifier. On a computer logged in, the bare line re-mints at once.`,
6154
- `Each run revokes the development credentials the application had before it. A deployed development environment holds the revoked values until it restarts, so call restart_application on development afterwards. Its redeploy takes both re-minted credentials from custody, the connection string and the platform credential, and spends no deploy of the day. ${PROVISION_SUBCOMMAND} writes no ${PORT_SETTING}: ${RUN_SUBCOMMAND} sets it from ${RUN_OPTION.port}.`
6295
+ `Each run revokes the development credentials the application had before it. A deployed development environment holds the revoked values until it restarts, so the line's last re-mint restarts it where development serves a version. Its redeploy takes both re-minted credentials from custody, the connection string and the platform credential, and does not count toward the day's deploys. Where a deploy of development is still in flight, the re-mint's answer names restart_application on development, and your tool calls it once that deploy ends. ${PROVISION_SUBCOMMAND} writes no ${PORT_SETTING}: ${RUN_SUBCOMMAND} sets it from ${RUN_OPTION.port}.`
6155
6296
  ],
6156
6297
  afterOwnError: provisionAfterOwnError,
6157
6298
  run: provision
@@ -6178,7 +6319,7 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
6178
6319
  "",
6179
6320
  "Turn Zero Cloud serves its rules to a coding tool through its MCP server, `turnzero-cloud`, and they change with the platform, so none is repeated here.",
6180
6321
  "",
6181
- `- Where the server is not connected, connect it first. The platform's page "Connect your tool" says how.`,
6322
+ `- Where the server is not connected, connect it first. The platform's page "Connect Claude Code or Codex" says how.`,
6182
6323
  "- Before you change or deploy this service, read the server's `deploy` skill.",
6183
6324
  "",
6184
6325
  "## What you run here",
@@ -6341,9 +6482,9 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
6341
6482
  " }",
6342
6483
  " send(404, { error: 'not_found' });",
6343
6484
  " } catch (error) {",
6344
- " const refused = error instanceof SyntaxError || error?.code === '23514';",
6345
- " if (!refused) console.error(error);",
6346
- " send(refused ? 400 : 500, { error: refused ? 'invalid_note' : 'internal' });",
6485
+ " const refused = error instanceof SyntaxError ? 'invalid_json' : error?.code === '23514' ? 'invalid_note' : null;",
6486
+ " if (refused === null) console.error(error);",
6487
+ " send(refused === null ? 500 : 400, { error: refused ?? 'internal' });",
6347
6488
  " }",
6348
6489
  " });",
6349
6490
  "}",
@@ -6423,6 +6564,17 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
6423
6564
  " assert.deepEqual(await response.json(), { error: 'invalid_note' });",
6424
6565
  " assert.deepEqual(await (await fetch(`${base}/notes`)).json(), []);",
6425
6566
  "});",
6567
+ "",
6568
+ "test('POST /notes answers 400 invalid_json for a body that is not JSON, and GET /notes returns no row', async () => {",
6569
+ " const response = await fetch(`${base}/notes`, {",
6570
+ " method: 'POST',",
6571
+ " headers: { 'content-type': 'application/json' },",
6572
+ " body: '{',",
6573
+ " });",
6574
+ " assert.equal(response.status, 400);",
6575
+ " assert.deepEqual(await response.json(), { error: 'invalid_json' });",
6576
+ " assert.deepEqual(await (await fetch(`${base}/notes`)).json(), []);",
6577
+ "});",
6426
6578
  ""
6427
6579
  ].join("\n")]
6428
6580
  ]),
@@ -6444,7 +6596,7 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
6444
6596
  "",
6445
6597
  "Turn Zero Cloud serves its rules to a coding tool through its MCP server, `turnzero-cloud`, and they change with the platform, so none is repeated here.",
6446
6598
  "",
6447
- `- Where the server is not connected, connect it first. The platform's page "Connect your tool" says how.`,
6599
+ `- Where the server is not connected, connect it first. The platform's page "Connect Claude Code or Codex" says how.`,
6448
6600
  "- Before you change or deploy this service, read the server's `deploy` skill.",
6449
6601
  "",
6450
6602
  "## What you run here",
@@ -6563,7 +6715,7 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
6563
6715
  "",
6564
6716
  "Turn Zero Cloud serves its rules to a coding tool through its MCP server, `turnzero-cloud`, and they change with the platform, so none is repeated here.",
6565
6717
  "",
6566
- `- Where the server is not connected, connect it first. The platform's page "Connect your tool" says how.`,
6718
+ `- Where the server is not connected, connect it first. The platform's page "Connect Claude Code or Codex" says how.`,
6567
6719
  "- Before you change or deploy this service, read the server's `deploy` skill, and before you change its schedule, the server's `schedule` skill.",
6568
6720
  "",
6569
6721
  "## What you run here",
@@ -6769,7 +6921,7 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
6769
6921
  "",
6770
6922
  "Turn Zero Cloud serves its rules to a coding tool through its MCP server, `turnzero-cloud`, and they change with the platform, so none is repeated here.",
6771
6923
  "",
6772
- `- Where the server is not connected, connect it first. The platform's page "Connect your tool" says how.`,
6924
+ `- Where the server is not connected, connect it first. The platform's page "Connect Claude Code or Codex" says how.`,
6773
6925
  "- Before you change or deploy this service, read the server's `deploy` skill, and before you change its sign-in or its AI call, the server's `add-sign-in` or `use-ai-allowance` skill.",
6774
6926
  "",
6775
6927
  "## What you run here",
@@ -7235,9 +7387,9 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
7235
7387
  " }",
7236
7388
  " send(404, { error: 'not_found' });",
7237
7389
  " } catch (error) {",
7238
- " const refused = error instanceof SyntaxError || error?.code === '23514';",
7239
- " if (!refused) console.error(error);",
7240
- " send(refused ? 400 : 500, { error: refused ? 'invalid_note' : 'internal' });",
7390
+ " const refused = error instanceof SyntaxError ? 'invalid_json' : error?.code === '23514' ? 'invalid_note' : null;",
7391
+ " if (refused === null) console.error(error);",
7392
+ " send(refused === null ? 500 : 400, { error: refused ?? 'internal' });",
7241
7393
  " }",
7242
7394
  " });",
7243
7395
  "}",
@@ -7406,6 +7558,17 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
7406
7558
  " assert.deepEqual(await (await fetch(`${base}/api/notes`, { headers: signedIn(sam) })).json(), []);",
7407
7559
  "});",
7408
7560
  "",
7561
+ "test('POST /api/notes answers 400 invalid_json for a body that is not JSON, and GET /api/notes returns no row', async () => {",
7562
+ " const response = await fetch(`${base}/api/notes`, {",
7563
+ " method: 'POST',",
7564
+ " headers: { 'content-type': 'application/json', ...signedIn(sam) },",
7565
+ " body: '{',",
7566
+ " });",
7567
+ " assert.equal(response.status, 400);",
7568
+ " assert.deepEqual(await response.json(), { error: 'invalid_json' });",
7569
+ " assert.deepEqual(await (await fetch(`${base}/api/notes`, { headers: signedIn(sam) })).json(), []);",
7570
+ "});",
7571
+ "",
7409
7572
  "test('POST /api/ask answers under the AI allowance, and answers 503 naming why the model gave no answer: the allowance drawn, no key in place, or the provider out of capacity', async () => {",
7410
7573
  " ai.prime({ text: 'Lunch is at noon.', usage: { input: 40, output: 12 } });",
7411
7574
  " const ask = (question) =>",
@@ -7446,7 +7609,6 @@ var WEB_OPEN_STEP = "Open the address the deploy answers in a browser and sign i
7446
7609
  var SUBMIT_NO_SETTING_STEP = `Call submit_manifest from your tool with the application and its manifest, ${APPLICATION_FOLDER}/${APPLICATION_MANIFEST}, naming no local_run, since the service reads no setting.`;
7447
7610
  var provisionStepLineOf = (origin, windows) => machineLineOf(provisionBareLineOf({ origin, application: "<application id>" }), windows, origin);
7448
7611
  var provisionStepOf = (origin, windows) => `Call submit_manifest from your tool with the application and its manifest, ${APPLICATION_FOLDER}/${APPLICATION_MANIFEST}, naming no local_run. Then run ${freshLineSteps2("the provision line below")}, all in ${APPLICATION_FOLDER}/. The provision line: ${provisionStepLineOf(origin, windows)}`;
7449
- var RUN_STEP_BACKGROUND = "The line runs until the application ends, so start it as a background process and read its lines";
7450
7612
  var SCHEDULED_JOB_SCHEDULE = "heartbeat";
7451
7613
  var ENTRY_PAGES = Object.freeze({
7452
7614
  database: Object.freeze({ follow: "the guide Add a database", page: "/cloud/guides/add-a-database/" }),
@@ -8080,16 +8242,20 @@ var standsAt = (io2, folder, name) => {
8080
8242
  var noFolderSentence = (name, where) => `Something that is no folder stands at ${name} in the project's root, where ${where}: move it, then run the line again. ${NOTHING_WRITTEN2}`;
8081
8243
  var scaffoldLinkSentence = (what) => `${what} stands on a link that leads outside the project's root, and the scaffold never writes through one. Put a folder in the link's place, or remove it, then run the line again. ${NOTHING_WRITTEN2}`;
8082
8244
  var listOf = (value) => value === null ? [] : Array.isArray(value) ? value : [value];
8245
+ var BRING_PAGE = "/cloud/guides/bring-an-existing-application/";
8246
+ var BRING_FOLLOW = "the guide Bring an existing application";
8247
+ var STANDS_DEPLOY_LINE = `Where ${APPLICATION_FOLDER}/ holds an application, deploy it: call deploy from your tool and run the line it answers in ${APPLICATION_FOLDER}/, as ${BRING_FOLLOW}, read_documentation's page ${BRING_PAGE}, says.`;
8083
8248
  var applicationStandsLines = (template, takeLines) => {
8084
8249
  const { takes } = TEMPLATE_FACTS[template];
8085
8250
  const lines = listOf(takeLines);
8086
8251
  const first = `The project's root holds ${APPLICATION_FOLDER}/ with files in it, so an application stands there, and ${SCAFFOLD_SUBCOMMAND} starts a new application and writes over none. ${NOTHING_WRITTEN2}`;
8087
- if (takes.length === 0 || lines.length !== takes.length) return [first];
8252
+ if (takes.length === 0 || lines.length !== takes.length) return [first, STANDS_DEPLOY_LINE];
8088
8253
  return [
8089
8254
  first,
8090
8255
  ...takes.map(
8091
8256
  (take, index) => `To add the ${take.entry} package to the application that stands, run this line at the project's root, then follow ${take.follow}, read_documentation's page ${take.page}: ${lines[index]}`
8092
- )
8257
+ ),
8258
+ STANDS_DEPLOY_LINE
8093
8259
  ];
8094
8260
  };
8095
8261
  var STAGE_NOT_OWN = `Something that is not this run's own stands at ${STAGE_FOLDER} in the project's root, the name the scaffold writes under, an earlier run's files under another template or another resource name among it: move or remove ${STAGE_FOLDER}, then run the line again. ${NOTHING_WRITTEN2}`;
@@ -8170,8 +8336,9 @@ var applicationStateOf = (io2, app) => {
8170
8336
  }
8171
8337
  };
8172
8338
  var standsWayOn = (template, takeLines) => {
8173
- const lines = applicationStandsLines(template, takeLines);
8174
- return lines.length > 1 ? `. ${lines.slice(1).join("\n")}` : `: ${SCAFFOLD_SUBCOMMAND} starts a new application and writes over none.`;
8339
+ const takes = applicationStandsLines(template, takeLines).slice(1, -1);
8340
+ return `${takes.length > 0 ? `. ${takes.join("\n")}` : `: ${SCAFFOLD_SUBCOMMAND} starts a new application and writes over none.`}
8341
+ ${STANDS_DEPLOY_LINE}`;
8175
8342
  };
8176
8343
  var stoppedCloseOf = (state, template, takeLines) => {
8177
8344
  const before = `before ${APPLICATION_FOLDER}/ was written`;
@@ -8836,44 +9003,79 @@ var usageLineOf = (subcommand) => {
8836
9003
  return `${pipedHead(subcommand)}${SERVED} ${[namedWithActs(subcommand), ...required, ...optional].join(" ")}`;
8837
9004
  };
8838
9005
  var usageHeadOf = (version) => `${TURNZERO_CLOUD_PACKAGE} ${version}: the Turn Zero Cloud command, installed from the public npm registry by your platform's tag, latest on production and next on development.`;
8839
- var usageText = (version = commandVersion()) => {
8840
- const lines = [usageHeadOf(version), "", "Usage:"];
8841
- for (const subcommand of SUBCOMMANDS) lines.push(` ${usageLineOf(subcommand)}`);
8842
- lines.push(` ${SERVED} --version`, ` ${SERVED} --help`, "", WINDOWS_SENTENCE, "", NPX_FORM_SENTENCE, "", QUOTED_PATH_SENTENCE, "");
8843
- for (const subcommand of SUBCOMMANDS) {
8844
- lines.push(`${namedWithActs(subcommand)}: ${subcommand.summary}`, "", "Options:");
8845
- for (const option of subcommand.options) lines.push(` ${shown(option)}${option.required ? " (required)" : ""}`, ` ${option.description}`);
8846
- lines.push("");
8847
- }
8848
- const reading = SUBCOMMANDS.filter((subcommand) => subcommand.credential !== null);
8849
- const unread = SUBCOMMANDS.filter((subcommand) => subcommand.credential === null && subcommand.reads === void 0).map((subcommand) => subcommand.name);
8850
- const unattended = reading.filter((subcommand) => subcommand.credential.mintedToken).map((subcommand) => subcommand.name);
8851
- const computerOnly = reading.filter((subcommand) => subcommand.credential.computerToken === true).map((subcommand) => subcommand.name);
8852
- lines.push("Input:");
8853
- for (const subcommand of reading) {
8854
- const credential = subcommand.credential;
8855
- if (credential.piped === false) continue;
8856
- lines.push(
8857
- ` ${subcommand.name} reads ${credential.aGrant} from standard input, as the line ${credential.call} answers pipes it in${credential.mintedToken ? `; and otherwise a minted token from ${MINTED_TOKEN_VARIABLE} where it is set, or this computer's token from its credential file` : credential.computerToken === true ? `; and otherwise this computer's token from its credential file, never a minted token from ${MINTED_TOKEN_VARIABLE}, whose being set refuses the run` : ""}${credential.bare !== void 0 ? `; ${credential.bare}` : ""}.`
8858
- );
8859
- }
8860
- for (const subcommand of SUBCOMMANDS) if (subcommand.reads !== void 0) lines.push(` ${subcommand.name} reads ${subcommand.reads}.`);
8861
- lines.push(` The command takes no credential in its arguments, and ${listed(unread)} ${unread.length === 1 ? "reads" : "read"} none.`, "", "Environment variables:");
8862
- for (const subcommand of SUBCOMMANDS) for (const [name, does] of subcommand.variables ?? []) lines.push(` ${name} under ${subcommand.name}, ${does}`);
8863
- if (unattended.length > 0) {
8864
- lines.push(
8865
- ` ${MINTED_TOKEN_VARIABLE} under ${listed(unattended)}, a minted token for a tool connected by a minted token or an unattended run, set for the one line from where the tool keeps it or from your CI's secret store. Where it is set, it wins over this computer's credential file.`,
8866
- ` ${ORIGIN_VARIABLE} under ${listed(unattended)}, in an unattended run, the platform's address the minted token is sent to where the line names no ${DEPLOY_OPTION.origin}. Under the token, a ${DEPLOY_OPTION.origin} the line names must be that address, or ${DEFAULT_COMMAND_ORIGIN} where this variable is unset. Where ${MINTED_TOKEN_VARIABLE} is not set and the line names no ${DEPLOY_OPTION.origin}, this variable names the address whose token the command reads from this computer's credential file${computerOnly.length > 0 ? `, under ${listed(computerOnly)} too where no grant is piped` : ""}. A run under a grant does not read it.`
8867
- );
8868
- }
8869
- lines.push(` ${USER_AGENT_VARIABLE} text appended to the command's user agent on each request.`, "", "Statuses:");
9006
+ var unreadNames = () => SUBCOMMANDS.filter((subcommand) => subcommand.credential === null && subcommand.reads === void 0).map((subcommand) => subcommand.name);
9007
+ var unattendedNames = () => SUBCOMMANDS.filter((subcommand) => subcommand.credential?.mintedToken === true).map((subcommand) => subcommand.name);
9008
+ var computerOnlyNames = () => SUBCOMMANDS.filter((subcommand) => subcommand.credential?.computerToken === true).map((subcommand) => subcommand.name);
9009
+ var sectionLinesOf = (subcommand) => [
9010
+ `${namedWithActs(subcommand)}: ${subcommand.summary}`,
9011
+ "",
9012
+ "Options:",
9013
+ ...subcommand.options.flatMap((option) => [` ${shown(option)}${option.required ? " (required)" : ""}`, ` ${option.description}`]),
9014
+ ""
9015
+ ];
9016
+ var pipedLinesOf = (subcommand) => {
9017
+ const credential = subcommand.credential;
9018
+ if (credential === null || credential.piped === false) return [];
9019
+ return [
9020
+ ` ${subcommand.name} reads ${credential.aGrant} from standard input, as the line ${credential.call} answers pipes it in${credential.mintedToken ? `; and otherwise a minted token from ${MINTED_TOKEN_VARIABLE} where it is set, or this computer's token from its credential file` : credential.computerToken === true ? `; and otherwise this computer's token from its credential file, never a minted token from ${MINTED_TOKEN_VARIABLE}, whose being set refuses the run` : ""}${credential.bare !== void 0 ? `; ${credential.bare}` : ""}.`
9021
+ ];
9022
+ };
9023
+ var readsLinesOf = (subcommand) => subcommand.reads !== void 0 ? [` ${subcommand.name} reads ${subcommand.reads}.`] : [];
9024
+ var noCredentialLine = () => {
9025
+ const unread = unreadNames();
9026
+ return ` The command takes no credential in its arguments, and ${listed(unread)} ${unread.length === 1 ? "reads" : "read"} none.`;
9027
+ };
9028
+ var mintedTokenVariableLine = () => ` ${MINTED_TOKEN_VARIABLE} under ${listed(unattendedNames())}, a minted token for a tool connected by a minted token or an unattended run, set for the one line from where the tool keeps it or from your CI's secret store. Where it is set, it wins over this computer's credential file.`;
9029
+ var originVariableLine = () => {
9030
+ const computerOnly = computerOnlyNames();
9031
+ return ` ${ORIGIN_VARIABLE} under ${listed(unattendedNames())}, in an unattended run, the platform's address the minted token is sent to where the line names no ${DEPLOY_OPTION.origin}. Under the token, a ${DEPLOY_OPTION.origin} the line names must be that address, or ${DEFAULT_COMMAND_ORIGIN} where this variable is unset. Where ${MINTED_TOKEN_VARIABLE} is not set and the line names no ${DEPLOY_OPTION.origin}, this variable names the address whose token the command reads from this computer's credential file${computerOnly.length > 0 ? `, under ${listed(computerOnly)} too where no grant is piped` : ""}. A run under a grant does not read it.`;
9032
+ };
9033
+ var USER_AGENT_LINE = ` ${USER_AGENT_VARIABLE} text appended to the command's user agent on each request.`;
9034
+ var ownVariableLinesOf = (subcommand) => (subcommand.variables ?? []).map(([name, does]) => ` ${name} under ${subcommand.name}, ${does}`);
9035
+ var statusLineOf = (status, meaning, clauses, also) => ` ${COMMAND_STATUS[status]} ${meaning}${clauses.length > 0 ? `: ${clauses.join("; ")}` : ""}.${also !== void 0 ? ` ${also}` : ""}`;
9036
+ var noteLinesOf2 = (subcommand) => (subcommand.note ?? []).flatMap((paragraph) => ["", paragraph]);
9037
+ var usageBlockOf = (subcommandHelp) => [
9038
+ "Usage:",
9039
+ ...SUBCOMMANDS.map((subcommand) => ` ${usageLineOf(subcommand)}`),
9040
+ ` ${SERVED} --version`,
9041
+ ` ${SERVED} --help`,
9042
+ ...subcommandHelp ? [` ${SERVED} <subcommand> --help`] : []
9043
+ ];
9044
+ var sharedSentenceLines = () => [WINDOWS_SENTENCE, "", NPX_FORM_SENTENCE, "", QUOTED_PATH_SENTENCE];
9045
+ var SUBCOMMAND_HELP_SENTENCE = `Run ${SERVED} <subcommand> --help for that subcommand's options, credential, variables, statuses, and notes.`;
9046
+ var SHARED_HELP_SENTENCE = `Run ${SERVED} --help for every subcommand and the sentences they share: a line's Windows form, its npx form, and a path between double quotes.`;
9047
+ var shortUsageText = (version = commandVersion()) => [
9048
+ usageHeadOf(version),
9049
+ "",
9050
+ ...usageBlockOf(true),
9051
+ "",
9052
+ ...sharedSentenceLines(),
9053
+ "",
9054
+ "Statuses:",
9055
+ ...STATUS_MEANINGS.map(([status, meaning, also]) => statusLineOf(status, meaning, [], also)),
9056
+ "",
9057
+ "Subcommands:",
9058
+ ...SUBCOMMANDS.map((subcommand) => ` ${namedWithActs(subcommand)}: ${subcommand.summary}`),
9059
+ "",
9060
+ SUBCOMMAND_HELP_SENTENCE
9061
+ ].join("\n");
9062
+ var subcommandHelpOf = (subcommand, version = commandVersion()) => {
9063
+ const lines = [usageHeadOf(version), "", "Usage:", ` ${usageLineOf(subcommand)}`, "", ...sectionLinesOf(subcommand)];
9064
+ lines.push("Input:", ...pipedLinesOf(subcommand), ...readsLinesOf(subcommand), noCredentialLine(), "", "Environment variables:", ...ownVariableLinesOf(subcommand));
9065
+ if (unattendedNames().includes(subcommand.name)) lines.push(mintedTokenVariableLine());
9066
+ if (unattendedNames().length > 0 && (unattendedNames().includes(subcommand.name) || computerOnlyNames().includes(subcommand.name))) lines.push(originVariableLine());
9067
+ lines.push(USER_AGENT_LINE, "", "Statuses:");
8870
9068
  for (const [status, meaning, also] of STATUS_MEANINGS) {
8871
- const clauses = SUBCOMMANDS.flatMap((subcommand) => subcommand.statuses[status] !== void 0 ? [`under ${subcommand.name}, ${subcommand.statuses[status]}`] : []);
8872
- lines.push(` ${COMMAND_STATUS[status]} ${meaning}${clauses.length > 0 ? `: ${clauses.join("; ")}` : ""}.${also !== void 0 ? ` ${also}` : ""}`);
9069
+ const clause = subcommand.statuses[status];
9070
+ if (clause !== void 0 || also !== void 0) lines.push(statusLineOf(status, meaning, clause !== void 0 ? [`under ${subcommand.name}, ${clause}`] : [], also));
8873
9071
  }
8874
- for (const subcommand of SUBCOMMANDS) for (const paragraph of subcommand.note ?? []) lines.push("", paragraph);
9072
+ lines.push(...noteLinesOf2(subcommand), "", SHARED_HELP_SENTENCE);
8875
9073
  return lines.join("\n");
8876
9074
  };
9075
+ var subcommandHelpText = (name, version = commandVersion()) => {
9076
+ const subcommand = SUBCOMMANDS.find((entry) => entry.name === name);
9077
+ return subcommand === void 0 ? null : subcommandHelpOf(subcommand, version);
9078
+ };
8877
9079
  var grantArgumentSentence = () => "An argument holds a grant, and the command never takes a grant from its arguments: it reads the grant from standard input, as the line your tool's call answers pipes it in. Run the line the call answered as given. Nothing was sent and nothing was started.";
8878
9080
  var tokenArgumentSentence = () => `An argument holds a minted token, and the command never takes a credential from its arguments: it reads a minted token from ${MINTED_TOKEN_VARIABLE} alone, which a tool connected by a minted token, or an unattended run, sets for the one line from where the tool keeps the token or from your CI's secret store. Nothing was sent and nothing was started.`;
8879
9081
  var NOTHING_STARTED2 = "Nothing was started.";
@@ -8894,21 +9096,21 @@ var valuePlacesOf = (argv2) => {
8894
9096
  }
8895
9097
  return places;
8896
9098
  };
9099
+ var noSubcommandSentence = (first) => `${first === void 0 ? "No subcommand was named" : `The first argument is not a subcommand of ${TURNZERO_CLOUD_PACKAGE}`}: the subcommands are ${SUBCOMMANDS.map((entry) => entry.name).join(", ")}. ${NOTHING_STARTED2}`;
8897
9100
  var parseArguments = (argv2, onWindows2 = ON_WINDOWS) => {
8898
9101
  if (argv2.some((argument) => argument.includes(UPLOAD_GRANT_PREFIX))) return { kind: "refused", sentence: grantArgumentSentence() };
8899
9102
  if (argv2.some((argument) => argument.includes(MINTED_TOKEN_PREFIX))) return { kind: "refused", sentence: tokenArgumentSentence() };
8900
9103
  const values = valuePlacesOf(argv2);
8901
9104
  const standsAlone = (words) => argv2.some((argument, index) => words.includes(argument) && !values.has(index));
8902
- if (standsAlone(["--help", "-h"])) return { kind: "help" };
8903
- if (standsAlone(["--version"])) return { kind: "version" };
8904
9105
  const [first, ...afterName] = argv2;
8905
9106
  const subcommand = SUBCOMMANDS.find((entry) => entry.name === first);
8906
- if (subcommand === void 0) {
8907
- return {
8908
- kind: "refused",
8909
- sentence: `${first === void 0 ? "No subcommand was named" : `The first argument is not a subcommand of ${TURNZERO_CLOUD_PACKAGE}`}: the subcommands are ${SUBCOMMANDS.map((entry) => entry.name).join(", ")}. ${NOTHING_STARTED2}`
8910
- };
9107
+ if (standsAlone(["--help", "-h"])) {
9108
+ if (subcommand !== void 0) return { kind: "help", subcommand };
9109
+ if (first.startsWith("-")) return { kind: "help" };
9110
+ return { kind: "refused", sentence: noSubcommandSentence(first) };
8911
9111
  }
9112
+ if (standsAlone(["--version"])) return { kind: "version" };
9113
+ if (subcommand === void 0) return { kind: "refused", sentence: noSubcommandSentence(first) };
8912
9114
  let act = null;
8913
9115
  let rest = afterName;
8914
9116
  if (subcommand.acts !== void 0) {
@@ -8968,7 +9170,7 @@ async function runGuarded(argv2, io2) {
8968
9170
  }
8969
9171
  const parsed = parseArguments(argv2, io2.windows ?? ON_WINDOWS);
8970
9172
  if (parsed.kind === "help") {
8971
- io2.print(usageText(version));
9173
+ io2.print((parsed.subcommand === void 0 ? null : subcommandHelpText(parsed.subcommand.name, version)) ?? shortUsageText(version));
8972
9174
  return COMMAND_STATUS.done;
8973
9175
  }
8974
9176
  if (parsed.kind === "version") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turnzero",
3
- "version": "0.18.2-next.1",
3
+ "version": "0.18.3-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",