turnzero 0.18.3 → 0.20.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Turn Zero Cloud connects to your AI coding tool through a Model Context Protocol (MCP) server. Once connected, your AI can code, build, and test backends, connect them to your app, and host your code in the cloud.
4
4
 
5
- Turn Zero Cloud is for developers who build with an AI coding tool and do not want to run servers. Your tool writes, builds, and tests the application on your computer. The platform builds it, runs it in production at the application's address, and records its logs, counters, and usage. To start, connect your AI coding tool: https://turnzero.ai/cloud/getting-started/connect-your-tool/
5
+ Your AI coding tool writes the app; Turn Zero Cloud runs it, with hosting, database, sign-in and scaling included. Your tool writes, builds, and tests the application on your computer. The platform builds it, runs it in production at the application's address, and records its logs, counters, and usage. To start, connect your AI coding tool: https://turnzero.ai/cloud/getting-started/connect-your-tool/
6
6
 
7
7
  ## What this package is
8
8
 
@@ -23,7 +23,7 @@ Install it once with `npm install -g turnzero`, then run `turnzero login` once i
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
25
  - `login` logs this computer in through your browser, `whoami` names the account its token acts as, and `logout` revokes its token.
26
- - `authorize` gives this computer a token through your tool, where you ask your tool to.
26
+ - `authorize` gives this computer a token through your signed-in tool, which then tells you it did.
27
27
  - `token request` and `token claim` get a token for another computer or for an unattended run.
28
28
 
29
29
  Every subcommand's options, statuses, and output are on the reference page: https://turnzero.ai/cloud/reference/turnzero-command/
@@ -222,7 +222,8 @@ var SCAFFOLD_OPTION = Object.freeze({
222
222
  singular: "--singular",
223
223
  fields: "--fields",
224
224
  path: DEPLOY_OPTION.path,
225
- origin: DEPLOY_OPTION.origin
225
+ origin: DEPLOY_OPTION.origin,
226
+ application: DEPLOY_OPTION.application
226
227
  });
227
228
  var SCAFFOLD_NAME_FORM = /^[a-z][a-z0-9]{0,29}$/;
228
229
  var SCAFFOLD_FIELD_NAME_FORM = /^[a-z][a-z0-9_]{0,29}$/;
@@ -419,6 +420,10 @@ var originOf = (text) => {
419
420
  if (url.protocol === "http:" && LOOPBACK_HOSTNAMES.includes(url.hostname)) return url.origin;
420
421
  return null;
421
422
  };
