turnzero 0.18.3 → 0.19.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
 
@@ -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 };
@@ -2922,6 +2927,76 @@ function healthPathReadingOf(bytes, reading, recorded) {
2922
2927
  };
2923
2928
  }
2924
2929
 
2930
+ // src/turnzero_cloud_command/application_manifest.ts
2931
+ import { join as join3, relative as relative3 } from "node:path";
2932
+ var FOLDER_MANIFEST = "manifest.json";
2933
+ var APPLICATION_MEMBER = "application";
2934
+ var BYTE_ORDER_MARK3 = "\uFEFF";
2935
+ var jsonTextOf = (bytes) => {
2936
+ const text = bytes.toString("utf8");
2937
+ return text.startsWith(BYTE_ORDER_MARK3) ? text.slice(BYTE_ORDER_MARK3.length) : text;
2938
+ };
2939
+ var applicationManifestPartialOf = (at) => `${at}.partial`;
2940
+ var NOT_BOUND = Object.freeze({ kind: "unbound", manifest: null });
2941
+ var folderBindingOf = (io2, path) => {
2942
+ let text;
2943
+ let at;
2944
+ try {
2945
+ const folder = io2.realPath(path);
2946
+ if (folder === null) return NOT_BOUND;
2947
+ at = join3(folder, FOLDER_MANIFEST);
2948
+ const real = io2.realPath(at);
2949
+ if (real === null || relative3(at, real) !== "") return NOT_BOUND;
2950
+ const standing = io2.stat(at);
2951
+ if (standing === null || standing.folder) return NOT_BOUND;
2952
+ text = jsonTextOf(io2.readFile(at));
2953
+ } catch {
2954
+ return NOT_BOUND;
2955
+ }
2956
+ let parsed;
2957
+ try {
2958
+ parsed = JSON.parse(text);
2959
+ } catch {
2960
+ return NOT_BOUND;
2961
+ }
2962
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return NOT_BOUND;
2963
+ const members = parsed;
2964
+ if (!Object.prototype.hasOwnProperty.call(members, APPLICATION_MEMBER)) return { kind: "unbound", manifest: { at, parsed: members } };
2965
+ const application = members[APPLICATION_MEMBER];
2966
+ return typeof application === "string" && UPLOAD_ID.test(application) ? { kind: "bound", application } : { kind: "off_form" };
2967
+ };
2968
+ var BOUND_LINE_HEAD = "bound: ";
2969
+ var boundLineOf = (application) => `${BOUND_LINE_HEAD}${FOLDER_MANIFEST} now names application ${application}; a deploy or check line naming another application is refused`;
2970
+ 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.`;
2971
+ var boundElsewhereSentence = (bound) => `${FOLDER_MANIFEST} binds this folder to application ${bound}, and this line names another.`;
2972
+ 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.`;
2973
+ 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.`;
2974
+ 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.`;
2975
+ var bindingRefusalOf = (binding, application, closing) => {
2976
+ if (binding.kind === "unbound" || binding.kind === "bound" && binding.application === application) return null;
2977
+ const first = binding.kind === "bound" ? boundElsewhereSentence(binding.application) : OFF_FORM_SENTENCE;
2978
+ return `${first} ${closing} ${BINDING_OWNER_ASK}`;
2979
+ };
2980
+ var manifestTextWithMember = (parsed, member, value) => `${JSON.stringify({ ...parsed, [member]: value }, null, 2)}
2981
+ `;
2982
+ var writeManifestMember = (io2, manifest, member, value) => {
2983
+ const partial = applicationManifestPartialOf(manifest.at);
2984
+ let made = false;
2985
+ try {
2986
+ if (io2.stat(partial)?.folder === false) io2.remove(partial);
2987
+ made = io2.stat(partial) === null;
2988
+ io2.writeNewFile(partial, manifestTextWithMember(manifest.parsed, member, value));
2989
+ io2.rename(partial, manifest.at);
2990
+ } catch {
2991
+ try {
2992
+ if (made) io2.remove(partial);
2993
+ } catch {
2994
+ }
2995
+ return false;
2996
+ }
2997
+ return true;
2998
+ };
2999
+
2925
3000
  // src/turnzero_cloud_command/deploy.ts
2926
3001
  import { createHash as createHash2 } from "node:crypto";
2927
3002
  import { readFileSync as readFileSync4, realpathSync, statSync as statSync3 } from "node:fs";
@@ -2929,7 +3004,7 @@ import { resolve as resolve4 } from "node:path";
2929
3004
 
2930
3005
  // src/turnzero_cloud_command/commit.ts
2931
3006
  import { readFileSync as readFileSync2, statSync } from "node:fs";
