@vincentt-xr/cli 0.6.0 → 0.8.0

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.
Files changed (38) hide show
  1. package/dist/cli/args.d.ts +8 -1
  2. package/dist/cli/args.js +10 -1
  3. package/dist/cli/index.js +190 -5
  4. package/dist/dx/command.d.ts +91 -1
  5. package/dist/dx/command.js +213 -2
  6. package/dist/dx/enablement.d.ts +51 -0
  7. package/dist/dx/enablement.js +97 -0
  8. package/dist/dx/nudge.d.ts +7 -0
  9. package/dist/dx/nudge.js +54 -0
  10. package/dist/dx/qa.f7.fixtures.d.ts +73 -0
  11. package/dist/dx/qa.f7.fixtures.js +58 -0
  12. package/dist/dx/send.d.ts +70 -0
  13. package/dist/dx/send.js +108 -0
  14. package/dist/dx/skeleton.js +88 -5
  15. package/dist/lib/version.d.ts +20 -1
  16. package/dist/lib/version.js +41 -5
  17. package/dist/library/assetsCommand.d.ts +32 -0
  18. package/dist/library/assetsCommand.js +264 -0
  19. package/dist/library/client.d.ts +42 -0
  20. package/dist/library/client.js +137 -0
  21. package/dist/library/download.d.ts +46 -0
  22. package/dist/library/download.js +98 -0
  23. package/dist/library/parse.d.ts +43 -0
  24. package/dist/library/parse.js +109 -0
  25. package/dist/library/qa.f5-b32.spec.d.ts +1 -0
  26. package/dist/library/qa.f5-b32.spec.js +202 -0
  27. package/dist/library/qa.f5-cli.spec.d.ts +1 -0
  28. package/dist/library/qa.f5-cli.spec.js +433 -0
  29. package/dist/library/qa.f5-contract.spec.d.ts +1 -0
  30. package/dist/library/qa.f5-contract.spec.js +206 -0
  31. package/dist/library/render.d.ts +39 -0
  32. package/dist/library/render.js +305 -0
  33. package/dist/library/wire.d.ts +96 -0
  34. package/dist/library/wire.js +167 -0
  35. package/dist/publish/prompt.js +17 -2
  36. package/dist/shared/config.d.ts +27 -0
  37. package/dist/shared/config.js +64 -9
  38. package/package.json +1 -1
@@ -7,6 +7,16 @@ export interface MachineConfig {
7
7
  pat: string;
8
8
  /** Org slug commands act in when no --org flag or VINCENTT_ORG env is given. */
9
9
  activeOrg: string;
10
+ /**
11
+ * Durable authorization for unattended `dx --send`. Written only by
12
+ * `vincentt dx enable`, which requires a TTY. Cleared by `disable` and by `logout`.
13
+ */
14
+ dxSendEnabled?: boolean;
15
+ /**
16
+ * ISO8601 UTC, when enablement was granted. Its AGE is the mitigation for having
17
+ * no expiry, so an unparseable value fails closed rather than printing.
18
+ */
19
+ dxSendEnabledAt?: string;
10
20
  }
11
21
  export interface ProjectBinding {
12
22
  /** The backend Project id this working tree publishes to. */
@@ -26,9 +36,26 @@ export declare function loadMachineConfig(): Promise<Partial<MachineConfig>>;
26
36
  * every write, even when the file pre-existed with looser perms).
27
37
  */
28
38
  export declare function writeMachineConfig(patch: Partial<MachineConfig>): Promise<string>;
39
+ /**
40
+ * Set or clear unattended-send enablement (`vincentt dx enable` / `disable`).
41
+ *
42
+ * A dedicated function rather than a `writeMachineConfig` patch because clearing
43
+ * has to DELETE the keys. A patch of `{dxSendEnabled: undefined}` happens to work
44
+ * — JSON.stringify drops undefined values — but only by a coincidence of the
45
+ * serializer, and the read of a key that survived would be the difference between
46
+ * off and on.
47
+ */
48
+ export declare function writeDxEnablement(next: {
49
+ at: string;
50
+ } | undefined): Promise<string>;
29
51
  /**
30
52
  * Remove the stored PAT from ~/.vincentt/config.json (used by `vincentt logout`),
31
53
  * preserving apiUrl and any other fields. Returns whether a token was present.
54
+ *
55
+ * Unattended-send enablement goes with the PAT. This function preserves every
56
+ * other field by design, which would leave a disconnected machine still armed to
57
+ * transmit for whoever signs in next — a creator who disconnects a machine has
58
+ * disconnected it.
32
59
  */