423
+ var originReadOf = (value) => {
424
+ const held = (value ?? "").trim();
425
+ return held === "" ? DEFAULT_COMMAND_ORIGIN : originOf(held) ?? held;
426
+ };
422
427
  var commandOriginOf = (text) => {
423
428
  const origin = originOf(text);
424
429
  if (origin !== null) return { origin };
@@ -905,10 +910,14 @@ var variableReadSentence = (origin, standing) => {
905
910
  };
906
911
  var fileReadSentence = (origin) => `The credential this run read was this computer's token for ${origin}, from its credential file: where the platform no longer accepts it, run ${LOGIN_SUBCOMMAND}${originWordsOf(origin)} to authorize this computer again.`;
907
912
  var noTokenSentence = (origin, expiredAt) => expiredAt === null ? `No credential was read, so nothing was sent: ${MINTED_TOKEN_VARIABLE} is not set, and this computer's credential file holds no token for ${origin}.` : `No credential was read, so nothing was sent: ${MINTED_TOKEN_VARIABLE} is not set, and this computer's token for ${origin} in its credential file expired at ${expiredAt}.`;
908
- var VARIABLE_ROUTE_SENTENCES = `A tool connected by a minted token, or a job, sets ${MINTED_TOKEN_VARIABLE} from where its token is kept, never by writing the value in a line, and ${ORIGIN_VARIABLE} to the address that minted the token where that is not ${DEFAULT_COMMAND_ORIGIN}, then runs the line again. Such a tool cannot use ${MINT_TOKEN_ACTION}, which takes a sign-in alone. A tool sets ${MINTED_TOKEN_VARIABLE} for the deploy or check line alone and unsets it once that line ends.`;
913
+ var VARIABLE_ROUTE_SENTENCES = `A tool connected by a minted token, or a job, sets ${MINTED_TOKEN_VARIABLE} from where its token is kept, never by writing the value in a line, and ${ORIGIN_VARIABLE} to the address that minted the token where that is not ${DEFAULT_COMMAND_ORIGIN}, then runs the line again. Such a tool cannot use ${MINT_TOKEN_ACTION}, which takes a sign-in alone. A tool sets ${MINTED_TOKEN_VARIABLE} for the deploy, check, take, or scaffold line alone and unsets it once that line ends.`;
909
914
  var LOGIN_ROUTE_OPENER = "A person logs this computer in once, in a terminal of their own, and its token then stays on this computer for 30 days:";
910
915
  var DEPLOY_FILES_ROUTE_SENTENCE = "A folder within the caps of your tool's deploy_files call deploys now through deploy_files, with no login.";
911
- var loginRouteLines = (io2, origin) => [LOGIN_ROUTE_OPENER, ...loginLinesFor(io2, origin), DEPLOY_FILES_ROUTE_SENTENCE];
916
+ var loginRouteLines = (io2, origin, deployFiles = true) => [
917
+ LOGIN_ROUTE_OPENER,
918
+ ...loginLinesFor(io2, origin),
919
+ ...deployFiles ? [DEPLOY_FILES_ROUTE_SENTENCE] : []
920
+ ];
912
921
  var loginRouteRefusalOf = (io2, closing) => {
913
922
  const place = credentialFileOf(io2);
914
923
  const tag = runTagOf();
@@ -1364,7 +1373,7 @@ var AUTHORIZE_ROW = Object.freeze({
1364
1373
  },
1365
1374
  variables: [[ORIGIN_VARIABLE, AUTHORIZATION_ORIGIN_VARIABLE_LINE]],
1366
1375
  note: [
1367
- `Authorizing this computer takes three acts once for each platform address, where you ask your tool to authorize it; login, run in a terminal of your own, is your own way. ${AUTHORIZE_SUBCOMMAND} run with nothing piped prints a challenge and the ${MINT_TOKEN_ACTION} call to make; your tool makes that call through the connection you signed in with, and runs the line it answers once, as given, on this computer, which keeps the token.`,
1376
+ `Authorizing this computer takes three acts once for each platform address, which your signed-in tool makes itself and then reports to you; login, run in a terminal of your own, is your own way. ${AUTHORIZE_SUBCOMMAND} run with nothing piped prints a challenge and the ${MINT_TOKEN_ACTION} call to make; your tool makes that call through the connection you signed in with, and runs the line it answers once, as given, on this computer, which keeps the token.`,
1368
1377
  `The token is account-scoped and works from any computer until it expires or revoke_token ends it: keep it as you would a password, list_tokens shows it by this computer's host name, and logout ends it. The credential file is ${CREDENTIAL_FILE_WORDS}.`
1369
1378
  ],
1370
1379
  afterOwnError: AUTHORIZE_AFTER_OWN_ERROR,
@@ -2922,6 +2931,76 @@ function healthPathReadingOf(bytes, reading, recorded) {
2922
2931
  };
2923
2932
  }
2924
2933
 
2934
+ // src/turnzero_cloud_command/application_manifest.ts
2935
+ import { join as join3, relative as relative3 } from "node:path";
2936
+ var FOLDER_MANIFEST = "manifest.json";
2937
+ var APPLICATION_MEMBER = "application";
2938
+ var BYTE_ORDER_MARK3 = "\uFEFF";
2939
+ var jsonTextOf = (bytes) => {
2940
+ const text = bytes.toString("utf8");
2941
+ return text.startsWith(BYTE_ORDER_MARK3) ? text.slice(BYTE_ORDER_MARK3.length) : text;
2942
+ };
2943
+ var applicationManifestPartialOf = (at) => `${at}.partial`;
2944
+ var NOT_BOUND = Object.freeze({ kind: "unbound", manifest: null });
2945
+ var folderBindingOf = (io2, path) => {
2946
+ let text;
2947
+ let at;
2948
+ try {
2949
+ const folder = io2.realPath(path);
2950
+ if (folder === null) return NOT_BOUND;
2951
+ at = join3(folder, FOLDER_MANIFEST);
2952
+ const real = io2.realPath(at);
2953
+ if (real === null || relative3(at, real) !== "") return NOT_BOUND;
2954
+ const standing = io2.stat(at);
2955
+ if (standing === null || standing.folder) return NOT_BOUND;
2956
+ text = jsonTextOf(io2.readFile(at));
2957
+ } catch {
2958
+ return NOT_BOUND;
2959
+ }
2960
+ let parsed;
2961
+ try {
2962
+ parsed = JSON.parse(text);
2963
+ } catch {
2964
+ return NOT_BOUND;
2965
+ }
2966
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return NOT_BOUND;
2967
+ const members = parsed;
2968
+ if (!Object.prototype.hasOwnProperty.call(members, APPLICATION_MEMBER)) return { kind: "unbound", manifest: { at, parsed: members } };
2969
+ const application = members[APPLICATION_MEMBER];
2970
+ return typeof application === "string" && UPLOAD_ID.test(application) ? { kind: "bound", application } : { kind: "off_form" };
2971
+ };
2972
+ var BOUND_LINE_HEAD = "bound: ";
2973
+ var boundLineOf = (application) => `${BOUND_LINE_HEAD}${FOLDER_MANIFEST} now names application ${application}; a deploy or check line naming another application is refused`;
2974
+ var BIND_WRITE_FAILED_LINE = `${FOLDER_MANIFEST} could not be written to bind this folder to its application, so the deploy goes on unbound, and the next deploy writes it again.`;
2975
+ var boundElsewhereSentence = (bound) => `${FOLDER_MANIFEST} binds this folder to application ${bound}, and this line names another.`;
2976
+ var OFF_FORM_SENTENCE = `${FOLDER_MANIFEST}'s ${APPLICATION_MEMBER} member is not an application id, a lowercase UUID, so it binds this folder to no application a line can name.`;
2977
+ var BINDING_OWNER_ASK = `Ask the account's owner; where they mean this folder for that application, change or remove the manifest's ${APPLICATION_MEMBER} member, then run the line again.`;
2978
+ var HASH_BINDING_OWNER_ASK = `Ask the account's owner which application this folder deploys to; change or remove the manifest's ${APPLICATION_MEMBER} member to match, then run the line again.`;
2979
+ var bindingRefusalOf = (binding, application, closing) => {
2980
+ if (binding.kind === "unbound" || binding.kind === "bound" && binding.application === application) return null;
2981
+ const first = binding.kind === "bound" ? boundElsewhereSentence(binding.application) : OFF_FORM_SENTENCE;
2982
+ return `${first} ${closing} ${BINDING_OWNER_ASK}`;
2983
+ };
2984
+ var manifestTextWithMember = (parsed, member, value) => `${JSON.stringify({ ...parsed, [member]: value }, null, 2)}
2985
+ `;
2986
+ var writeManifestMember = (io2, manifest, member, value) => {
2987
+ const partial = applicationManifestPartialOf(manifest.at);
2988
+ let made = false;
2989
+ try {
2990
+ if (io2.stat(partial)?.folder === false) io2.remove(partial);
2991
+ made = io2.stat(partial) === null;
2992
+ io2.writeNewFile(partial, manifestTextWithMember(manifest.parsed, member, value));
2993
+ io2.rename(partial, manifest.at);
2994
+ } catch {
2995
+ try {
2996
+ if (made) io2.remove(partial);
2997
+ } catch {
2998
+ }
2999
+ return false;
3000
+ }
3001
+ return true;
3002
+ };
3003
+
2925
3004
  // src/turnzero_cloud_command/deploy.ts
2926
3005
  import { createHash as createHash2 } from "node:crypto";
2927
3006
  import { readFileSync as readFileSync4, realpathSync, statSync as statSync3 } from "node:fs";
@@ -2929,7 +3008,7 @@ import { resolve as resolve4 } from "node:path";
2929
3008
 
2930
3009
  // src/turnzero_cloud_command/commit.ts
2931
3010
  import { readFileSync as readFileSync2, statSync } from "node:fs";
2932
- import { dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
3011
+ import { dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
2933
3012
  var START_COMMIT_FORM = /^[0-9a-f]{7,64}$/;
2934
3013
  var GIT_ENTRY = ".git";
2935
3014
  var SYMBOLIC_DEPTH_MAX = 5;
@@ -2959,7 +3038,7 @@ var firstLineOf = (text) => (text.split(/\r?\n/, 1)[0] ?? "").trim();
2959
3038
  var gitFolderOf = (folder, reads = FILE_SYSTEM_READS) => {
2960
3039
  let dir = resolve3(folder);
2961
3040
  for (; ; ) {
2962
- const entry = join3(dir, GIT_ENTRY);
3041
+ const entry = join4(dir, GIT_ENTRY);
2963
3042
  const kind = reads.kind(entry);
2964
3043
  if (kind === "folder") return entry;
2965
3044
  if (kind === "file") {
@@ -2977,7 +3056,7 @@ var gitFolderOf = (folder, reads = FILE_SYSTEM_READS) => {
2977
3056
  }
2978
3057
  };
2979
3058
  var sharedGitFolderOf = (gitFolder, reads = FILE_SYSTEM_READS) => {
2980
- const text = reads.text(join3(gitFolder, "commondir"));
3059
+ const text = reads.text(join4(gitFolder, "commondir"));
2981
3060
  const named = text === null ? "" : firstLineOf(text);
2982
3061
  if (named === "") return gitFolder;
2983
3062
  const shared = resolve3(gitFolder, named);
@@ -2987,9 +3066,9 @@ var followable = (ref) => ref.startsWith("refs/") && ref.split("/").every((segme
2987
3066
  var refValueOf = (reads, gitFolder, sharedFolder, ref, depth) => {
2988
3067
  if (depth > SYMBOLIC_DEPTH_MAX || !followable(ref)) return null;
2989
3068
  const home = PER_WORKTREE_REFS.some((prefix) => ref.startsWith(prefix)) ? gitFolder : sharedFolder;
2990
- const loose = reads.text(join3(home, ...ref.split("/")));
3069
+ const loose = reads.text(join4(home, ...ref.split("/")));
2991
3070
  if (loose !== null) return valueOf(reads, gitFolder, sharedFolder, firstLineOf(loose), depth + 1);
2992
- const packed = reads.text(join3(sharedFolder, "packed-refs"));
3071
+ const packed = reads.text(join4(sharedFolder, "packed-refs"));
2993
3072
  if (packed === null) return null;
2994
3073
  for (const line of packed.split(/\r?\n/)) {
2995
3074
  if (line.startsWith("#") || line.startsWith("^")) continue;
@@ -3006,7 +3085,7 @@ var headCommitOf = (folder, reads = FILE_SYSTEM_READS) => {
3006
3085
  try {
3007
3086
  const gitFolder = gitFolderOf(folder, reads);
3008
3087
  if (gitFolder === null) return null;
3009
- const head = reads.text(join3(gitFolder, "HEAD"));
3088
+ const head = reads.text(join4(gitFolder, "HEAD"));
3010
3089
  if (head === null) return null;
3011
3090
  const sharedFolder = sharedGitFolderOf(gitFolder, reads);
3012
3091
  if (sharedFolder === null) return null;
@@ -3105,7 +3184,7 @@ async function writeAgain(io2, what, write) {
3105
3184
 
3106
3185
  // src/turnzero_cloud_command/zip.ts
3107
3186
  import { readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
3108
- import { join as join4 } from "node:path";
3187
+ import { join as join5 } from "node:path";
3109
3188
  import * as zlib from "node:zlib";
3110
3189
  import { deflateRawSync, inflateRawSync as inflateRawSync2 } from "node:zlib";
3111
3190
  var AI_TOOL_NAMES = Object.freeze([
@@ -3144,7 +3223,7 @@ var walkFolder = (root, limit, leftOut) => {
3144
3223
  leftOut?.push(entry.isDirectory() ? `${name}/` : name);
3145
3224
  continue;
3146
3225
  }
3147
- const path = join4(folder.path, entry.name);
3226
+ const path = join5(folder.path, entry.name);
3148
3227
  if (entry.isSymbolicLink()) throw new ZipRefusal(linkRefusalSentence(name));
3149
3228
  if (entry.isDirectory()) {
3150
3229
  pending.push({ path, name });
@@ -3234,7 +3313,7 @@ var NO_PACKAGE_SENTENCE = "The folder holds no package.json at its root, so it i
3234
3313
  function zipFolder(root, seams = {}) {
3235
3314
  let rootFile = false;
3236
3315
  try {
3237
- rootFile = statSync2(join4(root, "package.json")).isFile();
3316
+ rootFile = statSync2(join5(root, "package.json")).isFile();
3238
3317
  } catch {
3239
3318
  rootFile = false;
3240
3319
  }
@@ -3440,6 +3519,7 @@ async function prepareDeploy(io2, options, zipSha256Value, bearer, userAgent, re
3440
3519
  return prepared;
3441
3520
  }
3442
3521
  var NOTHING_STARTED = "Nothing was started.";
3522
+ var BINDING_NOTHING_SENT = "Nothing further was read and nothing was sent.";
3443
3523
  var NOTHING_UPLOADED_OR_STARTED = "Nothing was uploaded and nothing was started.";
3444
3524
  var healthPathRefusalOf = (prepared, zip, places) => {
3445
3525
  if (prepared.healthPathToRead === void 0 || places === null) return null;
@@ -3536,6 +3616,12 @@ async function deploy(named, io2, flags = /* @__PURE__ */ new Set()) {
3536
3616
  }
3537
3617
  const origin = sent.origin;
3538
3618
  const refused = () => credential.source === "credential_file" ? fileReadSentence(origin) : variableReadSentence(origin, fileStandingOf(io2, origin, io2.print));
3619
+ const binding = pathKindOf(options.path) === "folder" ? folderBindingOf(io2, options.path) : null;
3620
+ const unbound = binding === null ? null : bindingRefusalOf(binding, options.application, BINDING_NOTHING_SENT);
3621
+ if (unbound !== null) {
3622
+ io2.print(unbound);
3623
+ return PROGRESS_EXIT_STATUS.nothing_started;
3624
+ }
3539
3625
  const zipped = zipOf(options.path, io2, NOTHING_STARTED, named[DEPLOY_OPTION.path] !== void 0);
3540
3626
  if ("refusal" in zipped) {
3541
3627
  io2.print(zipped.refusal);
@@ -3557,6 +3643,9 @@ async function deploy(named, io2, flags = /* @__PURE__ */ new Set()) {
3557
3643
  return PROGRESS_EXIT_STATUS.nothing_started;
3558
3644
  }
3559
3645
  io2.print(preparedLineOf(prepared));
3646
+ if (binding?.kind === "unbound" && binding.manifest !== null) {
3647
+ io2.print(writeManifestMember(io2, binding.manifest, APPLICATION_MEMBER, options.application) ? boundLineOf(options.application) : BIND_WRITE_FAILED_LINE);
3648
+ }
3560
3649
  const { upload, grant, environment } = prepared;
3561
3650
  const headers = { authorization: `Bearer ${grant}`, "user-agent": agent.userAgent };
3562
3651
  const stop = commandStopSentence(options.application, environment);
@@ -3667,7 +3756,7 @@ async function deploy(named, io2, flags = /* @__PURE__ */ new Set()) {
3667
3756
  }
3668
3757
  if (read2.status !== 200 || !read2.type.startsWith("text/plain")) {
3669
3758
  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)];
3759
+ const said = refusal === null ? [notThePlatformLine(PROGRESS_READ_ACT, read2.status)] : read2.status >= PLATFORM_FAILURE_STATUS ? failureLines(PROGRESS_READ_ACT, read2.status, refusal) : refusalLines(PROGRESS_READ_ACT, read2.status, refusal);
3671
3760
  for (const line of said) io2.print(line);
3672
3761
  io2.print(stop);
3673
3762
  return PROGRESS_EXIT_STATUS.refused;
@@ -3702,19 +3791,21 @@ var DEPLOY_ROW = Object.freeze({
3702
3791
  done: `the version deployed, or the deploy started where ${DEPLOY_NO_WAIT_VARIABLE} is set`,
3703
3792
  failed: "the deploy failed",
3704
3793
  unknown: "the wait stopped, or the start drew no answer: the deploy may go on, and read_status reads it",
3705
- nothing_started: "an option, the credential, or the folder was refused, no credential was read, the platform was too old, no usable upload was answered, no zipped file names the health path, the upload or start was refused, or the upload drew no answer"
3794
+ nothing_started: "an option, credential, folder, or the folder's binding was refused, no credential was read, the platform was too old, no usable upload was answered, no zipped file names the health path, the upload or start was refused, or the upload drew no answer"
3706
3795
  },
3707
3796
  variables: [[DEPLOY_NO_WAIT_VARIABLE, "any value returns the command once the deploy has started, without waiting for it."]],
3708
3797
  note: [
3709
3798
  `A deploy is one line, which carries no credential: run the line your tool's deploy call answers, or ${DEPLOY_SUBCOMMAND} ${DEPLOY_OPTION.application} <application id>, from the application's folder. It reads no standard input. On an authorized computer it runs under this computer's token, which ${LOGIN_SUBCOMMAND} or ${AUTHORIZE_SUBCOMMAND} keeps in the credential file for each platform address, and sends it only to the address it is kept under: ${DEPLOY_OPTION.origin} where the line names one, else the address ${ORIGIN_VARIABLE} holds, else ${DEFAULT_COMMAND_ORIGIN}.`,
3710
3799
  `On a computer with no token for that address, or one that has expired, it sends nothing, ends 3, and prints two routes, in this order. A tool connected by a minted token, or a job with no tool, such as a CI job's, sets ${MINTED_TOKEN_VARIABLE} from where its token is kept, such as the job's secret store, never by writing the value in a line. It sets ${ORIGIN_VARIABLE} to the address that minted the token where that is not ${DEFAULT_COMMAND_ORIGIN}, then runs the deploy line again. Such a tool cannot use ${MINT_TOKEN_ACTION}, which takes a sign-in alone.`,
3711
3800
  `Otherwise a person logs this computer in once with ${LOGIN_SUBCOMMAND}, in a terminal of their own, then runs the deploy line again; a folder within the caps of the tool's deploy_files call deploys now through deploy_files, with no login. The credential file is ${CREDENTIAL_FILE_WORDS}.`,
3712
- `A tool sets ${MINTED_TOKEN_VARIABLE} for the deploy or check line alone and unsets it once that line ends, since the command cannot unset a variable of the shell that started it. A value a job's environment already supplies stays as it stands. A job's token is minted for the application, with an expiry. The variable's token wins over the credential file: the command makes the preparing call under that token, and sends the token in no other request.`,
3801
+ `A tool sets ${MINTED_TOKEN_VARIABLE} for the deploy, check, take, or scaffold line alone and unsets it once that line ends, since the command cannot unset a variable of the shell that started it. A value a job's environment already supplies stays as it stands. A job's token is minted for the application, with an expiry. The variable's token wins over the credential file: the command makes the preparing call under that token, and sends the token in no other request.`,
3713
3802
  `Under the variable, the command sends its token only to the address ${ORIGIN_VARIABLE} holds, or to ${DEFAULT_COMMAND_ORIGIN} where that variable is unset: under the token, ${DEPLOY_OPTION.origin} may name that address alone, and where the line names none the command addresses it. ${DEPLOY_OPTION.env} names the environment where a line names one, ${COMMAND_ENVIRONMENTS.join(" or ")}; where none is named, the platform chooses, and the command deploys to the environment the platform answers.`,
3714
3803
  `A progress read answered 502, 503, or 504, which is often temporary, is made again, at most three more times, and an upload answered 503 credential_lookup_busy, which wrote nothing, is made again after the wait it names, at most twice.`,
3715
3804
  `After its zip line, ${DEPLOY_SUBCOMMAND} prints the folder's top-level entries the zip holds on one line, at most ten and then the count of the rest, and ${HASH_SUBCOMMAND} ${HASH_OPTION.list} names every file; no such line prints for a .zip file.`,
3716
3805
  `Then it prints the paths the zip left out, a warning for a .zip file at the folder's root, a warning for a file that looks like a key or a credential, and a warning for a health path that no file names, package files and documentation set aside, each on a line of its own and only where there is something to name. The health warning refuses nothing.`,
3717
- `After the preparing call, where the platform's answer names the recorded manifest's health path and says the environment serves no version, and no file of the folder's zip names that path, ${DEPLOY_SUBCOMMAND} prints the platform's own refusal, then that nothing was uploaded and nothing was started, and ends 3 with no ${PREPARED_LINE_HEAD.trimEnd()} line, as the start would refuse it. A .zip file the line names is not read for it, and the start's own refusal stands.`
3806
+ `After the preparing call, where the platform's answer names the recorded manifest's health path and says the environment serves no version, and no file of the folder's zip names that path, ${DEPLOY_SUBCOMMAND} prints the platform's own refusal, then that nothing was uploaded and nothing was started, and ends 3 with no ${PREPARED_LINE_HEAD.trimEnd()} line, as the start would refuse it. A .zip file the line names is not read for it, and the start's own refusal stands.`,
3807
+ `Before its zip, ${DEPLOY_SUBCOMMAND} reads the folder's manifest.json. Where its application member names another application than ${DEPLOY_OPTION.application}, or is not an application id, the deploy ends 3 before anything is zipped or sent. To deploy the folder to another application on purpose, the account's owner changes or removes that member.`,
3808
+ `Where the manifest holds no application member, ${DEPLOY_SUBCOMMAND} writes ${DEPLOY_OPTION.application}'s id into it after the ${PREPARED_LINE_HEAD.trimEnd()} line and prints a ${BOUND_LINE_HEAD.trimEnd()} line, so later deploy and check lines naming another application are refused. A write that fails prints one line and stops nothing. A .zip file the line names is not read for it.`
3718
3809
  ],
3719
3810
  afterOwnError: DEPLOY_AFTER_OWN_ERROR,
3720
3811
  run: (call, io2) => deploy(call.named, io2, call.flags)
@@ -3723,6 +3814,7 @@ var DEPLOY_ROW = Object.freeze({
3723
3814
  // src/turnzero_cloud_command/check.ts
3724
3815
  var NOTHING_SENT_OR_CHECKED = "Nothing was sent and nothing was checked.";
3725
3816
  var NOTHING_CHECKED = "Nothing was checked.";
3817
+ var BINDING_NOTHING_CHECKED = "Nothing further was read, and nothing was sent or checked.";
3726
3818
  var REFUSED_LINE_HEAD = "refused: ";
3727
3819
  var MANIFEST_LINE_HEAD = "manifest: ";
3728
3820
  var CHECKED_LINE_HEAD = "checked: ";
@@ -3914,6 +4006,12 @@ async function check(named, io2) {
3914
4006
  }
3915
4007
  const origin = sent.origin;
3916
4008
  const refused = () => credential.source === "credential_file" ? fileReadSentence(origin) : variableReadSentence(origin, fileStandingOf(io2, origin, io2.print));
4009
+ const binding = pathKindOf(options.path) === "folder" ? folderBindingOf(io2, options.path) : null;
4010
+ const unbound = binding === null ? null : bindingRefusalOf(binding, options.application, BINDING_NOTHING_CHECKED);
4011
+ if (unbound !== null) {
4012
+ io2.print(unbound);
4013
+ return COMMAND_STATUS.nothing_started;
4014
+ }
3917
4015
  const zipped = zipOf(options.path, io2, NOTHING_CHECKED, named[CHECK_OPTION.path] !== void 0, null, "checked");
3918
4016
  if ("refusal" in zipped) {
3919
4017
  io2.print(zipped.refusal);
@@ -3946,11 +4044,12 @@ var CHECK_ROW = Object.freeze({
3946
4044
  statuses: {
3947
4045
  done: "none of the checks would refuse a deploy now",
3948
4046
  failed: "a deploy would be refused now, the refused: lines naming each refusal",
3949
- nothing_started: "nothing was checked: an option, the credential, or the folder was refused, no credential was read and the two routes to one were printed, the platform was too old, or the check call was refused, drew no answer, was redirected, or failed"
4047
+ nothing_started: "nothing was checked: an option, credential, folder, or the folder's binding was refused, no credential was read and the two routes to one were printed, the platform was too old, or the check call was refused, drew no answer, was redirected, or failed"
3950
4048
  },
3951
4049
  note: [
3952
4050
  `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.`,
3953
4051
  `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.`,
4052
+ `Before its zip, ${CHECK_SUBCOMMAND} reads the folder's manifest.json as ${DEPLOY_SUBCOMMAND} reads it. Where its application member names another application than ${CHECK_OPTION.application}, or is not an application id, the check ends 3 before anything is zipped or sent. A check writes no member where none stands.`,
3954
4053
  `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.`
3955
4054
  ],
3956
4055
  afterOwnError: CHECK_AFTER_OWN_ERROR,
@@ -3978,6 +4077,10 @@ async function hash(named, io2, flags = /* @__PURE__ */ new Set()) {
3978
4077
  io2.print(LIST_ZIP_FILE_SENTENCE);
3979
4078
  return PROGRESS_EXIT_STATUS.nothing_started;
3980
4079
  }
4080
+ if (pathKindOf(folder) === "folder" && folderBindingOf(io2, folder).kind === "off_form") {
4081
+ io2.print(`${OFF_FORM_SENTENCE} ${NO_HASH_PRINTED} ${HASH_BINDING_OWNER_ASK}`);
4082
+ return PROGRESS_EXIT_STATUS.nothing_started;
4083
+ }
3981
4084
  const zipped = zipOf(folder, io2, NO_HASH_PRINTED, path !== void 0);
3982
4085
  if ("refusal" in zipped) {
3983
4086
  io2.print(zipped.refusal);
@@ -3999,7 +4102,7 @@ var HASH_ROW = Object.freeze({
3999
4102
  });
4000
4103
 
4001
4104
  // src/turnzero_cloud_command/secret.ts
4002
- import { isAbsolute as isAbsolute3, join as join5, relative as relative3, resolve as resolve7, sep as sep2 } from "node:path";
4105
+ import { isAbsolute as isAbsolute3, join as join6, relative as relative4, resolve as resolve7, sep as sep2 } from "node:path";
4003
4106
 
4004
4107
  // src/turnzero_cloud_command/value.ts
4005
4108
  var NOTHING_SENT2 = "Nothing was sent.";
@@ -4204,15 +4307,15 @@ var secretUnknownSentence = (act) => `Whether the value was written is unknown:
4204
4307
  var PACKAGE_FILE = "package.json";
4205
4308
  var VALUE_FILE_WARNING = `warning: the file ${SECRET_OPTION.valueFile} names stands inside an application folder, and a deploy from that folder would include it. Keep the file outside the application's folder, or delete it once this line ends 0.`;
4206
4309
  var valueFileWouldDeploy = (io2, cwd, file) => {
4207
- const between = relative3(cwd, file);
4310
+ const between = relative4(cwd, file);
4208
4311
  if (between === "" || between === ".." || between.startsWith(`..${sep2}`) || isAbsolute3(between)) return false;
4209
4312
  const segments = between.split(sep2);
4210
4313
  if (segments.some(leftOutName)) return false;
4211
4314
  let folder = cwd;
4212
4315
  for (let depth = 0; depth < segments.length; depth += 1) {
4213
- if (depth > 0) folder = join5(folder, segments[depth - 1]);
4316
+ if (depth > 0) folder = join6(folder, segments[depth - 1]);
4214
4317
  try {
4215
- if (io2.stat(join5(folder, PACKAGE_FILE))?.folder === false) return true;
4318
+ if (io2.stat(join6(folder, PACKAGE_FILE))?.folder === false) return true;
4216
4319
  } catch {
4217
4320
  }
4218
4321
  }
@@ -4486,11 +4589,11 @@ var PUT_ROW = Object.freeze({
4486
4589
 
4487
4590
  // src/turnzero_cloud_command/library_take.ts
4488
4591
  import { createHash as createHash3 } from "node:crypto";
4489
- import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute4, join as join7, relative as relative4, resolve as resolve10, sep as sep3 } from "node:path";
4592
+ import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute4, join as join8, relative as relative5, resolve as resolve10, sep as sep3 } from "node:path";
4490
4593
 
4491
4594
  // src/turnzero_cloud_command/run.ts
4492
4595
  import { constants } from "node:os";
4493
- import { join as join6, resolve as resolve9, win32 as win323 } from "node:path";
4596
+ import { join as join7, resolve as resolve9, win32 as win323 } from "node:path";
4494
4597
  var RUN_OPTIONS = Object.freeze([
4495
4598
  {
4496
4599
  name: RUN_OPTION.path,
@@ -4674,7 +4777,7 @@ async function localRun(call, io2) {
4674
4777
  return COMMAND_STATUS.nothing_started;
4675
4778
  }
4676
4779
  try {
4677
- io2.readFile(join6(options.folder, PACKAGE_FILE2));
4780
+ io2.readFile(join7(options.folder, PACKAGE_FILE2));
4678
4781
  } catch (error) {
4679
4782
  io2.print(
4680
4783
  `The application's folder holds no ${PACKAGE_FILE2} the command could read (${fileErrorCodeOf(error, "unreadable")}), so npm start has nothing to start there: ${RUN_OPTION.path} names the folder, and it is the folder the command runs in where the option is absent. ${NO_APPLICATION_STARTED}`
@@ -4746,10 +4849,38 @@ var LIBRARY_TAKE_OPTIONS = Object.freeze([
4746
4849
  name: LIBRARY_OPTION.origin,
4747
4850
  value: "<origin>",
4748
4851
  required: false,
4749
- description: `The platform's address. Where absent, ${DEFAULT_COMMAND_ORIGIN}.`
4852
+ description: `The platform's address. Where absent, the address ${ORIGIN_VARIABLE} holds, or ${DEFAULT_COMMAND_ORIGIN} where it is unset. Under ${MINTED_TOKEN_VARIABLE} the token is sent only to the address ${ORIGIN_VARIABLE} holds, or to ${DEFAULT_COMMAND_ORIGIN} where it is unset. Otherwise it names the address whose token the command reads from this computer's credential file, and that token is sent to that address alone.`
4750
4853
  }
4751
4854
  ]);
4752
4855
  var NOTHING_WRITTEN2 = "Nothing was written.";
4856
+ var LIBRARY_CREDENTIAL = Object.freeze({
4857
+ grant: "minted token",
4858
+ aGrant: "a minted token",
4859
+ call: `the ${LIBRARY_READ_ACTION} call`,
4860
+ mintedToken: true,
4861
+ piped: false,
4862
+ places: `the command reads no standard input: it reads a minted token from ${MINTED_TOKEN_VARIABLE} where it is set, as a tool connected by a minted token sets it from where its token is kept, or a job with no tool from its secret store, and otherwise this computer's token for the platform's address from its credential file, which ${LOGIN_SUBCOMMAND} and ${AUTHORIZE_SUBCOMMAND} write.`
4863
+ });
4864
+ var LIBRARY_READS = `no standard input: a minted token from ${MINTED_TOKEN_VARIABLE} where it is set, and otherwise this computer's token for the platform's address from its credential file, which ${LOGIN_SUBCOMMAND} and ${AUTHORIZE_SUBCOMMAND} write, sent only to that address; with neither, it prints the variable route first and then, where the address is admitted, the login route`;
4865
+ var TAKE_AFTER_LOGIN = `Once ${LOGIN_SUBCOMMAND} has printed authorized:, run this take line again. ${NOTHING_WRITTEN2}`;
4866
+ var takeRedirectedLine = (status) => `${LIBRARY_READ_ACTION} was answered ${status >= 100 ? `${answeredHow(status)}, a redirect` : answeredHow(status)}, which the command never follows, so the credential was sent to no second address. ${NOTHING_WRITTEN2}`;
4867
+ async function readTakeCredential(io2, named, afterLogin) {
4868
+ const line = { named, closing: NOTHING_WRITTEN2 };
4869
+ const read2 = await readCredential(io2, LIBRARY_CREDENTIAL, "", line);
4870
+ if ("unauthorized" in read2) {
4871
+ const lines = [noTokenSentence(read2.unauthorized.origin, read2.unauthorized.expiredAt), VARIABLE_ROUTE_SENTENCES];
4872
+ const origin = authorizationOriginOf(io2.env, named, NOTHING_WRITTEN2);
4873
+ if ("refusal" in origin) return { lines: [...lines, origin.refusal] };
4874
+ const unwritable = loginRouteRefusalOf(io2, NOTHING_WRITTEN2);
4875
+ if (unwritable !== null) return { lines: [...lines, unwritable] };
4876
+ return { lines: [...lines, ...loginRouteLines(io2, origin.origin, false), afterLogin] };
4877
+ }
4878
+ if ("refusal" in read2) return { lines: [read2.refusal] };
4879
+ io2.hide(read2.credential, REDACTED_TOKEN);
4880
+ const sent = credentialOriginOf(io2.env, read2, line);
4881
+ if ("refusal" in sent) return { lines: [sent.refusal] };
4882
+ return { bearer: read2.credential, origin: sent.origin };
4883
+ }
4753
4884
  var LINES_IN_WINDOWS_FORM = process.platform === "win32";
4754
4885
  var machineLineOf = (line, form = LINES_IN_WINDOWS_FORM, origin = DEFAULT_COMMAND_ORIGIN) => {
4755
4886
  const windows = typeof form === "boolean" ? form : form.windows;
@@ -4788,13 +4919,13 @@ var libraryTakeOptionsOf = (call, cwd) => {
4788
4919
  return { entry, root: resolve10(cwd, path ?? "."), named: path !== void 0, origin: origin.origin };
4789
4920
  };
4790
4921
  var inside = (parent, child) => {
4791
- const between = relative4(parent, child);
4922
+ const between = relative5(parent, child);
4792
4923
  return between !== "" && !between.startsWith("..") && !isAbsolute4(between);
4793
4924
  };
4794
- var shownFrom = (root) => (path) => shownName(relative4(root, path).split(sep3).join("/") || ".");
4925
+ var shownFrom = (root) => (path) => shownName(relative5(root, path).split(sep3).join("/") || ".");
4795
4926
  var repositoryRootOf = (io2, root) => {
4796
4927
  for (let at = root; ; at = dirname4(at)) {
4797
- if (io2.stat(join7(at, ".git")) !== null) return at;
4928
+ if (io2.stat(join8(at, ".git")) !== null) return at;
4798
4929
  if (dirname4(at) === at) return root;
4799
4930
  }
4800
4931
  };
@@ -4814,14 +4945,14 @@ var TAKE_FOLDER_WORDS = Object.freeze({
4814
4945
  manifestCannot: "so the take cannot add its row to it"
4815
4946
  });
4816
4947
  var libraryFolderOf = (io2, root, words = TAKE_FOLDER_WORDS) => {
4817
- const own = join7(root, LIBRARY_FOLDER);
4948
+ const own = join8(root, LIBRARY_FOLDER);
4818
4949
  const inRoot = { folder: root, called: ROOT_CALLED };
4819
4950
  const standing = io2.stat(own);
4820
4951
  if (standing !== null) {
4821
4952
  if (!standing.folder) return { refusal: `The project's ${LIBRARY_FOLDER} is a file, where the library folder stands. ${NOTHING_WRITTEN2}` };
4822
4953
  return { folder: own, fresh: false, within: inRoot };
4823
4954
  }
4824
- const pointerAt = join7(root, POINTER_FILE);
4955
+ const pointerAt = join8(root, POINTER_FILE);
4825
4956
  if (io2.stat(pointerAt) === null) return { folder: own, fresh: true, within: inRoot };
4826
4957
  let named;
4827
4958
  try {
@@ -4845,22 +4976,22 @@ var libraryFolderOf = (io2, root, words = TAKE_FOLDER_WORDS) => {
4845
4976
  return { folder: at, fresh: false, within: { folder: repository, called: REPOSITORY_CALLED } };
4846
4977
  };
4847
4978
  var applicationFolderRefusalOf = (io2, options) => {
4848
- const holds = (folder) => io2.stat(join7(folder, LIBRARY_FOLDER)) !== null || io2.stat(join7(folder, POINTER_FILE)) !== null;
4979
+ const holds = (folder) => io2.stat(join8(folder, LIBRARY_FOLDER)) !== null || io2.stat(join8(folder, POINTER_FILE)) !== null;
4849
4980
  const parent = dirname4(options.root);
4850
4981
  if (parent === options.root || holds(options.root) || !holds(parent)) return null;
4851
4982
  return options.named ? `The folder ${LIBRARY_OPTION.path} names holds neither ${LIBRARY_FOLDER}/ nor ${POINTER_FILE}, and the folder above it holds one, so it may be an application's folder inside that project. Take the entry at the project's root: name the folder above it with ${LIBRARY_OPTION.path}. Or, where the folder is a project of its own, make an empty ${LIBRARY_FOLDER}/ folder in it and run the line again. ${NOTHING_WRITTEN2}` : `This folder holds neither ${LIBRARY_FOLDER}/ nor ${POINTER_FILE}, and the folder above it holds one, so this may be an application's folder inside that project. Take the entry at the project's root: run the line in the folder above. Or, where this folder is a project of its own, make an empty ${LIBRARY_FOLDER}/ folder here and run the line again. ${NOTHING_WRITTEN2}`;
4852
4983
  };
4853
4984
  var entryFoldersIn = (io2, library) => {
4854
- const folders = (at) => (io2.listFolder(join7(library, ...at.split("/"))) ?? []).filter((name) => !NEVER_PUBLISHED.has(name) && io2.stat(join7(library, ...at.split("/"), name))?.folder === true).sort();
4985
+ const folders = (at) => (io2.listFolder(join8(library, ...at.split("/"))) ?? []).filter((name) => !NEVER_PUBLISHED.has(name) && io2.stat(join8(library, ...at.split("/"), name))?.folder === true).sort();
4855
4986
  return [
4856
4987
  ...folders("features"),
4857
4988
  ...folders("ui").filter((name) => name !== "assets").map((name) => `ui/${name}`),
4858
4989
  ...folders("ui/assets/fonts").map((name) => `fonts/${name}`),
4859
- ...io2.stat(join7(library, "prd"))?.folder === true ? ["library/prd"] : []
4990
+ ...io2.stat(join8(library, "prd"))?.folder === true ? ["library/prd"] : []
4860
4991
  ];
4861
4992
  };
4862
4993
  var manifestRowsOf = (io2, library, words = TAKE_FOLDER_WORDS) => {
4863
- const at = join7(library, MANIFEST_FILE2);
4994
+ const at = join8(library, MANIFEST_FILE2);
4864
4995
  if (io2.stat(at) === null) return { rows: null, stood: null };
4865
4996
  try {
4866
4997
  const stood = io2.readFile(at);
@@ -4902,7 +5033,7 @@ var listedEntryOf = (answer, name) => {
4902
5033
  return { name, path, version, closure_hash: closure, files: listed2, source_commit: commit };
4903
5034
  };
4904
5035
  var LIST_READ = "The read of the entry's file list";
4905
- var readOf = async (io2, origin, userAgent, body, what) => {
5036
+ var readOf = async (io2, origin, bearer, userAgent, body, what) => {
4906
5037
  let status;
4907
5038
  let text;
4908
5039
  try {
@@ -4911,10 +5042,15 @@ var readOf = async (io2, origin, userAgent, body, what) => {
4911
5042
  what,
4912
5043
  () => io2.fetch(`${origin}${LIBRARY_READ_PATH}`, {
4913
5044
  method: "POST",
4914
- headers: { "user-agent": userAgent, "content-type": "application/json" },
4915
- body: JSON.stringify(body)
5045
+ headers: { authorization: `Bearer ${bearer}`, "user-agent": userAgent, "content-type": "application/json" },
5046
+ body: JSON.stringify(body),
5047
+ redirect: "manual"
4916
5048
  })
4917
5049
  );
5050
+ if (redirected(response)) {
5051
+ await response.body?.cancel().catch(() => void 0);
5052
+ return { lines: [takeRedirectedLine(response.status)] };
5053
+ }
4918
5054
  status = response.status;
4919
5055
  text = await response.text();
4920
5056
  } catch {
@@ -4924,16 +5060,17 @@ var readOf = async (io2, origin, userAgent, body, what) => {
4924
5060
  if (status >= 200 && status <= 299 && answer !== null) return { answer };
4925
5061
  const refusal = markedRefusalOf(status, text);
4926
5062
  if (refusal === null) return { lines: [`${LIBRARY_READ_ACTION} was answered HTTP ${status}, not by the platform. ${NOTHING_WRITTEN2}`] };
5063
+ if (status >= PLATFORM_FAILURE_STATUS) return { lines: [...failureLines(LIBRARY_READ_ACTION, status, refusal), NOTHING_WRITTEN2] };
4927
5064
  return { lines: [...refusalLines(LIBRARY_READ_ACTION, status, refusal), NOTHING_WRITTEN2] };
4928
5065
  };
4929
- var fileBytesOf = async (io2, origin, userAgent, name, file) => {
5066
+ var fileBytesOf = async (io2, origin, bearer, userAgent, name, file) => {
4930
5067
  const shown2 = `${LIBRARY_FOLDER}/${shownName(file.path)}`;
4931
5068
  let text = "";
4932
5069
  let encoding = null;
4933
5070
  let offset = 0;
4934
5071
  let stamp = null;
4935
5072
  for (; ; ) {
4936
- const read2 = await readOf(io2, origin, userAgent, { name, file: file.path, limit: LIBRARY_READ_CHUNK, ...stamp !== null ? { offset, stamp } : {} }, `The read of ${shown2}`);
5073
+ const read2 = await readOf(io2, origin, bearer, userAgent, { name, file: file.path, limit: LIBRARY_READ_CHUNK, ...stamp !== null ? { offset, stamp } : {} }, `The read of ${shown2}`);
4937
5074
  if ("lines" in read2) return { lines: [`${shown2}: the read stopped.`, ...read2.lines] };
4938
5075
  const chunk = read2.answer.file;
4939
5076
  const next = chunk?.next_offset;
@@ -4979,11 +5116,6 @@ var ofModuleType = (plan, name) => {
4979
5116
  };
4980
5117
  var MADE_LINE_HEAD = "made: ";
4981
5118
  var madeLineOf = (made) => `${MADE_LINE_HEAD}${MADE_FOLDER}/package.json, since neither app/package.json nor the project root's package.json stood: private, named ${made.name}${made.module ? ", of type module" : ""}, with no scripts.`;
4982
- var BYTE_ORDER_MARK3 = "\uFEFF";
4983
- var jsonTextOf = (bytes) => {
4984
- const text = bytes.toString("utf8");
4985
- return text.startsWith(BYTE_ORDER_MARK3) ? text.slice(BYTE_ORDER_MARK3.length) : text;
4986
- };
4987
5119
  var openingCharacterClauseOf = (text) => {
4988
5120
  const first = text.codePointAt(0);
4989
5121
  if (first === void 0) return "";
@@ -4993,19 +5125,19 @@ var openingCharacterClauseOf = (text) => {
4993
5125
  const control = first < 32 && first !== 9 && first !== 10 && first !== 13 || first >= 127 && first <= 159;
4994
5126
  return control ? `: the file opens with ${point}, a control character` : "";
4995
5127
  };
4996
- var applicationFolderOf = (io2, root) => [join7(root, "app"), root].find((folder) => io2.stat(join7(folder, "package.json"))?.folder === false) ?? null;
5128
+ var applicationFolderOf = (io2, root) => [join8(root, "app"), root].find((folder) => io2.stat(join8(folder, "package.json"))?.folder === false) ?? null;
4997
5129
  var copyPlanOf = (io2, root, library, name) => {
4998
5130
  const shown2 = shownFrom(root);
4999
5131
  if (name.includes("/")) return { refusal: `The entry ${LIBRARY_OPTION.entry} names carries compiled modules under a name that holds a slash, which an npm package cannot take. ${NOTHING_WRITTEN2}` };
5000
5132
  const application = applicationFolderOf(io2, root);
5001
5133
  if (application === null) {
5002
- const app = join7(root, MADE_FOLDER);
5134
+ const app = join8(root, MADE_FOLDER);
5003
5135
  const standing = io2.stat(app);
5004
5136
  if (standing !== null && !standing.folder || danglingLinkAt(io2, root, MADE_FOLDER)) {
5005
5137
  return { refusal: `Something that is no folder stands at ${MADE_FOLDER}, where the take would make the application's package.json. ${NOTHING_WRITTEN2}` };
5006
5138
  }
5007
- const copy2 = join7(app, "lib", name);
5008
- const madeFile = join7(app, "package.json");
5139
+ const copy2 = join8(app, "lib", name);
5140
+ const madeFile = join8(app, "package.json");
5009
5141
  if (io2.stat(madeFile) !== null || standing !== null && danglingLinkAt(io2, app, "package.json")) {
5010
5142
  return { refusal: `Something that is no package file stands at ${MADE_FOLDER}/package.json, where the take would make it. ${NOTHING_WRITTEN2}` };
5011
5143
  }
@@ -5018,7 +5150,7 @@ var copyPlanOf = (io2, root, library, name) => {
5018
5150
  const declaredAt2 = `file:lib/${name}`;
5019
5151
  return { application: app, copy: copy2, declared: madePackageFileOf(made, `${PACKAGE_SCOPE}${name}`, declaredAt2), declaredAt: declaredAt2, made };
5020
5152
  }
5021
- const packageFile = join7(application, "package.json");
5153
+ const packageFile = join8(application, "package.json");
5022
5154
  const read2 = io2.readFile(packageFile);
5023
5155
  const text = jsonTextOf(read2);
5024
5156
  let parsed;
@@ -5030,12 +5162,12 @@ var copyPlanOf = (io2, root, library, name) => {
5030
5162
  }
5031
5163
  const packageName = `${PACKAGE_SCOPE}${name}`;
5032
5164
  const specs = DEPENDENCY_SECTIONS.map((section) => parsed[section]?.[packageName]).filter((spec) => spec !== void 0);
5033
- const place = application === root && io2.stat(join7(root, "app"))?.folder === true ? `app/lib/${name}` : `lib/${name}`;
5165
+ const place = application === root && io2.stat(join8(root, "app"))?.folder === true ? `app/lib/${name}` : `lib/${name}`;
5034
5166
  let copy;
5035
5167
  let declared = null;
5036
5168
  let declaredAt = null;
5037
5169
  if (specs.length === 0) {
5038
- copy = join7(application, ...place.split("/"));
5170
+ copy = join8(application, ...place.split("/"));
5039
5171
  declaredAt = `file:${place}`;
5040
5172
  declared = withDependency(text, parsed, packageName, declaredAt);
5041
5173
  } else {
@@ -5061,11 +5193,11 @@ var stageRefusalOf = (io2, stage, beside) => {
5061
5193
  return own ? null : `Something that is not the take's own stands at ${STAGE} beside ${beside}, the name the take writes under: move it, then run the line again. ${NOTHING_WRITTEN2}`;
5062
5194
  };
5063
5195
  var moveFolderIn = (io2, stage, target, files) => {
5064
- const fresh = join7(stage, "new");
5065
- const old = join7(stage, "old");
5196
+ const fresh = join8(stage, "new");
5197
+ const old = join8(stage, "old");
5066
5198
  io2.remove(stage);
5067
5199
  for (const [path, bytes] of files) {
5068
- const at = join7(fresh, ...path.split("/"));
5200
+ const at = join8(fresh, ...path.split("/"));
5069
5201
  io2.makeFolder(dirname4(at));
5070
5202
  io2.writeFile(at, bytes);
5071
5203
  }
@@ -5105,15 +5237,14 @@ var PACKAGES_LINE_HEAD = "packages: ";
5105
5237
  var jsonEscapeOf = (character) => Array.from({ length: character.length }, (_, index) => `\\u${character.charCodeAt(index).toString(16).padStart(4, "0")}`).join("");
5106
5238
  var shownJson = (text) => [...text].map((character) => shownWhole(character) === character ? character : jsonEscapeOf(character)).join("");
5107
5239
  var packagesLineOf = (rows) => `${PACKAGES_LINE_HEAD}${shownJson(JSON.stringify(rows))}`;
5108
- var applicationManifestPartialOf = (at) => `${at}.partial`;
5109
5240
  var PARTIAL_LINK_SENTENCE = writtenLinkSentence(`The file the application manifest is written to first, ${MANIFEST_FILE2}.partial beside it,`);
5110
- var danglingLinkAt = (io2, folder, name) => io2.stat(join7(folder, name)) === null && (io2.listFolder(folder) ?? []).some((held) => held.toLowerCase() === name.toLowerCase() && io2.stat(join7(folder, held)) === null);
5241
+ var danglingLinkAt = (io2, folder, name) => io2.stat(join8(folder, name)) === null && (io2.listFolder(folder) ?? []).some((held) => held.toLowerCase() === name.toLowerCase() && io2.stat(join8(folder, held)) === null);
5111
5242
  var linkStandsAt = (io2, path) => {
5112
5243
  const name = basename2(path);
5113
5244
  if (!(io2.listFolder(dirname4(path)) ?? []).includes(name)) return false;
5114
5245
  const real = io2.realPath(path);
5115
5246
  const folder = io2.realPath(dirname4(path));
5116
- return real === null || folder === null || relative4(join7(folder, name), real) !== "";
5247
+ return real === null || folder === null || relative5(join8(folder, name), real) !== "";
5117
5248
  };
5118
5249
  var writesThroughLink = (io2, holder, path) => linkStandsAt(io2, path) || !heldInside(io2, holder, path);
5119
5250
  var writtenNameRefusalOf = (io2, holder, names) => {
@@ -5122,11 +5253,11 @@ var writtenNameRefusalOf = (io2, holder, names) => {
5122
5253
  };
5123
5254
  var applicationManifestOf = (io2, root, library, folder = applicationFolderOf(io2, root)) => {
5124
5255
  if (folder === null) return { at: null, unwritten: "no_folder" };
5125
- const at = join7(folder, MANIFEST_FILE2);
5256
+ const at = join8(folder, MANIFEST_FILE2);
5126
5257
  const standing = io2.stat(at);
5127
5258
  if (standing === null) return { at, unwritten: "not_standing" };
5128
5259
  if (!heldInside(io2, root, at)) return { refusal: linkSentence("The application manifest", ROOT_CALLED) };
5129
- const libraryManifest = join7(library, MANIFEST_FILE2);
5260
+ const libraryManifest = join8(library, MANIFEST_FILE2);
5130
5261
  if (resolve10(at) === resolve10(libraryManifest) || realPlaceOf(io2, at) === realPlaceOf(io2, libraryManifest)) return { at, unwritten: "library_manifest" };
5131
5262
  if (standing.folder) return { at, unwritten: "no_object" };
5132
5263
  const partial = applicationManifestPartialOf(at);
@@ -5138,25 +5269,9 @@ var applicationManifestOf = (io2, root, library, folder = applicationFolderOf(io
5138
5269
  }
5139
5270
  return { at, unwritten: "no_object" };
5140
5271
  };
5141
- var applicationManifestTextOf = (parsed, packages) => `${JSON.stringify({ ...parsed, packages }, null, 2)}
5142
- `;
5143
5272
  var writeApplicationManifest = (io2, manifest, packages) => {
5144
5273
  if (!("parsed" in manifest)) return manifest;
5145
- const partial = applicationManifestPartialOf(manifest.at);
5146
- let made = false;
5147
- try {
5148
- if (io2.stat(partial)?.folder === false) io2.remove(partial);
5149
- made = io2.stat(partial) === null;
5150
- io2.writeNewFile(partial, applicationManifestTextOf(manifest.parsed, packages));
5151
- io2.rename(partial, manifest.at);
5152
- } catch {
5153
- try {
5154
- if (made) io2.remove(partial);
5155
- } catch {
5156
- }
5157
- return { at: manifest.at, unwritten: "write_failed" };
5158
- }
5159
- return { written: manifest.at };
5274
+ return writeManifestMember(io2, manifest, "packages", packages) ? { written: manifest.at } : { at: manifest.at, unwritten: "write_failed" };
5160
5275
  };
5161
5276
  var packagesWhereOf = (root, outcome) => {
5162
5277
  const shown2 = shownFrom(root);
@@ -5180,17 +5295,17 @@ var COPY_PACKAGES_CLAUSE = "copy the packages line into the application manifest
5180
5295
  var KEEP_PACKAGES_CLAUSE = "the application manifest was written whole, so read it again before patching it, and carry its packages member over in any later rewrite; ";
5181
5296
  var installFailedSentence = (folder) => `npm install in ${folder} did not finish: the entry, its row, and the copy are written. Run npm install there again; running this line again takes the entry again whole.`;
5182
5297
  var LIBRARY_AFTER_OWN_ERROR = "the entry's folder, its manifest row, or the application's copy may stand in part, and running the line again replaces each whole";
5183
- async function readTake(io2, options, userAgent, planOf, planeChecked = false) {
5298
+ async function readTake(io2, options, userAgent, bearer, planOf, planeChecked = false) {
5184
5299
  const refuse = (...lines) => ({ lines });
5185
5300
  const found = libraryFolderOf(io2, options.root);
5186
5301
  if ("refusal" in found) return refuse(found.refusal);
5187
5302
  const library = found.folder;
5188
5303
  if (!heldInside(io2, found.within.folder, library)) return refuse(linkSentence("The project's library folder", found.within.called));
5189
- const manifestAt = join7(library, MANIFEST_FILE2);
5304
+ const manifestAt = join8(library, MANIFEST_FILE2);
5190
5305
  const inLibrary = writtenNameRefusalOf(io2, found.within.folder, [
5191
5306
  [WRITTEN_NAMES.manifest, manifestAt],
5192
5307
  [WRITTEN_NAMES.manifestPartial, `${manifestAt}.partial`],
5193
- [WRITTEN_NAMES.libraryStage, join7(library, STAGE)]
5308
+ [WRITTEN_NAMES.libraryStage, join8(library, STAGE)]
5194
5309
  ]);
5195
5310
  if (inLibrary !== null) return refuse(inLibrary);
5196
5311
  const manifest = found.fresh ? { rows: null, stood: null } : manifestRowsOf(io2, library);
@@ -5200,11 +5315,11 @@ async function readTake(io2, options, userAgent, planOf, planeChecked = false) {
5200
5315
  const older = await planeWireRefusal(io2, options.origin);
5201
5316
  if (older !== null) return refuse(older);
5202
5317
  }
5203
- const list = await readOf(io2, options.origin, userAgent, { name: options.entry }, LIST_READ);
5318
+ const list = await readOf(io2, options.origin, bearer, userAgent, { name: options.entry }, LIST_READ);
5204
5319
  if ("lines" in list) return refuse(...list.lines);
5205
5320
  const entry = listedEntryOf(list.answer, options.entry);
5206
5321
  if ("refusal" in entry) return refuse(entry.refusal);
5207
- const entryFolder = join7(library, ...entry.path.split("/"));
5322
+ const entryFolder = join8(library, ...entry.path.split("/"));
5208
5323
  if (!heldInside(io2, found.within.folder, entryFolder)) return refuse(linkSentence("The entry's folder", found.within.called));
5209
5324
  if (io2.stat(entryFolder) !== null && !(manifest.rows ?? []).some((row) => rowName(row) === entry.name)) {
5210
5325
  return refuse(sourceSentence(`holds ${entry.name}'s folder and no row for it in its ${MANIFEST_FILE2}`));
@@ -5214,15 +5329,15 @@ async function readTake(io2, options, userAgent, planOf, planeChecked = false) {
5214
5329
  if (plan !== null && "refusal" in plan) return refuse(plan.refusal, ...plan.after ?? []);
5215
5330
  if (plan !== null && !heldInside(io2, options.root, plan.copy)) return refuse(linkSentence("The application's copy", ROOT_CALLED));
5216
5331
  const besideCopy = plan === null ? null : writtenNameRefusalOf(io2, options.root, [
5217
- ...plan.declared !== null ? [[WRITTEN_NAMES.packageFile, join7(plan.application, "package.json")]] : [],
5332
+ ...plan.declared !== null ? [[WRITTEN_NAMES.packageFile, join8(plan.application, "package.json")]] : [],
5218
5333
  [WRITTEN_NAMES.copyStage, `${plan.copy}${STAGE}`]
5219
5334
  ]);
5220
5335
  if (besideCopy !== null) return refuse(besideCopy);
5221
- const staged = stageRefusalOf(io2, join7(library, STAGE), "the library folder") ?? (plan !== null ? stageRefusalOf(io2, `${plan.copy}${STAGE}`, "the application's copy") : null);
5336
+ const staged = stageRefusalOf(io2, join8(library, STAGE), "the library folder") ?? (plan !== null ? stageRefusalOf(io2, `${plan.copy}${STAGE}`, "the application's copy") : null);
5222
5337
  if (staged !== null) return refuse(staged);
5223
5338
  const bytes = /* @__PURE__ */ new Map();
5224
5339
  for (const file of entry.files) {
5225
- const read2 = await fileBytesOf(io2, options.origin, userAgent, entry.name, file);
5340
+ const read2 = await fileBytesOf(io2, options.origin, bearer, userAgent, entry.name, file);
5226
5341
  if ("lines" in read2) return refuse(...read2.lines);
5227
5342
  const actual = sha256(read2.bytes);
5228
5343
  if (actual !== file.sha256) return refuse(`${LIBRARY_FOLDER}/${shownName(file.path)}: SHA-256 ${actual} is not the listed value ${file.sha256}. ${NOTHING_WRITTEN2}`);
@@ -5242,11 +5357,11 @@ function writeTake(io2, root, { entry, library, entryFolder, rows, manifestStood
5242
5357
  source_commit: entry.source_commit,
5243
5358
  fetched_at: (/* @__PURE__ */ new Date()).toISOString()
5244
5359
  });
5245
- const manifestAt = join7(library, MANIFEST_FILE2);
5360
+ const manifestAt = join8(library, MANIFEST_FILE2);
5246
5361
  const manifestPartial = `${manifestAt}.partial`;
5247
5362
  io2.writeFile(manifestPartial, manifestText);
5248
5363
  io2.rename(manifestPartial, manifestAt);
5249
- const entryStage = join7(library, STAGE);
5364
+ const entryStage = join8(library, STAGE);
5250
5365
  try {
5251
5366
  moveFolderIn(
5252
5367
  io2,
@@ -5276,7 +5391,7 @@ function writeTake(io2, root, { entry, library, entryFolder, rows, manifestStood
5276
5391
  plan.copy,
5277
5392
  compiled.map((file) => [file.path.slice(entry.path.length + 1), bytes.get(file.path)])
5278
5393
  );
5279
- const packageFile = join7(plan.application, "package.json");
5394
+ const packageFile = join8(plan.application, "package.json");
5280
5395
  if (plan.declared !== null) io2.writeFile(packageFile, plan.declared);
5281
5396
  if (plan.made !== void 0) io2.print(madeLineOf(plan.made));
5282
5397
  io2.print(
@@ -5330,7 +5445,9 @@ async function libraryTake(call, io2) {
5330
5445
  }
5331
5446
  const fromApplication = applicationFolderRefusalOf(io2, options);
5332
5447
  if (fromApplication !== null) return refuse(fromApplication);
5333
- const checked = await readTake(io2, options, agent.userAgent, (library, name) => copyPlanOf(io2, options.root, library, name));
5448
+ const held = await readTakeCredential(io2, call.named[LIBRARY_OPTION.origin], TAKE_AFTER_LOGIN);
5449
+ if ("lines" in held) return refuse(...held.lines);
5450
+ const checked = await readTake(io2, { ...options, origin: held.origin }, agent.userAgent, held.bearer, (library, name) => copyPlanOf(io2, options.root, library, name));
5334
5451
  if ("lines" in checked) return refuse(...checked.lines);
5335
5452
  const application = applicationManifestOf(io2, options.root, checked.library, checked.plan?.application ?? applicationFolderOf(io2, options.root));
5336
5453
  if ("refusal" in application) return refuse(application.refusal);
@@ -5341,9 +5458,10 @@ async function libraryTake(call, io2) {
5341
5458
  var LIBRARY_ROW = Object.freeze({
5342
5459
  name: LIBRARY_SUBCOMMAND,
5343
5460
  acts: LIBRARY_ACTS,
5344
- summary: "Takes a library entry into the project: reads its files from the library, checks each file's SHA-256 and the entry's closure hash, then writes its row in the library folder's manifest.json and replaces the entry's folder under that folder whole. For an entry with compiled modules it copies its package.json and lib/ into the application's folder, declares the copy where it is not declared, and runs npm install there. It reads no credential.",
5461
+ summary: "Takes a library entry into the project: reads its files from the library, checks each file's SHA-256 and the entry's closure hash, then writes its row in the library folder's manifest.json and replaces the entry's folder under that folder whole. For an entry with compiled modules it copies its package.json and lib/ into the application's folder, declares the copy where it is not declared, and runs npm install there. It reads under the credential check reads. With no credential, it sends nothing and prints the two routes to one, as check does.",
5345
5462
  options: LIBRARY_TAKE_OPTIONS,
5346
- credential: null,
5463
+ credential: LIBRARY_CREDENTIAL,
5464
+ reads: LIBRARY_READS,
5347
5465
  statuses: {
5348
5466
  done: "the entry taken, and installed where it carries compiled modules",
5349
5467
  failed: "the entry, its row, and the copy written, and npm install failed or the application manifest's packages was not written",
@@ -5730,6 +5848,7 @@ var whoamiLinesOf = (origin, account, entry) => [
5730
5848
  `${WHOAMI_LINE_HEADS.token}label ${entry.label !== null ? JSON.stringify(entry.label) : "none"}, expiring ${entry.expires_at !== null ? `at ${entry.expires_at}` : "never"}`
5731
5849
  ];
5732
5850
  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.`;
5851
+ var noLoginRoutesSentence = (origin) => `Your AI coding tool's own sign-in is not this computer's credential. With no login, deploy_files deploys a folder within its caps, ${AUTHORIZE_SUBCOMMAND}${originWordsOf(origin)} run with nothing piped prints the ${MINT_TOKEN_ACTION} call a signed-in tool makes to authorize this computer, and ${PROVISION_SUBCOMMAND}${originWordsOf(origin)} run so prints the call that answers its line.`;
5733
5852
  var whoamiAfterLoginSentence = (whoami2) => `Once login has printed authorized:, run ${whoami2} again.`;
5734
5853
  async function whoami(call, io2) {
5735
5854
  const origin = entryOriginOf(io2.env, call.named[DEPLOY_OPTION.origin], NOTHING_SENT_OR_READ);
@@ -5753,6 +5872,7 @@ async function whoami(call, io2) {
5753
5872
  const expired2 = entry !== void 0 && entry.expires_at !== null && Date.parse(entry.expires_at) <= io2.clock().getTime();
5754
5873
  if (entry === void 0 || expired2) {
5755
5874
  io2.print(noComputerTokenSentence(origin.origin, entry === void 0 ? null : entry.expires_at));
5875
+ io2.print(noLoginRoutesSentence(origin.origin));
5756
5876
  const reached = authorizationOriginOf(io2.env, call.named[DEPLOY_OPTION.origin], NOTHING_SENT_OR_READ);
5757
5877
  if ("refusal" in reached) {
5758
5878
  io2.print(reached.refusal);
@@ -5783,7 +5903,7 @@ async function whoami(call, io2) {
5783
5903
  }
5784
5904
  var WHOAMI_ROW = Object.freeze({
5785
5905
  name: WHOAMI_SUBCOMMAND,
5786
- 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.`,
5906
+ 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, names the ways on with no login (deploy_files, ${AUTHORIZE_SUBCOMMAND}, and ${PROVISION_SUBCOMMAND}), and then prints the login route. It prints no token.`,
5787
5907
  options: WHOAMI_OPTIONS,
5788
5908
  credential: null,
5789
5909
  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",
@@ -5791,7 +5911,7 @@ var WHOAMI_ROW = Object.freeze({
5791
5911
  done: "the account was read",
5792
5912
  failed: "the platform refused the read, where it no longer accepts the token among other causes: the printed lines name login",
5793
5913
  unknown: "the read drew no answer, or an answer that is not a refusal",
5794
- 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"
5914
+ 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 name the ways on with no login, then the login route"
5795
5915
  },
5796
5916
  variables: [[ORIGIN_VARIABLE, `the platform's address whose token is read where the line names no ${DEPLOY_OPTION.origin}.`]],
5797
5917
  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}.`],
@@ -5856,7 +5976,7 @@ var startUpgradeCheck = (io2, subcommand, named, running) => {
5856
5976
  const read2 = io2.registryFetch;
5857
5977
  if (read2 === void 0 || !UPGRADE_CHECK_SUBCOMMANDS.includes(subcommand)) return NO_CHECK;
5858
5978
  if (UPGRADE_CHECK_SKIP_VARIABLES.some((name) => io2.env[name] !== void 0)) return NO_CHECK;
5859
- const origin = named[DEPLOY_OPTION.origin] ?? io2.env[ORIGIN_VARIABLE] ?? DEFAULT_COMMAND_ORIGIN;
5979
+ const origin = originReadOf(named[DEPLOY_OPTION.origin] ?? io2.env[ORIGIN_VARIABLE]);
5860
5980
  const tag = commandTagOf(origin);
5861
5981
  const place = credentialFileOf(io2);
5862
5982
  const path = (onWindows(io2) ? win324 : posix3).join(place.folder, UPGRADE_CHECK_FILE_NAME);
@@ -5900,7 +6020,7 @@ var startUpgradeCheck = (io2, subcommand, named, running) => {
5900
6020
  };
5901
6021
 
5902
6022
  // src/turnzero_cloud_command/provision.ts
5903
- import { basename as basename3, dirname as dirname5, join as join8 } from "node:path";
6023
+ import { basename as basename3, dirname as dirname5, join as join9 } from "node:path";
5904
6024
  var PROVISION_OPTIONS = Object.freeze([
5905
6025
  {
5906
6026
  name: PROVISION_OPTION.application,
@@ -5957,7 +6077,7 @@ var APPLICATION_FOLDER_MARKS = Object.freeze(["package.json", "manifest.json"]);
5957
6077
  var outsideApplicationWarningOf = (folder) => `warning: ${folder} holds neither ${APPLICATION_FOLDER_MARKS[0]} nor ${APPLICATION_FOLDER_MARKS[1]}, so it may not be the application's folder, and the environment file was written there all the same. Where the application stands in another folder, move the file into that folder: a local run reads the environment file from the folder it runs in.`;
5958
6078
  var outsideApplicationFolder = (io2, envFile) => APPLICATION_FOLDER_MARKS.every((mark) => {
5959
6079
  try {
5960
- return io2.stat(join8(dirname5(envFile), mark))?.folder !== false;
6080
+ return io2.stat(join9(dirname5(envFile), mark))?.folder !== false;
5961
6081
  } catch {
5962
6082
  return false;
5963
6083
  }
@@ -6299,7 +6419,7 @@ var PROVISION_ROW = Object.freeze({
6299
6419
  });
6300
6420
 
6301
6421
  // src/turnzero_cloud_command/scaffold.ts
6302
- import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute5, join as join9, relative as relative5, resolve as resolve11 } from "node:path";
6422
+ import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute5, join as join10, relative as relative6, resolve as resolve11 } from "node:path";
6303
6423
 
6304
6424
  // src/turnzero_cloud_command/scaffold_template.ts
6305
6425
  var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
@@ -7750,7 +7870,13 @@ var SCAFFOLD_OPTIONS = Object.freeze([
7750
7870
  name: SCAFFOLD_OPTION.origin,
7751
7871
  value: "<origin>",
7752
7872
  required: false,
7753
- description: `The platform's address. Where absent, ${DEFAULT_COMMAND_ORIGIN}.`
7873
+ description: `The platform's address. Under a template that takes library entries, where absent, the address ${ORIGIN_VARIABLE} holds, or ${DEFAULT_COMMAND_ORIGIN} where it is unset. There, under ${MINTED_TOKEN_VARIABLE} the token is sent only to the address ${ORIGIN_VARIABLE} holds, or to ${DEFAULT_COMMAND_ORIGIN} where it is unset. Otherwise it names the address whose token the command reads from this computer's credential file, and that token is sent to that address alone. Under a template that takes none, where absent, ${DEFAULT_COMMAND_ORIGIN}.`
7874
+ },
7875
+ {
7876
+ name: SCAFFOLD_OPTION.application,
7877
+ value: "<application id>",
7878
+ required: false,
7879
+ description: `Under every template, the application the service deploys to, by its id, a lowercase UUID, as create_application answers it. The written ${APPLICATION_MANIFEST} names it in its application member, which binds the folder to that application, so a deploy or check line naming another is refused. Where absent, the first deploy writes the member.`
7754
7880
  }
7755
7881
  ]);
7756
7882
  var EMPTY_PACKAGES = '"packages": []';
@@ -7777,6 +7903,7 @@ var NEITHER_LEFT = Object.freeze({ tools: "written", claude: false });
7777
7903
  var ROOT_TOOLS_STEP = `Add this sentence to ${TOOLS_FILE} at the project's root, which the scaffold did not change, so a coding tool started at the root reads ${APPLICATION_FOLDER}/${TOOLS_FILE}: ${ROOT_SENTENCE}`;
7778
7904
  var ROOT_TOOLS_FAILED_STEP = `Add this sentence to ${TOOLS_FILE} at the project's root, which the scaffold could not write, so a coding tool started at the root reads ${APPLICATION_FOLDER}/${TOOLS_FILE}: ${ROOT_SENTENCE}`;
7779
7905
  var ROOT_CLAUDE_STEP = `Add this sentence to ${CLAUDE_FILE} at the project's root, since Claude Code reads that file and then reads no ${TOOLS_FILE} by itself: ${ROOT_SENTENCE}`;
7906
+ var APPLICATION_FORM_REFUSAL = `${SCAFFOLD_OPTION.application} names the application the service deploys to, by its id, a lowercase UUID, as create_application answers it. ${NOTHING_WRITTEN2}`;
7780
7907
  var RESOURCE_REFUSAL = Object.freeze({
7781
7908
  /** A plural outside SCAFFOLD_NAME_FORM. */
7782
7909
  resourceForm: `${SCAFFOLD_OPTION.resource} takes the plural of the resource the service keeps, the word of its table and its route: an ASCII lower-case letter, then ASCII lower-case letters and digits, at most 30 characters, such as orders. ${NOTHING_WRITTEN2}`,
@@ -7787,11 +7914,17 @@ var RESOURCE_REFUSAL = Object.freeze({
7787
7914
  /** A plural that does not end in s, with no `--singular`. */
7788
7915
  noClosingS: `${SCAFFOLD_OPTION.resource} takes a plural that ends in s where the line names no ${SCAFFOLD_OPTION.singular}, since the singular is then the plural without its closing s: name the singular with ${SCAFFOLD_OPTION.singular}, such as ${SCAFFOLD_OPTION.resource} people ${SCAFFOLD_OPTION.singular} person. ${NOTHING_WRITTEN2}`,
7789
7916
  /** The plural `s` alone, with no `--singular`: its singular would be empty. */
7790
- emptySingular: `${SCAFFOLD_OPTION.resource} takes a plural longer than s alone where the line names no ${SCAFFOLD_OPTION.singular}, since the singular, the plural without its closing s, is never empty: name the singular with ${SCAFFOLD_OPTION.singular}, or a longer plural. ${NOTHING_WRITTEN2}`
7917
+ emptySingular: `${SCAFFOLD_OPTION.resource} takes a plural longer than s alone where the line names no ${SCAFFOLD_OPTION.singular}, since the singular, the plural without its closing s, is never empty: name the singular with ${SCAFFOLD_OPTION.singular}, or a longer plural. ${NOTHING_WRITTEN2}`,
7918
+ /** A singular, given or derived, of `json` (#5225): the resource's
7919
+ * invalid error, invalid_<singular>, would then be invalid_json, the
7920
+ * error of a body that does not parse, and a caller could no longer
7921
+ * tell the two apart (PLD-L0-106). */
7922
+ jsonSingular: `${SCAFFOLD_OPTION.singular} takes a singular other than json, since the service answers invalid_json for a body that does not parse and invalid_ with the singular for a row it refuses: name another singular with ${SCAFFOLD_OPTION.singular}, such as ${SCAFFOLD_OPTION.resource} jsons ${SCAFFOLD_OPTION.singular} document. ${NOTHING_WRITTEN2}`
7791
7923
  });
7792
7924
  var RESOURCE_NONE_KEPT = `${SCAFFOLD_OPTION.resource} and ${SCAFFOLD_OPTION.singular} name the resource ${RESOURCE_TEMPLATES_NAMED} keep, and the template this line writes keeps none: leave them out, or name ${RESOURCE_TEMPLATE_CHOICES}. ${NOTHING_WRITTEN2}`;
7793
7925
  var UNKNOWN_TEMPLATE = `${SCAFFOLD_OPTION.template} names the template to write, and the command has no template of that name: it has ${listedAnd(SCAFFOLD_TEMPLATES)}, and it writes ${SCAFFOLD_DEFAULT_TEMPLATE} where the line names none. ${NOTHING_WRITTEN2}`;
7794
7926
  var ownRouteRefusal = (word) => `${SCAFFOLD_OPTION.resource} takes a plural other than ${word}, the word of a route the template serves itself: name another plural. ${NOTHING_WRITTEN2}`;
7927
+ var JSON_SINGULAR = "json";
7795
7928
  var resourceNamesOf = (call, template) => {
7796
7929
  const plural = call.named[SCAFFOLD_OPTION.resource];
7797
7930
  const singular = call.named[SCAFFOLD_OPTION.singular];
@@ -7802,10 +7935,11 @@ var resourceNamesOf = (call, template) => {
7802
7935
  if (singular !== void 0 && !SCAFFOLD_NAME_FORM.test(singular)) return { refusal: RESOURCE_REFUSAL.singularForm };
7803
7936
  const own = kept2.routes.find((word) => word === plural);
7804
7937
  if (own !== void 0) return { refusal: ownRouteRefusal(own) };
7805
- if (singular !== void 0) return { plural, singular };
7938
+ if (singular !== void 0) return singular === JSON_SINGULAR ? { refusal: RESOURCE_REFUSAL.jsonSingular } : { plural, singular };
7806
7939
  if (!plural.endsWith("s")) return { refusal: RESOURCE_REFUSAL.noClosingS };
7807
7940
  if (plural.length === 1) return { refusal: RESOURCE_REFUSAL.emptySingular };
7808
- return { plural, singular: plural.slice(0, -1) };
7941
+ const derived = plural.slice(0, -1);
7942
+ return derived === JSON_SINGULAR ? { refusal: RESOURCE_REFUSAL.jsonSingular } : { plural, singular: derived };
7809
7943
  };
7810
7944
  var CARRIED_FIELDS = Object.freeze([Object.freeze({ name: "title", kind: "text" })]);
7811
7945
  var FIELD_KINDS_CLAUSE = `the kind ${orListed(SCAFFOLD_FIELD_KINDS)}`;
@@ -7857,10 +7991,12 @@ var scaffoldOptionsOf = (call, cwd) => {
7857
7991
  if (names !== null && "refusal" in names) return names;
7858
7992
  const fields = fieldsOf(call, template);
7859
7993
  if (fields !== null && "refusal" in fields) return fields;
7994
+ const application = call.named[SCAFFOLD_OPTION.application];
7995
+ if (application !== void 0 && !UPLOAD_ID.test(application)) return { refusal: APPLICATION_FORM_REFUSAL };
7860
7996
  const path = call.named[SCAFFOLD_OPTION.path];
7861
7997
  const origin = commandOriginOf(call.named[SCAFFOLD_OPTION.origin] ?? DEFAULT_COMMAND_ORIGIN);
7862
7998
  if ("refusal" in origin) return origin;
7863
- return { template, names, fields, root: resolve11(cwd, path ?? "."), named: path !== void 0, origin: origin.origin };
7999
+ return { template, names, fields, root: resolve11(cwd, path ?? "."), named: path !== void 0, origin: origin.origin, application: application ?? null };
7864
8000
  };
7865
8001
  var capitalised = (name) => `${name.charAt(0).toUpperCase()}${name.slice(1)}`;
7866
8002
  var resourceFormsOf = (names) => [
@@ -8179,9 +8315,36 @@ var manifestWithMinute = (text, minute) => {
8179
8315
  if (!WRITTEN_MINUTE.test(String(minute))) throw new Error("a minute a run writes is a whole number from 0 to 59");
8180
8316
  return `${around[0]}${CRON_HEAD}${minute} ${around[1]}`;
8181
8317
  };
8182
- var composedFilesOf = (files, rows, minute) => files.map(([path, text]) => [path, path === APPLICATION_MANIFEST ? manifestWithMinute(manifestWithPackages(text, rows), minute) : text]);
8318
+ var MANIFEST_VERSION_LINE = ' "manifest_version": 1,\n';
8319
+ var APPLICATION_LINE_OPEN = ' "application": "';
8320
+ var applicationLineOf = (application) => `${APPLICATION_LINE_OPEN}${application}",
8321
+ `;
8322
+ var manifestWithApplication = (text, application) => {
8323
+ if (application === null) return text;
8324
+ const around = text.split(MANIFEST_VERSION_LINE);
8325
+ if (around.length !== 2) throw new Error("the template's manifest does not hold one manifest_version line");
8326
+ return `${around[0]}${MANIFEST_VERSION_LINE}${applicationLineOf(application)}${around[1]}`;
8327
+ };
8328
+ var composedFilesOf = (files, rows, minute, application = null) => files.map(([path, text]) => [path, path === APPLICATION_MANIFEST ? manifestWithApplication(manifestWithMinute(manifestWithPackages(text, rows), minute), application) : text]);
8329
+ var APPLICATION_LINE_REST = 'hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh",';
8330
+ var applicationLineRestPart = (text) => text.length <= APPLICATION_LINE_REST.length && [...text].every((character, index) => APPLICATION_LINE_REST[index] === "h" ? /^[0-9a-f]$/.test(character) : character === APPLICATION_LINE_REST[index]);
8331
+ var withoutApplicationLine = (staged, text) => {
8332
+ const at = text.indexOf(MANIFEST_VERSION_LINE);
8333
+ if (at < 0) return staged;
8334
+ const head = text.slice(0, at + MANIFEST_VERSION_LINE.length);
8335
+ if (staged.length <= head.length || !staged.startsWith(head)) return staged;
8336
+ const rest = staged.slice(head.length);
8337
+ if (APPLICATION_LINE_OPEN.startsWith(rest)) return true;
8338
+ if (!rest.startsWith(APPLICATION_LINE_OPEN)) return staged;
8339
+ const after = rest.slice(APPLICATION_LINE_OPEN.length);
8340
+ const lineEnd = after.indexOf("\n");
8341
+ if (lineEnd < 0) return applicationLineRestPart(after);
8342
+ const line = after.slice(0, lineEnd);
8343
+ if (line.length !== APPLICATION_LINE_REST.length || !applicationLineRestPart(line)) return false;
8344
+ return `${head}${after.slice(lineEnd + 1)}`;
8345
+ };
8183
8346
  var inside2 = (parent, child) => {
8184
- const between = relative5(parent, child);
8347
+ const between = relative6(parent, child);
8185
8348
  return between !== "" && !between.startsWith("..") && !isAbsolute5(between);
8186
8349
  };
8187
8350
  var ownManifestText = (staged, text) => {
@@ -8189,7 +8352,9 @@ var ownManifestText = (staged, text) => {
8189
8352
  if (around.length !== 2) return false;
8190
8353
  const carried = text.split(CARRIED_CRON);
8191
8354
  if (carried.length > 2) return false;
8192
- let read2 = staged;
8355
+ const unbound = withoutApplicationLine(staged, text);
8356
+ if (typeof unbound === "boolean") return unbound;
8357
+ let read2 = unbound;
8193
8358
  if (carried.length === 2) {
8194
8359
  const head = `${carried[0]}${CRON_HEAD}`;
8195
8360
  if (read2.length > head.length) {
@@ -8223,18 +8388,18 @@ var ownStage = (io2, stage, files) => {
8223
8388
  const folders = new Set([...texts.keys()].flatMap((path) => path.split("/").slice(0, -1).map((_, index, segments) => segments.slice(0, index + 1).join("/"))));
8224
8389
  const own = (at, prefix) => (io2.listFolder(at) ?? []).every((name) => {
8225
8390
  const path = prefix === "" ? name : `${prefix}/${name}`;
8226
- const standing = io2.stat(join9(at, name));
8391
+ const standing = io2.stat(join10(at, name));
8227
8392
  if (standing === null) return false;
8228
- if (standing.folder) return folders.has(path) && own(join9(at, name), path);
8393
+ if (standing.folder) return folders.has(path) && own(join10(at, name), path);
8229
8394
  const text = texts.get(path);
8230
- return text !== void 0 && ownFile(io2, join9(at, name), standing.size, path, text);
8395
+ return text !== void 0 && ownFile(io2, join10(at, name), standing.size, path, text);
8231
8396
  });
8232
8397
  return own(stage, "");
8233
8398
  };
8234
- var danglingAt = (io2, folder, name) => io2.stat(join9(folder, name)) === null && (io2.listFolder(folder) ?? []).some((held) => held.toLowerCase() === name.toLowerCase() && io2.stat(join9(folder, held)) === null);
8399
+ var danglingAt = (io2, folder, name) => io2.stat(join10(folder, name)) === null && (io2.listFolder(folder) ?? []).some((held) => held.toLowerCase() === name.toLowerCase() && io2.stat(join10(folder, held)) === null);
8235
8400
  var standsAt = (io2, folder, name) => {
8236
8401
  try {
8237
- return io2.stat(join9(folder, name)) !== null || danglingAt(io2, folder, name);
8402
+ return io2.stat(join10(folder, name)) !== null || danglingAt(io2, folder, name);
8238
8403
  } catch {
8239
8404
  return true;
8240
8405
  }
@@ -8269,8 +8434,8 @@ var folderRefusalOf = (io2, options, files, takeLines) => {
8269
8434
  }
8270
8435
  if (standing2 !== null || dirname6(at) === at) break;
8271
8436
  }
8272
- const app = join9(root, APPLICATION_FOLDER);
8273
- const stage = join9(root, STAGE_FOLDER);
8437
+ const app = join10(root, APPLICATION_FOLDER);
8438
+ const stage = join10(root, STAGE_FOLDER);
8274
8439
  const notOwn = STAGE_NOT_OWN;
8275
8440
  const standing = io2.stat(app);
8276
8441
  if (standing === null ? danglingAt(io2, root, APPLICATION_FOLDER) : !standing.folder) return [noFolderSentence(APPLICATION_FOLDER, "the scaffold writes the application")];
@@ -8379,7 +8544,7 @@ var nextStepLines = (origin, windows = LINES_IN_WINDOWS_FORM, left = NEITHER_LEF
8379
8544
  var writeTemplate = (io2, stage, app, files) => {
8380
8545
  io2.remove(stage);
8381
8546
  for (const [path, text] of files) {
8382
- const at = join9(stage, ...path.split("/"));
8547
+ const at = join10(stage, ...path.split("/"));
8383
8548
  io2.makeFolder(dirname6(at));
8384
8549
  io2.writeFile(at, text);
8385
8550
  }
@@ -8392,7 +8557,7 @@ var writeTemplate = (io2, stage, app, files) => {
8392
8557
  };
8393
8558
  var writeRootSentence = (io2, root) => {
8394
8559
  try {
8395
- io2.writeNewFile(join9(root, TOOLS_FILE), `${ROOT_SENTENCE}
8560
+ io2.writeNewFile(join10(root, TOOLS_FILE), `${ROOT_SENTENCE}
8396
8561
  `);
8397
8562
  return "written";
8398
8563
  } catch (error) {
@@ -8449,9 +8614,11 @@ async function scaffold(call, io2) {
8449
8614
  const { takes } = TEMPLATE_FACTS[template];
8450
8615
  const defaulted = call.named[SCAFFOLD_OPTION.template] === void 0;
8451
8616
  const files = writtenFilesOf(template, options.names, options.fields);
8452
- const app = join9(root, APPLICATION_FOLDER);
8453
- const stage = join9(root, STAGE_FOLDER);
8454
- const takeLines = takes.map((take) => machineLineOf(libraryTakeLineOf({ origin, entry: take.entry }), { windows: io2.windows ?? LINES_IN_WINDOWS_FORM, npx: ranThroughNpx(io2) }, origin));
8617
+ const app = join10(root, APPLICATION_FOLDER);
8618
+ const stage = join10(root, STAGE_FOLDER);
8619
+ const takeLinesFor = (at) => takes.map((take) => machineLineOf(libraryTakeLineOf({ origin: at, entry: take.entry }), { windows: io2.windows ?? LINES_IN_WINDOWS_FORM, npx: ranThroughNpx(io2) }, at));
8620
+ let lineOrigin = origin;
8621
+ let takeLines = takeLinesFor(lineOrigin);
8455
8622
  let reached = "checks";
8456
8623
  const stateNow = () => {
8457
8624
  if (reached === "template") return "template";
@@ -8478,10 +8645,14 @@ async function scaffold(call, io2) {
8478
8645
  if ("lines" in found) return refuse(...found.lines);
8479
8646
  held = found;
8480
8647
  } else {
8648
+ const credential = await readTakeCredential(io2, call.named[SCAFFOLD_OPTION.origin], SCAFFOLD_AFTER_LOGIN);
8649
+ if ("lines" in credential) return refuse(...credential.lines);
8650
+ lineOrigin = credential.origin;
8651
+ takeLines = takeLinesFor(lineOrigin);
8481
8652
  for (const take of takes) {
8482
- const copy = join9(app, "lib", take.entry);
8653
+ const copy = join10(app, "lib", take.entry);
8483
8654
  const planOf = (library) => writesOver(library, copy) ? { refusal: LIBRARY_OVER_APPLICATION } : { application: app, copy, declared: null, declaredAt: null };
8484
- const read2 = await readTake(io2, { entry: take.entry, root, named: options.named, origin }, agent.userAgent, planOf, checks.length > 0);
8655
+ const read2 = await readTake(io2, { entry: take.entry, root, named: options.named, origin: credential.origin }, agent.userAgent, credential.bearer, planOf, checks.length > 0);
8485
8656
  if ("lines" in read2) return refuse(...read2.lines);
8486
8657
  if (read2.plan === null) {
8487
8658
  return refuse(`The ${take.entry} entry the library served carries no compiled modules, which the service's package file declares and installs. That is the platform's to fix: report it. ${NOTHING_WRITTEN2}`);
@@ -8502,7 +8673,7 @@ async function scaffold(call, io2) {
8502
8673
  reached = "writes";
8503
8674
  if (io2.stat(root) === null) io2.makeFolder(root);
8504
8675
  if (held.fresh) io2.makeFolder(held.library);
8505
- writeTemplate(io2, stage, app, composedFilesOf(files, held.packages, minute));
8676
+ writeTemplate(io2, stage, app, composedFilesOf(files, held.packages, minute, options.application));
8506
8677
  reached = "template";
8507
8678
  const tools = toolsStands ? "stands" : writeRootSentence(io2, root);
8508
8679
  if (defaulted) io2.print(DEFAULT_TEMPLATE_LINE);
@@ -8514,7 +8685,7 @@ async function scaffold(call, io2) {
8514
8685
  release();
8515
8686
  if (ended) return COMMAND_STATUS.unknown;
8516
8687
  const taken = checks.map((check2, index) => ({ entry: takes[index].entry, version: check2.entry.version, line: takeLines[index] }));
8517
- return await installAndTest(io2, app, template, taken, origin, { tools, claude: claudeStands });
8688
+ return await installAndTest(io2, app, template, taken, lineOrigin, { tools, claude: claudeStands });
8518
8689
  } catch (error) {
8519
8690
  const state = stateNow();
8520
8691
  const arrivedAs = error instanceof ApplicationArrived ? state : null;
@@ -8564,11 +8735,15 @@ async function installAndTest(io2, app, template, taken, origin, left) {
8564
8735
  for (const line of nextStepLines(origin, { windows: io2.windows ?? LINES_IN_WINDOWS_FORM, npx: ranThroughNpx(io2) }, left, template)) io2.print(line);
8565
8736
  return COMMAND_STATUS.done;
8566
8737
  }
8738
+ var SCAFFOLD_CREDENTIAL = LIBRARY_CREDENTIAL;
8739
+ var SCAFFOLD_READS = `under a template that takes library entries what library take reads, ${LIBRARY_READS}; and under a template that takes none, no credential`;
8740
+ var SCAFFOLD_AFTER_LOGIN = `Once ${LOGIN_SUBCOMMAND} has printed authorized:, run this scaffold line again. ${NOTHING_WRITTEN2}`;
8567
8741
  var SCAFFOLD_ROW = Object.freeze({
8568
8742
  name: SCAFFOLD_SUBCOMMAND,
8569
- summary: `Writes a new service from a template into a project, ${SCAFFOLD_DEFAULT_TEMPLATE} where the line names no ${SCAFFOLD_OPTION.template}. A template that takes library entries, as database-service takes the Database entry and web-app takes three, takes each as library take does, checking each file's SHA-256 and each entry's closure hash before it writes anything. It then writes the template's files under app/, the manifest's packages member filled from the library folder's rows, runs npm install and npm test there, and prints the next steps. It starts a new application and writes over none. It reads no credential.`,
8743
+ summary: `Writes a new service from a template into a project, ${SCAFFOLD_DEFAULT_TEMPLATE} where the line names no ${SCAFFOLD_OPTION.template}. A template that takes library entries, as database-service takes the Database entry and web-app takes three, takes each as library take does, under the credential library take reads, checking each file's SHA-256 and each entry's closure hash before it writes anything. It then writes the template's files under app/, the manifest's packages member filled from the library folder's rows, runs npm install and npm test there, and prints the next steps. It starts a new application and writes over none. A template that takes no entry reads no credential.`,
8570
8744
  options: SCAFFOLD_OPTIONS,
8571
- credential: null,
8745
+ credential: SCAFFOLD_CREDENTIAL,
8746
+ reads: SCAFFOLD_READS,
8572
8747
  statuses: {
8573
8748
  done: "the files written and installed, the entries taken where the template takes any, and the tests passed",
8574
8749
  failed: "the files written, and npm install or the tests failed",
@@ -8577,6 +8752,7 @@ var SCAFFOLD_ROW = Object.freeze({
8577
8752
  },
8578
8753
  note: [
8579
8754
  `Run the ${SCAFFOLD_SUBCOMMAND} line at the root of a new project, or name the root with ${SCAFFOLD_OPTION.path}, which the command makes where it does not stand. A line that names no ${SCAFFOLD_OPTION.template} writes ${SCAFFOLD_DEFAULT_TEMPLATE}, which takes no library entry and makes no request, and says so in a line before the files it wrote. Under scheduled-job, the minute of the schedule's cron in the manifest is the minute of the hour, in UTC, at which the run started.`,
8755
+ `With ${SCAFFOLD_OPTION.application}, the manifest names that application in its application member, on the line after manifest_version, which binds the folder to it before any deploy: a deploy or check line naming another application is refused. Where absent, the first deploy writes the member.`,
8580
8756
  `Under ${listedAnd(RESOURCE_TEMPLATES)}, with ${SCAFFOLD_OPTION.resource}, and ${SCAFFOLD_OPTION.singular} where the singular is not the plural without its closing s, it writes the same service under the resource's names, its table, its route, its functions, and its file names among them, and runs the tests on the files as written. It refuses, writing nothing, where the root's ${APPLICATION_FOLDER}/ holds any file or folder, and it takes no option that overwrites: where the template takes entries, its sentence names the ${LIBRARY_SUBCOMMAND} take line for each, which adds the package to an application that stands.`,
8581
8757
  `A root that holds neither ${LIBRARY_FOLDER}/ nor ${POINTER_FILE} takes a ${LIBRARY_FOLDER}/ of its own. Where the root holds no ${TOOLS_FILE}, it writes one of one sentence, which sends a coding tool to ${APPLICATION_FOLDER}/${TOOLS_FILE}; it changes no file that stands at the root, and where an ${TOOLS_FILE} or a ${CLAUDE_FILE} stands there it prints the sentence to add.`,
8582
8758
  `${SCAFFOLD_SUBCOMMAND} leaves ${COMMAND_CREDENTIAL_VARIABLES.join(" and ")} out of what npm install and npm test receive. An interrupt reaches npm, the command ends after it, and after an interrupted install the tests are not started. A signal that arrives after the command's first write and before npm starts is acted on once the writes are made: the command prints the way on, starts no npm, and ends with 128 and the signal's number.`,
@@ -8759,6 +8935,7 @@ var startErrorOf = (error) => {
8759
8935
  var HELD_SIGNALS = Object.freeze(["SIGINT", "SIGTERM", "SIGHUP"]);
8760
8936
  var endedStatusOf = (signal) => 128 + (constants2.signals[signal] ?? 0);
8761
8937
  var SIGNALS_PASS_ON = process.platform !== "win32";
8938
+ var CHILD_GROUPS = process.platform !== "win32";
8762
8939
  var passedOnSignalOf = (signal, interrupts, inputIsTerminal, passesOn = SIGNALS_PASS_ON) => {
8763
8940
  if (!passesOn) return null;
8764
8941
  if (signal !== "SIGINT") return signal;
@@ -8783,16 +8960,18 @@ var holdSignals = (signals, inputIsTerminal, passesOn, passOn, onInterrupt) => {
8783
8960
  for (const [signal, handler] of held) signals.off(signal, handler);
8784
8961
  };
8785
8962
  };
8786
- var childRunnerOf = (signals, inputIsTerminal, passesOn = SIGNALS_PASS_ON) => (run) => new Promise((resolve12) => {
8963
+ var childRunnerOf = (signals, inputIsTerminal, passesOn = SIGNALS_PASS_ON, groups = CHILD_GROUPS) => (run) => new Promise((resolve12) => {
8787
8964
  const output = run.output;
8788
8965
  let interrupted = false;
8966
+ const group = run.foreground === true && passesOn && groups;
8789
8967
  try {
8790
8968
  const child = spawn(run.command, [...run.args], {
8791
8969
  cwd: run.cwd,
8792
8970
  env: run.env,
8793
8971
  shell: run.shell ?? false,
8794
8972
  stdio: ["ignore", output ? "pipe" : "inherit", output ? "pipe" : "inherit"],
8795
- windowsHide: false
8973
+ windowsHide: false,
8974
+ detached: group
8796
8975
  });
8797
8976
  if (output) {
8798
8977
  child.stdout?.setEncoding("utf8").on("data", (chunk) => output(chunk, "stdout"));
@@ -8803,9 +8982,17 @@ var childRunnerOf = (signals, inputIsTerminal, passesOn = SIGNALS_PASS_ON) => (r
8803
8982
  if (run.foreground) {
8804
8983
  const release = holdSignals(
8805
8984
  signals,
8806
- inputIsTerminal(),
8985
+ // A group of its own hears no terminal's interrupt, so the command hands on the first one too.
8986
+ group ? false : inputIsTerminal(),
8807
8987
  passesOn,
8808
8988
  (signal) => {
8989
+ if (group && child.pid !== void 0) {
8990
+ try {
8991
+ process.kill(-child.pid, signal);
8992
+ return;
8993
+ } catch {
8994
+ }
8995
+ }
8809
8996
  child.kill(signal);
8810
8997
  },
8811
8998
  () => {
@@ -9182,7 +9369,7 @@ async function runGuarded(argv2, io2) {
9182
9369
  if (parsed.kind === "refused") {
9183
9370
  say(carriesTokenCode(argv2) ? `${parsed.sentence} ${TOKEN_CODE_UNREAD}` : parsed.sentence);
9184
9371
  const npx = "ranThroughNpx" in io2 && io2.ranThroughNpx === true;
9185
- const help = npx ? npxFormOf(`${SERVED} --help`, io2.env[ORIGIN_VARIABLE] ?? DEFAULT_COMMAND_ORIGIN) : `${SERVED} --help`;
9372
+ const help = npx ? npxFormOf(`${SERVED} --help`, originReadOf(io2.env[ORIGIN_VARIABLE])) : `${SERVED} --help`;
9186
9373
  say(`${io2.windows ?? ON_WINDOWS ? windowsLineOf(help) : help} prints the usage.`);
9187
9374
  return COMMAND_STATUS.nothing_started;
9188
9375
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turnzero",
3
- "version": "0.18.3",
3
+ "version": "0.20.0-next.1",
4
4
  "type": "module",
5
5
  "description": "The Turn Zero Cloud command, which an AI coding tool runs on your machine to deploy an application, store a secret, upload a file, download an export, take a library entry, write a local run's settings, start the application locally, and scaffold a new service from a template.",
6
6
  "license": "Apache-2.0",