2932
- import { dirname as dirname3, join as join3, resolve as resolve3 } from "node:path";
3007
+ import { dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
2933
3008
  var START_COMMIT_FORM = /^[0-9a-f]{7,64}$/;
2934
3009
  var GIT_ENTRY = ".git";
2935
3010
  var SYMBOLIC_DEPTH_MAX = 5;
@@ -2959,7 +3034,7 @@ var firstLineOf = (text) => (text.split(/\r?\n/, 1)[0] ?? "").trim();
2959
3034
  var gitFolderOf = (folder, reads = FILE_SYSTEM_READS) => {
2960
3035
  let dir = resolve3(folder);
2961
3036
  for (; ; ) {
2962
- const entry = join3(dir, GIT_ENTRY);
3037
+ const entry = join4(dir, GIT_ENTRY);
2963
3038
  const kind = reads.kind(entry);
2964
3039
  if (kind === "folder") return entry;
2965
3040
  if (kind === "file") {
@@ -2977,7 +3052,7 @@ var gitFolderOf = (folder, reads = FILE_SYSTEM_READS) => {
2977
3052
  }
2978
3053
  };
2979
3054
  var sharedGitFolderOf = (gitFolder, reads = FILE_SYSTEM_READS) => {
2980
- const text = reads.text(join3(gitFolder, "commondir"));
3055
+ const text = reads.text(join4(gitFolder, "commondir"));
2981
3056
  const named = text === null ? "" : firstLineOf(text);
2982
3057
  if (named === "") return gitFolder;
2983
3058
  const shared = resolve3(gitFolder, named);
@@ -2987,9 +3062,9 @@ var followable = (ref) => ref.startsWith("refs/") && ref.split("/").every((segme
2987
3062
  var refValueOf = (reads, gitFolder, sharedFolder, ref, depth) => {
2988
3063
  if (depth > SYMBOLIC_DEPTH_MAX || !followable(ref)) return null;
2989
3064
  const home = PER_WORKTREE_REFS.some((prefix) => ref.startsWith(prefix)) ? gitFolder : sharedFolder;
2990
- const loose = reads.text(join3(home, ...ref.split("/")));
3065
+ const loose = reads.text(join4(home, ...ref.split("/")));
2991
3066
  if (loose !== null) return valueOf(reads, gitFolder, sharedFolder, firstLineOf(loose), depth + 1);
2992
- const packed = reads.text(join3(sharedFolder, "packed-refs"));
3067
+ const packed = reads.text(join4(sharedFolder, "packed-refs"));
2993
3068
  if (packed === null) return null;
2994
3069
  for (const line of packed.split(/\r?\n/)) {
2995
3070
  if (line.startsWith("#") || line.startsWith("^")) continue;
@@ -3006,7 +3081,7 @@ var headCommitOf = (folder, reads = FILE_SYSTEM_READS) => {
3006
3081
  try {
3007
3082
  const gitFolder = gitFolderOf(folder, reads);
3008
3083
  if (gitFolder === null) return null;
3009
- const head = reads.text(join3(gitFolder, "HEAD"));
3084
+ const head = reads.text(join4(gitFolder, "HEAD"));
3010
3085
  if (head === null) return null;
3011
3086
  const sharedFolder = sharedGitFolderOf(gitFolder, reads);
3012
3087
  if (sharedFolder === null) return null;
@@ -3105,7 +3180,7 @@ async function writeAgain(io2, what, write) {
3105
3180
 
3106
3181
  // src/turnzero_cloud_command/zip.ts
3107
3182
  import { readdirSync, readFileSync as readFileSync3, statSync as statSync2 } from "node:fs";
3108
- import { join as join4 } from "node:path";
3183
+ import { join as join5 } from "node:path";
3109
3184
  import * as zlib from "node:zlib";
3110
3185
  import { deflateRawSync, inflateRawSync as inflateRawSync2 } from "node:zlib";
3111
3186
  var AI_TOOL_NAMES = Object.freeze([
@@ -3144,7 +3219,7 @@ var walkFolder = (root, limit, leftOut) => {
3144
3219
  leftOut?.push(entry.isDirectory() ? `${name}/` : name);
3145
3220
  continue;
3146
3221
  }
3147
- const path = join4(folder.path, entry.name);
3222
+ const path = join5(folder.path, entry.name);
3148
3223
  if (entry.isSymbolicLink()) throw new ZipRefusal(linkRefusalSentence(name));
3149
3224
  if (entry.isDirectory()) {
3150
3225
  pending.push({ path, name });
@@ -3234,7 +3309,7 @@ var NO_PACKAGE_SENTENCE = "The folder holds no package.json at its root, so it i
3234
3309
  function zipFolder(root, seams = {}) {
3235
3310
  let rootFile = false;
3236
3311
  try {
3237
- rootFile = statSync2(join4(root, "package.json")).isFile();
3312
+ rootFile = statSync2(join5(root, "package.json")).isFile();
3238
3313
  } catch {
3239
3314
  rootFile = false;
3240
3315
  }
@@ -3440,6 +3515,7 @@ async function prepareDeploy(io2, options, zipSha256Value, bearer, userAgent, re
3440
3515
  return prepared;
3441
3516
  }
3442
3517
  var NOTHING_STARTED = "Nothing was started.";
3518
+ var BINDING_NOTHING_SENT = "Nothing further was read and nothing was sent.";
3443
3519
  var NOTHING_UPLOADED_OR_STARTED = "Nothing was uploaded and nothing was started.";
3444
3520
  var healthPathRefusalOf = (prepared, zip, places) => {
3445
3521
  if (prepared.healthPathToRead === void 0 || places === null) return null;
@@ -3536,6 +3612,12 @@ async function deploy(named, io2, flags = /* @__PURE__ */ new Set()) {
3536
3612
  }
3537
3613
  const origin = sent.origin;
3538
3614
  const refused = () => credential.source === "credential_file" ? fileReadSentence(origin) : variableReadSentence(origin, fileStandingOf(io2, origin, io2.print));
3615
+ const binding = pathKindOf(options.path) === "folder" ? folderBindingOf(io2, options.path) : null;
3616
+ const unbound = binding === null ? null : bindingRefusalOf(binding, options.application, BINDING_NOTHING_SENT);
3617
+ if (unbound !== null) {
3618
+ io2.print(unbound);
3619
+ return PROGRESS_EXIT_STATUS.nothing_started;
3620
+ }
3539
3621
  const zipped = zipOf(options.path, io2, NOTHING_STARTED, named[DEPLOY_OPTION.path] !== void 0);
3540
3622
  if ("refusal" in zipped) {
3541
3623
  io2.print(zipped.refusal);
@@ -3557,6 +3639,9 @@ async function deploy(named, io2, flags = /* @__PURE__ */ new Set()) {
3557
3639
  return PROGRESS_EXIT_STATUS.nothing_started;
3558
3640
  }
3559
3641
  io2.print(preparedLineOf(prepared));
3642
+ if (binding?.kind === "unbound" && binding.manifest !== null) {
3643
+ io2.print(writeManifestMember(io2, binding.manifest, APPLICATION_MEMBER, options.application) ? boundLineOf(options.application) : BIND_WRITE_FAILED_LINE);
3644
+ }
3560
3645
  const { upload, grant, environment } = prepared;
3561
3646
  const headers = { authorization: `Bearer ${grant}`, "user-agent": agent.userAgent };
3562
3647
  const stop = commandStopSentence(options.application, environment);
@@ -3667,7 +3752,7 @@ async function deploy(named, io2, flags = /* @__PURE__ */ new Set()) {
3667
3752
  }
3668
3753
  if (read2.status !== 200 || !read2.type.startsWith("text/plain")) {
3669
3754
  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)];
3755
+ 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
3756
  for (const line of said) io2.print(line);
3672
3757
  io2.print(stop);
3673
3758
  return PROGRESS_EXIT_STATUS.refused;
@@ -3702,7 +3787,7 @@ var DEPLOY_ROW = Object.freeze({
3702
3787
  done: `the version deployed, or the deploy started where ${DEPLOY_NO_WAIT_VARIABLE} is set`,
3703
3788
  failed: "the deploy failed",
3704
3789
  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"
3790
+ 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
3791
  },
3707
3792
  variables: [[DEPLOY_NO_WAIT_VARIABLE, "any value returns the command once the deploy has started, without waiting for it."]],
3708
3793
  note: [
@@ -3714,7 +3799,9 @@ var DEPLOY_ROW = Object.freeze({
3714
3799
  `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
3800
  `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
3801
  `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.`
3802
+ `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.`,
3803
+ `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.`,
3804
+ `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
3805
  ],
3719
3806
  afterOwnError: DEPLOY_AFTER_OWN_ERROR,
3720
3807
  run: (call, io2) => deploy(call.named, io2, call.flags)
@@ -3723,6 +3810,7 @@ var DEPLOY_ROW = Object.freeze({
3723
3810
  // src/turnzero_cloud_command/check.ts
3724
3811
  var NOTHING_SENT_OR_CHECKED = "Nothing was sent and nothing was checked.";
3725
3812
  var NOTHING_CHECKED = "Nothing was checked.";
3813
+ var BINDING_NOTHING_CHECKED = "Nothing further was read, and nothing was sent or checked.";
3726
3814
  var REFUSED_LINE_HEAD = "refused: ";
3727
3815
  var MANIFEST_LINE_HEAD = "manifest: ";
3728
3816
  var CHECKED_LINE_HEAD = "checked: ";
@@ -3914,6 +4002,12 @@ async function check(named, io2) {
3914
4002
  }
3915
4003
  const origin = sent.origin;
3916
4004
  const refused = () => credential.source === "credential_file" ? fileReadSentence(origin) : variableReadSentence(origin, fileStandingOf(io2, origin, io2.print));
4005
+ const binding = pathKindOf(options.path) === "folder" ? folderBindingOf(io2, options.path) : null;
4006
+ const unbound = binding === null ? null : bindingRefusalOf(binding, options.application, BINDING_NOTHING_CHECKED);
4007
+ if (unbound !== null) {
4008
+ io2.print(unbound);
4009
+ return COMMAND_STATUS.nothing_started;
4010
+ }
3917
4011
  const zipped = zipOf(options.path, io2, NOTHING_CHECKED, named[CHECK_OPTION.path] !== void 0, null, "checked");
3918
4012
  if ("refusal" in zipped) {
3919
4013
  io2.print(zipped.refusal);
@@ -3946,11 +4040,12 @@ var CHECK_ROW = Object.freeze({
3946
4040
  statuses: {
3947
4041
  done: "none of the checks would refuse a deploy now",
3948
4042
  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"
4043
+ 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
4044
  },
3951
4045
  note: [
3952
4046
  `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
4047
  `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.`,
4048
+ `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
4049
  `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
4050
  ],
3956
4051
  afterOwnError: CHECK_AFTER_OWN_ERROR,
@@ -3978,6 +4073,10 @@ async function hash(named, io2, flags = /* @__PURE__ */ new Set()) {
3978
4073
  io2.print(LIST_ZIP_FILE_SENTENCE);
3979
4074
  return PROGRESS_EXIT_STATUS.nothing_started;
3980
4075
  }
4076
+ if (pathKindOf(folder) === "folder" && folderBindingOf(io2, folder).kind === "off_form") {
4077
+ io2.print(`${OFF_FORM_SENTENCE} ${NO_HASH_PRINTED} ${HASH_BINDING_OWNER_ASK}`);
4078
+ return PROGRESS_EXIT_STATUS.nothing_started;
4079
+ }
3981
4080
  const zipped = zipOf(folder, io2, NO_HASH_PRINTED, path !== void 0);
3982
4081
  if ("refusal" in zipped) {
3983
4082
  io2.print(zipped.refusal);
@@ -3999,7 +4098,7 @@ var HASH_ROW = Object.freeze({
3999
4098
  });
4000
4099
 
4001
4100
  // 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";
4101
+ import { isAbsolute as isAbsolute3, join as join6, relative as relative4, resolve as resolve7, sep as sep2 } from "node:path";
4003
4102
 
4004
4103
  // src/turnzero_cloud_command/value.ts
4005
4104
  var NOTHING_SENT2 = "Nothing was sent.";
@@ -4204,15 +4303,15 @@ var secretUnknownSentence = (act) => `Whether the value was written is unknown:
4204
4303
  var PACKAGE_FILE = "package.json";
4205
4304
  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
4305
  var valueFileWouldDeploy = (io2, cwd, file) => {
4207
- const between = relative3(cwd, file);
4306
+ const between = relative4(cwd, file);
4208
4307
  if (between === "" || between === ".." || between.startsWith(`..${sep2}`) || isAbsolute3(between)) return false;
4209
4308
  const segments = between.split(sep2);
4210
4309
  if (segments.some(leftOutName)) return false;
4211
4310
  let folder = cwd;
4212
4311
  for (let depth = 0; depth < segments.length; depth += 1) {
4213
- if (depth > 0) folder = join5(folder, segments[depth - 1]);
4312
+ if (depth > 0) folder = join6(folder, segments[depth - 1]);
4214
4313
  try {
4215
- if (io2.stat(join5(folder, PACKAGE_FILE))?.folder === false) return true;
4314
+ if (io2.stat(join6(folder, PACKAGE_FILE))?.folder === false) return true;
4216
4315
  } catch {
4217
4316
  }
4218
4317
  }
@@ -4486,11 +4585,11 @@ var PUT_ROW = Object.freeze({
4486
4585
 
4487
4586
  // src/turnzero_cloud_command/library_take.ts
4488
4587
  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";
4588
+ 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
4589
 
4491
4590
  // src/turnzero_cloud_command/run.ts
4492
4591
  import { constants } from "node:os";
4493
- import { join as join6, resolve as resolve9, win32 as win323 } from "node:path";
4592
+ import { join as join7, resolve as resolve9, win32 as win323 } from "node:path";
4494
4593
  var RUN_OPTIONS = Object.freeze([
4495
4594
  {
4496
4595
  name: RUN_OPTION.path,
@@ -4674,7 +4773,7 @@ async function localRun(call, io2) {
4674
4773
  return COMMAND_STATUS.nothing_started;
4675
4774
  }
4676
4775
  try {
4677
- io2.readFile(join6(options.folder, PACKAGE_FILE2));
4776
+ io2.readFile(join7(options.folder, PACKAGE_FILE2));
4678
4777
  } catch (error) {
4679
4778
  io2.print(
4680
4779
  `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}`
@@ -4788,13 +4887,13 @@ var libraryTakeOptionsOf = (call, cwd) => {
4788
4887
  return { entry, root: resolve10(cwd, path ?? "."), named: path !== void 0, origin: origin.origin };
4789
4888
  };
4790
4889
  var inside = (parent, child) => {
4791
- const between = relative4(parent, child);
4890
+ const between = relative5(parent, child);
4792
4891
  return between !== "" && !between.startsWith("..") && !isAbsolute4(between);
4793
4892
  };
4794
- var shownFrom = (root) => (path) => shownName(relative4(root, path).split(sep3).join("/") || ".");
4893
+ var shownFrom = (root) => (path) => shownName(relative5(root, path).split(sep3).join("/") || ".");
4795
4894
  var repositoryRootOf = (io2, root) => {
4796
4895
  for (let at = root; ; at = dirname4(at)) {
4797
- if (io2.stat(join7(at, ".git")) !== null) return at;
4896
+ if (io2.stat(join8(at, ".git")) !== null) return at;
4798
4897
  if (dirname4(at) === at) return root;
4799
4898
  }
4800
4899
  };
@@ -4814,14 +4913,14 @@ var TAKE_FOLDER_WORDS = Object.freeze({
4814
4913
  manifestCannot: "so the take cannot add its row to it"
4815
4914
  });
4816
4915
  var libraryFolderOf = (io2, root, words = TAKE_FOLDER_WORDS) => {
4817
- const own = join7(root, LIBRARY_FOLDER);
4916
+ const own = join8(root, LIBRARY_FOLDER);
4818
4917
  const inRoot = { folder: root, called: ROOT_CALLED };
4819
4918
  const standing = io2.stat(own);
4820
4919
  if (standing !== null) {
4821
4920
  if (!standing.folder) return { refusal: `The project's ${LIBRARY_FOLDER} is a file, where the library folder stands. ${NOTHING_WRITTEN2}` };
4822
4921
  return { folder: own, fresh: false, within: inRoot };
4823
4922
  }
4824
- const pointerAt = join7(root, POINTER_FILE);
4923
+ const pointerAt = join8(root, POINTER_FILE);
4825
4924
  if (io2.stat(pointerAt) === null) return { folder: own, fresh: true, within: inRoot };
4826
4925
  let named;
4827
4926
  try {
@@ -4845,22 +4944,22 @@ var libraryFolderOf = (io2, root, words = TAKE_FOLDER_WORDS) => {
4845
4944
  return { folder: at, fresh: false, within: { folder: repository, called: REPOSITORY_CALLED } };
4846
4945
  };
4847
4946
  var applicationFolderRefusalOf = (io2, options) => {
4848
- const holds = (folder) => io2.stat(join7(folder, LIBRARY_FOLDER)) !== null || io2.stat(join7(folder, POINTER_FILE)) !== null;
4947
+ const holds = (folder) => io2.stat(join8(folder, LIBRARY_FOLDER)) !== null || io2.stat(join8(folder, POINTER_FILE)) !== null;
4849
4948
  const parent = dirname4(options.root);
4850
4949
  if (parent === options.root || holds(options.root) || !holds(parent)) return null;
4851
4950
  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
4951
  };
4853
4952
  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();
4953
+ 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
4954
  return [
4856
4955
  ...folders("features"),
4857
4956
  ...folders("ui").filter((name) => name !== "assets").map((name) => `ui/${name}`),
4858
4957
  ...folders("ui/assets/fonts").map((name) => `fonts/${name}`),
4859
- ...io2.stat(join7(library, "prd"))?.folder === true ? ["library/prd"] : []
4958
+ ...io2.stat(join8(library, "prd"))?.folder === true ? ["library/prd"] : []
4860
4959
  ];
4861
4960
  };
4862
4961
  var manifestRowsOf = (io2, library, words = TAKE_FOLDER_WORDS) => {
4863
- const at = join7(library, MANIFEST_FILE2);
4962
+ const at = join8(library, MANIFEST_FILE2);
4864
4963
  if (io2.stat(at) === null) return { rows: null, stood: null };
4865
4964
  try {
4866
4965
  const stood = io2.readFile(at);
@@ -4924,6 +5023,7 @@ var readOf = async (io2, origin, userAgent, body, what) => {
4924
5023
  if (status >= 200 && status <= 299 && answer !== null) return { answer };
4925
5024
  const refusal = markedRefusalOf(status, text);
4926
5025
  if (refusal === null) return { lines: [`${LIBRARY_READ_ACTION} was answered HTTP ${status}, not by the platform. ${NOTHING_WRITTEN2}`] };
5026
+ if (status >= PLATFORM_FAILURE_STATUS) return { lines: [...failureLines(LIBRARY_READ_ACTION, status, refusal), NOTHING_WRITTEN2] };
4927
5027
  return { lines: [...refusalLines(LIBRARY_READ_ACTION, status, refusal), NOTHING_WRITTEN2] };
4928
5028
  };
4929
5029
  var fileBytesOf = async (io2, origin, userAgent, name, file) => {
@@ -4979,11 +5079,6 @@ var ofModuleType = (plan, name) => {
4979
5079
  };
4980
5080
  var MADE_LINE_HEAD = "made: ";
4981
5081
  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
5082
  var openingCharacterClauseOf = (text) => {
4988
5083
  const first = text.codePointAt(0);
4989
5084
  if (first === void 0) return "";
@@ -4993,19 +5088,19 @@ var openingCharacterClauseOf = (text) => {
4993
5088
  const control = first < 32 && first !== 9 && first !== 10 && first !== 13 || first >= 127 && first <= 159;
4994
5089
  return control ? `: the file opens with ${point}, a control character` : "";
4995
5090
  };
4996
- var applicationFolderOf = (io2, root) => [join7(root, "app"), root].find((folder) => io2.stat(join7(folder, "package.json"))?.folder === false) ?? null;
5091
+ var applicationFolderOf = (io2, root) => [join8(root, "app"), root].find((folder) => io2.stat(join8(folder, "package.json"))?.folder === false) ?? null;
4997
5092
  var copyPlanOf = (io2, root, library, name) => {
4998
5093
  const shown2 = shownFrom(root);
4999
5094
  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
5095
  const application = applicationFolderOf(io2, root);
5001
5096
  if (application === null) {
5002
- const app = join7(root, MADE_FOLDER);
5097
+ const app = join8(root, MADE_FOLDER);
5003
5098
  const standing = io2.stat(app);
5004
5099
  if (standing !== null && !standing.folder || danglingLinkAt(io2, root, MADE_FOLDER)) {
5005
5100
  return { refusal: `Something that is no folder stands at ${MADE_FOLDER}, where the take would make the application's package.json. ${NOTHING_WRITTEN2}` };
5006
5101
  }
5007
- const copy2 = join7(app, "lib", name);
5008
- const madeFile = join7(app, "package.json");
5102
+ const copy2 = join8(app, "lib", name);
5103
+ const madeFile = join8(app, "package.json");
5009
5104
  if (io2.stat(madeFile) !== null || standing !== null && danglingLinkAt(io2, app, "package.json")) {
5010
5105
  return { refusal: `Something that is no package file stands at ${MADE_FOLDER}/package.json, where the take would make it. ${NOTHING_WRITTEN2}` };
5011
5106
  }
@@ -5018,7 +5113,7 @@ var copyPlanOf = (io2, root, library, name) => {
5018
5113
  const declaredAt2 = `file:lib/${name}`;
5019
5114
  return { application: app, copy: copy2, declared: madePackageFileOf(made, `${PACKAGE_SCOPE}${name}`, declaredAt2), declaredAt: declaredAt2, made };
5020
5115
  }
5021
- const packageFile = join7(application, "package.json");
5116
+ const packageFile = join8(application, "package.json");
5022
5117
  const read2 = io2.readFile(packageFile);
5023
5118
  const text = jsonTextOf(read2);
5024
5119
  let parsed;
@@ -5030,12 +5125,12 @@ var copyPlanOf = (io2, root, library, name) => {
5030
5125
  }
5031
5126
  const packageName = `${PACKAGE_SCOPE}${name}`;
5032
5127
  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}`;
5128
+ const place = application === root && io2.stat(join8(root, "app"))?.folder === true ? `app/lib/${name}` : `lib/${name}`;
5034
5129
  let copy;
5035
5130
  let declared = null;
5036
5131
  let declaredAt = null;
5037
5132
  if (specs.length === 0) {
5038
- copy = join7(application, ...place.split("/"));
5133
+ copy = join8(application, ...place.split("/"));
5039
5134
  declaredAt = `file:${place}`;
5040
5135
  declared = withDependency(text, parsed, packageName, declaredAt);
5041
5136
  } else {
@@ -5061,11 +5156,11 @@ var stageRefusalOf = (io2, stage, beside) => {
5061
5156
  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
5157
  };
5063
5158
  var moveFolderIn = (io2, stage, target, files) => {
5064
- const fresh = join7(stage, "new");
5065
- const old = join7(stage, "old");
5159
+ const fresh = join8(stage, "new");
5160
+ const old = join8(stage, "old");
5066
5161
  io2.remove(stage);
5067
5162
  for (const [path, bytes] of files) {
5068
- const at = join7(fresh, ...path.split("/"));
5163
+ const at = join8(fresh, ...path.split("/"));
5069
5164
  io2.makeFolder(dirname4(at));
5070
5165
  io2.writeFile(at, bytes);
5071
5166
  }
@@ -5105,15 +5200,14 @@ var PACKAGES_LINE_HEAD = "packages: ";
5105
5200
  var jsonEscapeOf = (character) => Array.from({ length: character.length }, (_, index) => `\\u${character.charCodeAt(index).toString(16).padStart(4, "0")}`).join("");
5106
5201
  var shownJson = (text) => [...text].map((character) => shownWhole(character) === character ? character : jsonEscapeOf(character)).join("");
5107
5202
  var packagesLineOf = (rows) => `${PACKAGES_LINE_HEAD}${shownJson(JSON.stringify(rows))}`;
5108
- var applicationManifestPartialOf = (at) => `${at}.partial`;
5109
5203
  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);
5204
+ 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
5205
  var linkStandsAt = (io2, path) => {
5112
5206
  const name = basename2(path);
5113
5207
  if (!(io2.listFolder(dirname4(path)) ?? []).includes(name)) return false;
5114
5208
  const real = io2.realPath(path);
5115
5209
  const folder = io2.realPath(dirname4(path));
5116
- return real === null || folder === null || relative4(join7(folder, name), real) !== "";
5210
+ return real === null || folder === null || relative5(join8(folder, name), real) !== "";
5117
5211
  };
5118
5212
  var writesThroughLink = (io2, holder, path) => linkStandsAt(io2, path) || !heldInside(io2, holder, path);
5119
5213
  var writtenNameRefusalOf = (io2, holder, names) => {
@@ -5122,11 +5216,11 @@ var writtenNameRefusalOf = (io2, holder, names) => {
5122
5216
  };
5123
5217
  var applicationManifestOf = (io2, root, library, folder = applicationFolderOf(io2, root)) => {
5124
5218
  if (folder === null) return { at: null, unwritten: "no_folder" };
5125
- const at = join7(folder, MANIFEST_FILE2);
5219
+ const at = join8(folder, MANIFEST_FILE2);
5126
5220
  const standing = io2.stat(at);
5127
5221
  if (standing === null) return { at, unwritten: "not_standing" };
5128
5222
  if (!heldInside(io2, root, at)) return { refusal: linkSentence("The application manifest", ROOT_CALLED) };
5129
- const libraryManifest = join7(library, MANIFEST_FILE2);
5223
+ const libraryManifest = join8(library, MANIFEST_FILE2);
5130
5224
  if (resolve10(at) === resolve10(libraryManifest) || realPlaceOf(io2, at) === realPlaceOf(io2, libraryManifest)) return { at, unwritten: "library_manifest" };
5131
5225
  if (standing.folder) return { at, unwritten: "no_object" };
5132
5226
  const partial = applicationManifestPartialOf(at);
@@ -5138,25 +5232,9 @@ var applicationManifestOf = (io2, root, library, folder = applicationFolderOf(io
5138
5232
  }
5139
5233
  return { at, unwritten: "no_object" };
5140
5234
  };
5141
- var applicationManifestTextOf = (parsed, packages) => `${JSON.stringify({ ...parsed, packages }, null, 2)}
5142
- `;
5143
5235
  var writeApplicationManifest = (io2, manifest, packages) => {
5144
5236
  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 };
5237
+ return writeManifestMember(io2, manifest, "packages", packages) ? { written: manifest.at } : { at: manifest.at, unwritten: "write_failed" };
5160
5238
  };
5161
5239
  var packagesWhereOf = (root, outcome) => {
5162
5240
  const shown2 = shownFrom(root);
@@ -5186,11 +5264,11 @@ async function readTake(io2, options, userAgent, planOf, planeChecked = false) {
5186
5264
  if ("refusal" in found) return refuse(found.refusal);
5187
5265
  const library = found.folder;
5188
5266
  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);
5267
+ const manifestAt = join8(library, MANIFEST_FILE2);
5190
5268
  const inLibrary = writtenNameRefusalOf(io2, found.within.folder, [
5191
5269
  [WRITTEN_NAMES.manifest, manifestAt],
5192
5270
  [WRITTEN_NAMES.manifestPartial, `${manifestAt}.partial`],
5193
- [WRITTEN_NAMES.libraryStage, join7(library, STAGE)]
5271
+ [WRITTEN_NAMES.libraryStage, join8(library, STAGE)]
5194
5272
  ]);
5195
5273
  if (inLibrary !== null) return refuse(inLibrary);
5196
5274
  const manifest = found.fresh ? { rows: null, stood: null } : manifestRowsOf(io2, library);
@@ -5204,7 +5282,7 @@ async function readTake(io2, options, userAgent, planOf, planeChecked = false) {
5204
5282
  if ("lines" in list) return refuse(...list.lines);
5205
5283
  const entry = listedEntryOf(list.answer, options.entry);
5206
5284
  if ("refusal" in entry) return refuse(entry.refusal);
5207
- const entryFolder = join7(library, ...entry.path.split("/"));
5285
+ const entryFolder = join8(library, ...entry.path.split("/"));
5208
5286
  if (!heldInside(io2, found.within.folder, entryFolder)) return refuse(linkSentence("The entry's folder", found.within.called));
5209
5287
  if (io2.stat(entryFolder) !== null && !(manifest.rows ?? []).some((row) => rowName(row) === entry.name)) {
5210
5288
  return refuse(sourceSentence(`holds ${entry.name}'s folder and no row for it in its ${MANIFEST_FILE2}`));
@@ -5214,11 +5292,11 @@ async function readTake(io2, options, userAgent, planOf, planeChecked = false) {
5214
5292
  if (plan !== null && "refusal" in plan) return refuse(plan.refusal, ...plan.after ?? []);
5215
5293
  if (plan !== null && !heldInside(io2, options.root, plan.copy)) return refuse(linkSentence("The application's copy", ROOT_CALLED));
5216
5294
  const besideCopy = plan === null ? null : writtenNameRefusalOf(io2, options.root, [
5217
- ...plan.declared !== null ? [[WRITTEN_NAMES.packageFile, join7(plan.application, "package.json")]] : [],
5295
+ ...plan.declared !== null ? [[WRITTEN_NAMES.packageFile, join8(plan.application, "package.json")]] : [],
5218
5296
  [WRITTEN_NAMES.copyStage, `${plan.copy}${STAGE}`]
5219
5297
  ]);
5220
5298
  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);
5299
+ const staged = stageRefusalOf(io2, join8(library, STAGE), "the library folder") ?? (plan !== null ? stageRefusalOf(io2, `${plan.copy}${STAGE}`, "the application's copy") : null);
5222
5300
  if (staged !== null) return refuse(staged);
5223
5301
  const bytes = /* @__PURE__ */ new Map();
5224
5302
  for (const file of entry.files) {
@@ -5242,11 +5320,11 @@ function writeTake(io2, root, { entry, library, entryFolder, rows, manifestStood
5242
5320
  source_commit: entry.source_commit,
5243
5321
  fetched_at: (/* @__PURE__ */ new Date()).toISOString()
5244
5322
  });
5245
- const manifestAt = join7(library, MANIFEST_FILE2);
5323
+ const manifestAt = join8(library, MANIFEST_FILE2);
5246
5324
  const manifestPartial = `${manifestAt}.partial`;
5247
5325
  io2.writeFile(manifestPartial, manifestText);
5248
5326
  io2.rename(manifestPartial, manifestAt);
5249
- const entryStage = join7(library, STAGE);
5327
+ const entryStage = join8(library, STAGE);
5250
5328
  try {
5251
5329
  moveFolderIn(
5252
5330
  io2,
@@ -5276,7 +5354,7 @@ function writeTake(io2, root, { entry, library, entryFolder, rows, manifestStood
5276
5354
  plan.copy,
5277
5355
  compiled.map((file) => [file.path.slice(entry.path.length + 1), bytes.get(file.path)])
5278
5356
  );
5279
- const packageFile = join7(plan.application, "package.json");
5357
+ const packageFile = join8(plan.application, "package.json");
5280
5358
  if (plan.declared !== null) io2.writeFile(packageFile, plan.declared);
5281
5359
  if (plan.made !== void 0) io2.print(madeLineOf(plan.made));
5282
5360
  io2.print(
@@ -5856,7 +5934,7 @@ var startUpgradeCheck = (io2, subcommand, named, running) => {
5856
5934
  const read2 = io2.registryFetch;
5857
5935
  if (read2 === void 0 || !UPGRADE_CHECK_SUBCOMMANDS.includes(subcommand)) return NO_CHECK;
5858
5936
  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;
5937
+ const origin = originReadOf(named[DEPLOY_OPTION.origin] ?? io2.env[ORIGIN_VARIABLE]);
5860
5938
  const tag = commandTagOf(origin);
5861
5939
  const place = credentialFileOf(io2);
5862
5940
  const path = (onWindows(io2) ? win324 : posix3).join(place.folder, UPGRADE_CHECK_FILE_NAME);
@@ -5900,7 +5978,7 @@ var startUpgradeCheck = (io2, subcommand, named, running) => {
5900
5978
  };
5901
5979
 
5902
5980
  // src/turnzero_cloud_command/provision.ts
5903
- import { basename as basename3, dirname as dirname5, join as join8 } from "node:path";
5981
+ import { basename as basename3, dirname as dirname5, join as join9 } from "node:path";
5904
5982
  var PROVISION_OPTIONS = Object.freeze([
5905
5983
  {
5906
5984
  name: PROVISION_OPTION.application,
@@ -5957,7 +6035,7 @@ var APPLICATION_FOLDER_MARKS = Object.freeze(["package.json", "manifest.json"]);
5957
6035
  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
6036
  var outsideApplicationFolder = (io2, envFile) => APPLICATION_FOLDER_MARKS.every((mark) => {
5959
6037
  try {
5960
- return io2.stat(join8(dirname5(envFile), mark))?.folder !== false;
6038
+ return io2.stat(join9(dirname5(envFile), mark))?.folder !== false;
5961
6039
  } catch {
5962
6040
  return false;
5963
6041
  }
@@ -6299,7 +6377,7 @@ var PROVISION_ROW = Object.freeze({
6299
6377
  });
6300
6378
 
6301
6379
  // 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";
6380
+ import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute5, join as join10, relative as relative6, resolve as resolve11 } from "node:path";
6303
6381
 
6304
6382
  // src/turnzero_cloud_command/scaffold_template.ts
6305
6383
  var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
@@ -7751,6 +7829,12 @@ var SCAFFOLD_OPTIONS = Object.freeze([
7751
7829
  value: "<origin>",
7752
7830
  required: false,
7753
7831
  description: `The platform's address. Where absent, ${DEFAULT_COMMAND_ORIGIN}.`
7832
+ },
7833
+ {
7834
+ name: SCAFFOLD_OPTION.application,
7835
+ value: "<application id>",
7836
+ required: false,
7837
+ 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
7838
  }
7755
7839
  ]);
7756
7840
  var EMPTY_PACKAGES = '"packages": []';
@@ -7777,6 +7861,7 @@ var NEITHER_LEFT = Object.freeze({ tools: "written", claude: false });
7777
7861
  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
7862
  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
7863
  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}`;
7864
+ 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
7865
  var RESOURCE_REFUSAL = Object.freeze({
7781
7866
  /** A plural outside SCAFFOLD_NAME_FORM. */
7782
7867
  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 +7872,17 @@ var RESOURCE_REFUSAL = Object.freeze({
7787
7872
  /** A plural that does not end in s, with no `--singular`. */
7788
7873
  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
7874
  /** 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}`
7875
+ 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}`,
7876
+ /** A singular, given or derived, of `json` (#5225): the resource's
7877
+ * invalid error, invalid_<singular>, would then be invalid_json, the
7878
+ * error of a body that does not parse, and a caller could no longer
7879
+ * tell the two apart (PLD-L0-106). */
7880
+ 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
7881
  });
7792
7882
  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
7883
  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
7884
  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}`;
7885
+ var JSON_SINGULAR = "json";
7795
7886
  var resourceNamesOf = (call, template) => {
7796
7887
  const plural = call.named[SCAFFOLD_OPTION.resource];
7797
7888
  const singular = call.named[SCAFFOLD_OPTION.singular];
@@ -7802,10 +7893,11 @@ var resourceNamesOf = (call, template) => {
7802
7893
  if (singular !== void 0 && !SCAFFOLD_NAME_FORM.test(singular)) return { refusal: RESOURCE_REFUSAL.singularForm };
7803
7894
  const own = kept2.routes.find((word) => word === plural);
7804
7895
  if (own !== void 0) return { refusal: ownRouteRefusal(own) };
7805
- if (singular !== void 0) return { plural, singular };
7896
+ if (singular !== void 0) return singular === JSON_SINGULAR ? { refusal: RESOURCE_REFUSAL.jsonSingular } : { plural, singular };
7806
7897
  if (!plural.endsWith("s")) return { refusal: RESOURCE_REFUSAL.noClosingS };
7807
7898
  if (plural.length === 1) return { refusal: RESOURCE_REFUSAL.emptySingular };
7808
- return { plural, singular: plural.slice(0, -1) };
7899
+ const derived = plural.slice(0, -1);
7900
+ return derived === JSON_SINGULAR ? { refusal: RESOURCE_REFUSAL.jsonSingular } : { plural, singular: derived };
7809
7901
  };
7810
7902
  var CARRIED_FIELDS = Object.freeze([Object.freeze({ name: "title", kind: "text" })]);
7811
7903
  var FIELD_KINDS_CLAUSE = `the kind ${orListed(SCAFFOLD_FIELD_KINDS)}`;
@@ -7857,10 +7949,12 @@ var scaffoldOptionsOf = (call, cwd) => {
7857
7949
  if (names !== null && "refusal" in names) return names;
7858
7950
  const fields = fieldsOf(call, template);
7859
7951
  if (fields !== null && "refusal" in fields) return fields;
7952
+ const application = call.named[SCAFFOLD_OPTION.application];
7953
+ if (application !== void 0 && !UPLOAD_ID.test(application)) return { refusal: APPLICATION_FORM_REFUSAL };
7860
7954
  const path = call.named[SCAFFOLD_OPTION.path];
7861
7955
  const origin = commandOriginOf(call.named[SCAFFOLD_OPTION.origin] ?? DEFAULT_COMMAND_ORIGIN);
7862
7956
  if ("refusal" in origin) return origin;
7863
- return { template, names, fields, root: resolve11(cwd, path ?? "."), named: path !== void 0, origin: origin.origin };
7957
+ return { template, names, fields, root: resolve11(cwd, path ?? "."), named: path !== void 0, origin: origin.origin, application: application ?? null };
7864
7958
  };
7865
7959
  var capitalised = (name) => `${name.charAt(0).toUpperCase()}${name.slice(1)}`;
7866
7960
  var resourceFormsOf = (names) => [
@@ -8179,9 +8273,36 @@ var manifestWithMinute = (text, minute) => {
8179
8273
  if (!WRITTEN_MINUTE.test(String(minute))) throw new Error("a minute a run writes is a whole number from 0 to 59");
8180
8274
  return `${around[0]}${CRON_HEAD}${minute} ${around[1]}`;
8181
8275
  };
8182
- var composedFilesOf = (files, rows, minute) => files.map(([path, text]) => [path, path === APPLICATION_MANIFEST ? manifestWithMinute(manifestWithPackages(text, rows), minute) : text]);
8276
+ var MANIFEST_VERSION_LINE = ' "manifest_version": 1,\n';
8277
+ var APPLICATION_LINE_OPEN = ' "application": "';
8278
+ var applicationLineOf = (application) => `${APPLICATION_LINE_OPEN}${application}",
8279
+ `;
8280
+ var manifestWithApplication = (text, application) => {
8281
+ if (application === null) return text;
8282
+ const around = text.split(MANIFEST_VERSION_LINE);
8283
+ if (around.length !== 2) throw new Error("the template's manifest does not hold one manifest_version line");
8284
+ return `${around[0]}${MANIFEST_VERSION_LINE}${applicationLineOf(application)}${around[1]}`;
8285
+ };
8286
+ var composedFilesOf = (files, rows, minute, application = null) => files.map(([path, text]) => [path, path === APPLICATION_MANIFEST ? manifestWithApplication(manifestWithMinute(manifestWithPackages(text, rows), minute), application) : text]);
8287
+ var APPLICATION_LINE_REST = 'hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh",';
8288
+ 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]);
8289
+ var withoutApplicationLine = (staged, text) => {
8290
+ const at = text.indexOf(MANIFEST_VERSION_LINE);
8291
+ if (at < 0) return staged;
8292
+ const head = text.slice(0, at + MANIFEST_VERSION_LINE.length);
8293
+ if (staged.length <= head.length || !staged.startsWith(head)) return staged;
8294
+ const rest = staged.slice(head.length);
8295
+ if (APPLICATION_LINE_OPEN.startsWith(rest)) return true;
8296
+ if (!rest.startsWith(APPLICATION_LINE_OPEN)) return staged;
8297
+ const after = rest.slice(APPLICATION_LINE_OPEN.length);
8298
+ const lineEnd = after.indexOf("\n");
8299
+ if (lineEnd < 0) return applicationLineRestPart(after);
8300
+ const line = after.slice(0, lineEnd);
8301
+ if (line.length !== APPLICATION_LINE_REST.length || !applicationLineRestPart(line)) return false;
8302
+ return `${head}${after.slice(lineEnd + 1)}`;
8303
+ };
8183
8304
  var inside2 = (parent, child) => {
8184
- const between = relative5(parent, child);
8305
+ const between = relative6(parent, child);
8185
8306
  return between !== "" && !between.startsWith("..") && !isAbsolute5(between);
8186
8307
  };
8187
8308
  var ownManifestText = (staged, text) => {
@@ -8189,7 +8310,9 @@ var ownManifestText = (staged, text) => {
8189
8310
  if (around.length !== 2) return false;
8190
8311
  const carried = text.split(CARRIED_CRON);
8191
8312
  if (carried.length > 2) return false;
8192
- let read2 = staged;
8313
+ const unbound = withoutApplicationLine(staged, text);
8314
+ if (typeof unbound === "boolean") return unbound;
8315
+ let read2 = unbound;
8193
8316
  if (carried.length === 2) {
8194
8317
  const head = `${carried[0]}${CRON_HEAD}`;
8195
8318
  if (read2.length > head.length) {
@@ -8223,18 +8346,18 @@ var ownStage = (io2, stage, files) => {
8223
8346
  const folders = new Set([...texts.keys()].flatMap((path) => path.split("/").slice(0, -1).map((_, index, segments) => segments.slice(0, index + 1).join("/"))));
8224
8347
  const own = (at, prefix) => (io2.listFolder(at) ?? []).every((name) => {
8225
8348
  const path = prefix === "" ? name : `${prefix}/${name}`;
8226
- const standing = io2.stat(join9(at, name));
8349
+ const standing = io2.stat(join10(at, name));
8227
8350
  if (standing === null) return false;
8228
- if (standing.folder) return folders.has(path) && own(join9(at, name), path);
8351
+ if (standing.folder) return folders.has(path) && own(join10(at, name), path);
8229
8352
  const text = texts.get(path);
8230
- return text !== void 0 && ownFile(io2, join9(at, name), standing.size, path, text);
8353
+ return text !== void 0 && ownFile(io2, join10(at, name), standing.size, path, text);
8231
8354
  });
8232
8355
  return own(stage, "");
8233
8356
  };
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);
8357
+ 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
8358
  var standsAt = (io2, folder, name) => {
8236
8359
  try {
8237
- return io2.stat(join9(folder, name)) !== null || danglingAt(io2, folder, name);
8360
+ return io2.stat(join10(folder, name)) !== null || danglingAt(io2, folder, name);
8238
8361
  } catch {
8239
8362
  return true;
8240
8363
  }
@@ -8269,8 +8392,8 @@ var folderRefusalOf = (io2, options, files, takeLines) => {
8269
8392
  }
8270
8393
  if (standing2 !== null || dirname6(at) === at) break;
8271
8394
  }
8272
- const app = join9(root, APPLICATION_FOLDER);
8273
- const stage = join9(root, STAGE_FOLDER);
8395
+ const app = join10(root, APPLICATION_FOLDER);
8396
+ const stage = join10(root, STAGE_FOLDER);
8274
8397
  const notOwn = STAGE_NOT_OWN;
8275
8398
  const standing = io2.stat(app);
8276
8399
  if (standing === null ? danglingAt(io2, root, APPLICATION_FOLDER) : !standing.folder) return [noFolderSentence(APPLICATION_FOLDER, "the scaffold writes the application")];
@@ -8379,7 +8502,7 @@ var nextStepLines = (origin, windows = LINES_IN_WINDOWS_FORM, left = NEITHER_LEF
8379
8502
  var writeTemplate = (io2, stage, app, files) => {
8380
8503
  io2.remove(stage);
8381
8504
  for (const [path, text] of files) {
8382
- const at = join9(stage, ...path.split("/"));
8505
+ const at = join10(stage, ...path.split("/"));
8383
8506
  io2.makeFolder(dirname6(at));
8384
8507
  io2.writeFile(at, text);
8385
8508
  }
@@ -8392,7 +8515,7 @@ var writeTemplate = (io2, stage, app, files) => {
8392
8515
  };
8393
8516
  var writeRootSentence = (io2, root) => {
8394
8517
  try {
8395
- io2.writeNewFile(join9(root, TOOLS_FILE), `${ROOT_SENTENCE}
8518
+ io2.writeNewFile(join10(root, TOOLS_FILE), `${ROOT_SENTENCE}
8396
8519
  `);
8397
8520
  return "written";
8398
8521
  } catch (error) {
@@ -8449,8 +8572,8 @@ async function scaffold(call, io2) {
8449
8572
  const { takes } = TEMPLATE_FACTS[template];
8450
8573
  const defaulted = call.named[SCAFFOLD_OPTION.template] === void 0;
8451
8574
  const files = writtenFilesOf(template, options.names, options.fields);
8452
- const app = join9(root, APPLICATION_FOLDER);
8453
- const stage = join9(root, STAGE_FOLDER);
8575
+ const app = join10(root, APPLICATION_FOLDER);
8576
+ const stage = join10(root, STAGE_FOLDER);
8454
8577
  const takeLines = takes.map((take) => machineLineOf(libraryTakeLineOf({ origin, entry: take.entry }), { windows: io2.windows ?? LINES_IN_WINDOWS_FORM, npx: ranThroughNpx(io2) }, origin));
8455
8578
  let reached = "checks";
8456
8579
  const stateNow = () => {
@@ -8479,7 +8602,7 @@ async function scaffold(call, io2) {
8479
8602
  held = found;
8480
8603
  } else {
8481
8604
  for (const take of takes) {
8482
- const copy = join9(app, "lib", take.entry);
8605
+ const copy = join10(app, "lib", take.entry);
8483
8606
  const planOf = (library) => writesOver(library, copy) ? { refusal: LIBRARY_OVER_APPLICATION } : { application: app, copy, declared: null, declaredAt: null };
8484
8607
  const read2 = await readTake(io2, { entry: take.entry, root, named: options.named, origin }, agent.userAgent, planOf, checks.length > 0);
8485
8608
  if ("lines" in read2) return refuse(...read2.lines);
@@ -8502,7 +8625,7 @@ async function scaffold(call, io2) {
8502
8625
  reached = "writes";
8503
8626
  if (io2.stat(root) === null) io2.makeFolder(root);
8504
8627
  if (held.fresh) io2.makeFolder(held.library);
8505
- writeTemplate(io2, stage, app, composedFilesOf(files, held.packages, minute));
8628
+ writeTemplate(io2, stage, app, composedFilesOf(files, held.packages, minute, options.application));
8506
8629
  reached = "template";
8507
8630
  const tools = toolsStands ? "stands" : writeRootSentence(io2, root);
8508
8631
  if (defaulted) io2.print(DEFAULT_TEMPLATE_LINE);
@@ -8577,6 +8700,7 @@ var SCAFFOLD_ROW = Object.freeze({
8577
8700
  },
8578
8701
  note: [
8579
8702
  `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.`,
8703
+ `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
8704
  `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
8705
  `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
8706
  `${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 +8883,7 @@ var startErrorOf = (error) => {
8759
8883
  var HELD_SIGNALS = Object.freeze(["SIGINT", "SIGTERM", "SIGHUP"]);
8760
8884
  var endedStatusOf = (signal) => 128 + (constants2.signals[signal] ?? 0);
8761
8885
  var SIGNALS_PASS_ON = process.platform !== "win32";
8886
+ var CHILD_GROUPS = process.platform !== "win32";
8762
8887
  var passedOnSignalOf = (signal, interrupts, inputIsTerminal, passesOn = SIGNALS_PASS_ON) => {
8763
8888
  if (!passesOn) return null;
8764
8889
  if (signal !== "SIGINT") return signal;
@@ -8783,16 +8908,18 @@ var holdSignals = (signals, inputIsTerminal, passesOn, passOn, onInterrupt) => {
8783
8908
  for (const [signal, handler] of held) signals.off(signal, handler);
8784
8909
  };
8785
8910
  };
8786
- var childRunnerOf = (signals, inputIsTerminal, passesOn = SIGNALS_PASS_ON) => (run) => new Promise((resolve12) => {
8911
+ var childRunnerOf = (signals, inputIsTerminal, passesOn = SIGNALS_PASS_ON, groups = CHILD_GROUPS) => (run) => new Promise((resolve12) => {
8787
8912
  const output = run.output;
8788
8913
  let interrupted = false;
8914
+ const group = run.foreground === true && passesOn && groups;
8789
8915
  try {
8790
8916
  const child = spawn(run.command, [...run.args], {
8791
8917
  cwd: run.cwd,
8792
8918
  env: run.env,
8793
8919
  shell: run.shell ?? false,
8794
8920
  stdio: ["ignore", output ? "pipe" : "inherit", output ? "pipe" : "inherit"],
8795
- windowsHide: false
8921
+ windowsHide: false,
8922
+ detached: group
8796
8923
  });
8797
8924
  if (output) {
8798
8925
  child.stdout?.setEncoding("utf8").on("data", (chunk) => output(chunk, "stdout"));
@@ -8803,9 +8930,17 @@ var childRunnerOf = (signals, inputIsTerminal, passesOn = SIGNALS_PASS_ON) => (r
8803
8930
  if (run.foreground) {
8804
8931
  const release = holdSignals(
8805
8932
  signals,
8806
- inputIsTerminal(),
8933
+ // A group of its own hears no terminal's interrupt, so the command hands on the first one too.
8934
+ group ? false : inputIsTerminal(),
8807
8935
  passesOn,
8808
8936
  (signal) => {
8937
+ if (group && child.pid !== void 0) {
8938
+ try {
8939
+ process.kill(-child.pid, signal);
8940
+ return;
8941
+ } catch {
8942
+ }
8943
+ }
8809
8944
  child.kill(signal);
8810
8945
  },
8811
8946
  () => {
@@ -9182,7 +9317,7 @@ async function runGuarded(argv2, io2) {
9182
9317
  if (parsed.kind === "refused") {
9183
9318
  say(carriesTokenCode(argv2) ? `${parsed.sentence} ${TOKEN_CODE_UNREAD}` : parsed.sentence);
9184
9319
  const npx = "ranThroughNpx" in io2 && io2.ranThroughNpx === true;
9185
- const help = npx ? npxFormOf(`${SERVED} --help`, io2.env[ORIGIN_VARIABLE] ?? DEFAULT_COMMAND_ORIGIN) : `${SERVED} --help`;
9320
+ const help = npx ? npxFormOf(`${SERVED} --help`, originReadOf(io2.env[ORIGIN_VARIABLE])) : `${SERVED} --help`;
9186
9321
  say(`${io2.windows ?? ON_WINDOWS ? windowsLineOf(help) : help} prints the usage.`);
9187
9322
  return COMMAND_STATUS.nothing_started;
9188
9323
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turnzero",
3
- "version": "0.18.3",
3
+ "version": "0.19.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",