33
60
  export declare function clearMachineToken(): Promise<{
34
61
  path: string;
@@ -8,6 +8,7 @@
8
8
  // Env vars (VINCENTT_API_URL / VINCENTT_PAT) override the file for local/dogfood
9
9
  // runs so a creator can point at a dev backend without editing the machine config.
10
10
  // The binding's optional apiUrl does NOT — see decideApiUrl.
11
+ import { randomBytes } from "node:crypto";
11
12
  import { promises as fs } from "node:fs";
12
13
  import os from "node:os";
13
14
  import path from "node:path";
@@ -31,18 +32,44 @@ export async function loadMachineConfig() {
31
32
  return (await readJsonIfExists(MACHINE_CONFIG_PATH)) ?? {};
32
33
  }
33
34
  /**
34
- * Persist the machine config with owner-only perms. `writeFile`'s `mode` only applies when
35
- * it CREATES the file, so a pre-existing config.json with looser perms (e.g. 0644) would keep
36
- * them and leave a bearer credential world-readable. An explicit chmod after the write tightens
37
- * it to 0600 every time, whether the file was just created or already existed.
35
+ * Persist the machine config with owner-only perms, ATOMICALLY: write a sibling
36
+ * temp file at 0600, then rename it over the target. Rename is atomic on the same
37
+ * filesystem, so there is no window in which config.json is truncated.
38
+ *
39
+ * Atomicity is not incidental cleanup. `dx enable` is a third writer to this file
40
+ * and, unlike `login`/`logout`, one an AGENT can trigger — in a session where
41
+ * `login` may also be running, against the file holding the bearer credential. A
42
+ * `writeFile` interrupted mid-write leaves a truncated config with the PAT gone,
43
+ * and it presents as an unexplained logout or as every later command throwing a
44
+ * SyntaxError, neither of which points at the write that caused it.
45
+ *
46
+ * `writeFile`'s `mode` only applies when it CREATES the file, so the temp file is
47
+ * chmod'ed explicitly: a lingering 0644 temp file would be a second, world-readable
48
+ * copy of the PAT. The temp file is removed if the rename fails, so a failed write
49
+ * leaves neither a truncated config nor a stray credential copy.
50
+ *
51
+ * The temp name is unique per call. A fixed `config.json.tmp` is safe for one
52
+ * writer and destructive for two: concurrent `login` and `dx enable` would write
53
+ * the same path, so whichever renamed second would fail ENOENT with its write
54
+ * silently lost — and a failing writer's cleanup would delete the other's
55
+ * in-flight file. Uniqueness also makes the `catch` safe by construction, since
56
+ * a writer can then only ever remove its own temp file.
38
57
  */
39
58
  async function persistMachineConfig(next) {
40
59
  await fs.mkdir(path.dirname(MACHINE_CONFIG_PATH), { recursive: true, mode: 0o700 });
41
- await fs.writeFile(MACHINE_CONFIG_PATH, JSON.stringify(next, null, 2) + "\n", {
42
- encoding: "utf8",
43
- mode: 0o600,
44
- });
45
- await fs.chmod(MACHINE_CONFIG_PATH, 0o600);
60
+ const tmp = `${MACHINE_CONFIG_PATH}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
61
+ try {
62
+ await fs.writeFile(tmp, JSON.stringify(next, null, 2) + "\n", {
63
+ encoding: "utf8",
64
+ mode: 0o600,
65
+ });
66
+ await fs.chmod(tmp, 0o600);
67
+ await fs.rename(tmp, MACHINE_CONFIG_PATH);
68
+ }
69
+ catch (err) {
70
+ await fs.rm(tmp, { force: true }).catch(() => { });
71
+ throw err;
72
+ }
46
73
  return MACHINE_CONFIG_PATH;
47
74
  }
48
75
  /**
@@ -55,14 +82,42 @@ export async function writeMachineConfig(patch) {
55
82
  const next = { ...(await loadMachineConfig()), ...patch };
56
83
  return persistMachineConfig(next);
57
84
  }
85
+ /**
86
+ * Set or clear unattended-send enablement (`vincentt dx enable` / `disable`).
87
+ *
88
+ * A dedicated function rather than a `writeMachineConfig` patch because clearing
89
+ * has to DELETE the keys. A patch of `{dxSendEnabled: undefined}` happens to work
90
+ * — JSON.stringify drops undefined values — but only by a coincidence of the
91
+ * serializer, and the read of a key that survived would be the difference between
92
+ * off and on.
93
+ */
94
+ export async function writeDxEnablement(next) {
95
+ const cfg = await loadMachineConfig();
96
+ if (next === undefined) {
97
+ delete cfg.dxSendEnabled;
98
+ delete cfg.dxSendEnabledAt;
99
+ }
100
+ else {
101
+ cfg.dxSendEnabled = true;
102
+ cfg.dxSendEnabledAt = next.at;
103
+ }
104
+ return persistMachineConfig(cfg);
105
+ }
58
106
  /**
59
107
  * Remove the stored PAT from ~/.vincentt/config.json (used by `vincentt logout`),
60
108
  * preserving apiUrl and any other fields. Returns whether a token was present.
109
+ *
110
+ * Unattended-send enablement goes with the PAT. This function preserves every
111
+ * other field by design, which would leave a disconnected machine still armed to
112
+ * transmit for whoever signs in next — a creator who disconnects a machine has
113
+ * disconnected it.
61
114
  */
62
115
  export async function clearMachineToken() {
63
116
  const current = await loadMachineConfig();
64
117
  const hadToken = Boolean(current.pat);
65
118
  delete current.pat;
119
+ delete current.dxSendEnabled;
120
+ delete current.dxSendEnabledAt;
66
121
  await persistMachineConfig(current);
67
122
  return { path: MACHINE_CONFIG_PATH, hadToken };
68
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincentt-xr/cli",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Vincentt CLI — the vincentt command-line dev loop (login, create, publish, preview, diagnostics, feedback, relay)",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",