@vincentt-xr/cli 0.7.0 → 0.9.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.
- package/dist/cli/args.d.ts +18 -1
- package/dist/cli/args.js +40 -1
- package/dist/cli/index.js +208 -10
- package/dist/dx/command.d.ts +59 -2
- package/dist/dx/command.js +150 -11
- package/dist/dx/enablement.d.ts +51 -0
- package/dist/dx/enablement.js +97 -0
- package/dist/dx/qa.f7.fixtures.d.ts +73 -0
- package/dist/dx/qa.f7.fixtures.js +58 -0
- package/dist/dx/skeleton.js +1 -0
- package/dist/lib/initCommand.d.ts +28 -1
- package/dist/lib/initCommand.js +91 -16
- package/dist/lib/initFrom.d.ts +48 -0
- package/dist/lib/initFrom.js +198 -0
- package/dist/lib/sourceRef.d.ts +46 -0
- package/dist/lib/sourceRef.js +107 -0
- package/dist/lib/sourceRefCopy.d.ts +11 -0
- package/dist/lib/sourceRefCopy.js +80 -0
- package/dist/lib/version.d.ts +20 -1
- package/dist/lib/version.js +41 -5
- package/dist/library/assetsCommand.d.ts +32 -0
- package/dist/library/assetsCommand.js +264 -0
- package/dist/library/client.d.ts +42 -0
- package/dist/library/client.js +137 -0
- package/dist/library/download.d.ts +46 -0
- package/dist/library/download.js +98 -0
- package/dist/library/parse.d.ts +43 -0
- package/dist/library/parse.js +109 -0
- package/dist/library/qa.f5-b32.spec.d.ts +1 -0
- package/dist/library/qa.f5-b32.spec.js +202 -0
- package/dist/library/qa.f5-cli.spec.d.ts +1 -0
- package/dist/library/qa.f5-cli.spec.js +433 -0
- package/dist/library/qa.f5-contract.spec.d.ts +1 -0
- package/dist/library/qa.f5-contract.spec.js +206 -0
- package/dist/library/render.d.ts +39 -0
- package/dist/library/render.js +305 -0
- package/dist/library/wire.d.ts +96 -0
- package/dist/library/wire.js +167 -0
- package/dist/publish/api.d.ts +20 -0
- package/dist/publish/command.d.ts +3 -0
- package/dist/publish/command.js +39 -4
- package/dist/publish/copy.d.ts +29 -0
- package/dist/publish/copy.js +86 -0
- package/dist/publish/prompt.js +17 -2
- package/dist/scaffold/index.d.ts +9 -0
- package/dist/scaffold/index.js +13 -1
- package/dist/shared/config.d.ts +27 -0
- package/dist/shared/config.js +64 -9
- package/package.json +1 -1
package/dist/dx/command.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { findClusters, rankClusters } from "./clusters.js";
|
|
2
|
+
import { enablementLine } from "./enablement.js";
|
|
2
3
|
import { journalEnabled, journalPath, readJournal } from "./journal.js";
|
|
3
4
|
import { buildReport, renderPayload } from "./send.js";
|
|
4
5
|
const DEFAULT_DAYS = 7;
|
|
@@ -7,12 +8,19 @@ export function parseDxOptions(args) {
|
|
|
7
8
|
const raw = i !== -1 ? Number(args[i + 1]) : NaN;
|
|
8
9
|
const n = args.indexOf("--note");
|
|
9
10
|
const note = n !== -1 ? args[n + 1] : undefined;
|
|
11
|
+
// Positional, and read only from the slot right after the verb: `--note enable`
|
|
12
|
+
// is a note, not a subcommand. Deliberately NOT flags — a flag form
|
|
13
|
+
// (`--enable`) would be one more thing an agent can append to a command it is
|
|
14
|
+
// already running, which is the shape this whole gate exists to refuse.
|
|
15
|
+
const sub = args[0] === "dx" ? args[1] : args[0];
|
|
10
16
|
return {
|
|
11
17
|
days: Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_DAYS,
|
|
12
18
|
json: args.includes("--json"),
|
|
13
19
|
send: args.includes("--send"),
|
|
14
20
|
yes: args.includes("--yes"),
|
|
15
21
|
note: note !== undefined && !note.startsWith("-") ? note : undefined,
|
|
22
|
+
enable: sub === "enable",
|
|
23
|
+
disable: sub === "disable",
|
|
16
24
|
};
|
|
17
25
|
}
|
|
18
26
|
function withinDays(entries, days, now) {
|
|
@@ -60,8 +68,20 @@ export function dxCommand(deps, options) {
|
|
|
60
68
|
}
|
|
61
69
|
const entries = withinDays(all, options.days, deps.now());
|
|
62
70
|
const clusters = rankClusters(findClusters(entries));
|
|
71
|
+
const enablement = deps.enablement?.() ?? { enabled: false };
|
|
63
72
|
if (options.json) {
|
|
64
|
-
|
|
73
|
+
// The JSON object carries enablement as FIELDS because --json is the path an
|
|
74
|
+
// AGENT reads, and an agent that can send unattended should be able to see
|
|
75
|
+
// that it can.
|
|
76
|
+
deps.print(JSON.stringify({
|
|
77
|
+
window: `${options.days}d`,
|
|
78
|
+
runs: entries.length,
|
|
79
|
+
clusters,
|
|
80
|
+
dxSendEnabled: enablement.enabled,
|
|
81
|
+
...(enablement.enabled && enablement.at !== undefined
|
|
82
|
+
? { dxSendEnabledAt: enablement.at }
|
|
83
|
+
: {}),
|
|
84
|
+
}, null, 2));
|
|
65
85
|
return 0;
|
|
66
86
|
}
|
|
67
87
|
deps.print(`${entries.length} runs in the last ${options.days} days.`);
|
|
@@ -87,18 +107,102 @@ export function dxCommand(deps, options) {
|
|
|
87
107
|
// and a claim only made on some runs is one a reader cannot rely on.
|
|
88
108
|
deps.print("");
|
|
89
109
|
deps.print(`Journal: ${deps.path()} (local only, never sent)`);
|
|
110
|
+
// Beside it, and ONLY when enabled. Enablement is durable and has no expiry, so
|
|
111
|
+
// this line is the mitigation rather than a status decoration; printing an
|
|
112
|
+
// `off` counterpart on every run is how it would get skimmed past.
|
|
113
|
+
const line = enablementLine(enablement, deps.now());
|
|
114
|
+
if (line !== undefined)
|
|
115
|
+
deps.print(line);
|
|
90
116
|
return 0;
|
|
91
117
|
}
|
|
92
118
|
/** Wire the command to the real journal. */
|
|
93
|
-
export function runDx(args, print) {
|
|
119
|
+
export function runDx(args, print, enablement = () => ({ enabled: false })) {
|
|
94
120
|
return dxCommand({
|
|
95
121
|
entries: readJournal,
|
|
96
122
|
print,
|
|
97
123
|
now: () => Date.now(),
|
|
98
124
|
enabled: () => journalEnabled(),
|
|
99
125
|
path: journalPath,
|
|
126
|
+
enablement,
|
|
100
127
|
}, parseDxOptions(args));
|
|
101
128
|
}
|
|
129
|
+
/** A date a human can read, for the sunset refusal. */
|
|
130
|
+
function sunsetDate(sunset) {
|
|
131
|
+
return new Date(sunset).toISOString().slice(0, 10);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* `vincentt dx enable` — grant this machine durable permission to send DX reports
|
|
135
|
+
* without asking first.
|
|
136
|
+
*
|
|
137
|
+
* The TTY requirement is a SPEED BUMP, NOT A BOUNDARY, and is written down as
|
|
138
|
+
* such so a later reader does not build on a guarantee that is not there. It
|
|
139
|
+
* stops the reflex — an agent that meets a prompt, infers there must be a way
|
|
140
|
+
* past it, and tries the obvious thing. It does not stop a caller that allocates
|
|
141
|
+
* a PTY or writes config.json directly.
|
|
142
|
+
*
|
|
143
|
+
* The refusal path writes NOTHING. Not "writes the same bytes back" — nothing at
|
|
144
|
+
* all: this file holds the PAT, and a refusal that round-trips the config through
|
|
145
|
+
* a write can lose the credential if it dies mid-write.
|
|
146
|
+
*/
|
|
147
|
+
export async function dxEnableCommand(deps, sunset) {
|
|
148
|
+
if (deps.now() >= sunset) {
|
|
149
|
+
deps.print("");
|
|
150
|
+
deps.print(` Unattended DX submission ended on ${sunsetDate(sunset)} and can no longer be enabled.`);
|
|
151
|
+
deps.print(" Reports can still be sent from your own terminal.");
|
|
152
|
+
deps.print("");
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
if (!deps.interactive()) {
|
|
156
|
+
deps.print("");
|
|
157
|
+
deps.print(" Enabling unattended sending needs a terminal to ask in, and this");
|
|
158
|
+
deps.print(" session has none. Nothing was changed.");
|
|
159
|
+
deps.print("");
|
|
160
|
+
deps.print(" Run `vincentt dx enable` yourself, in your own terminal.");
|
|
161
|
+
deps.print("");
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
// What is being granted, INCLUDING the note risk. The note is the part a
|
|
165
|
+
// creator does not picture: enablement is what lets an agent compose prose they
|
|
166
|
+
// have not read and put it on the wire.
|
|
167
|
+
deps.print("");
|
|
168
|
+
deps.print(" Unattended DX submission");
|
|
169
|
+
deps.print("");
|
|
170
|
+
deps.print(" Your agent will be able to send friction reports from this machine without");
|
|
171
|
+
deps.print(" asking you first. A report includes a note, which is sent exactly as written.");
|
|
172
|
+
deps.print(" An agent may compose a note you have not read.");
|
|
173
|
+
deps.print("");
|
|
174
|
+
deps.print(" This stays on until you run `vincentt dx disable`.");
|
|
175
|
+
deps.print("");
|
|
176
|
+
if (!(await deps.confirm("Enable on this machine?"))) {
|
|
177
|
+
deps.print("");
|
|
178
|
+
deps.print(" Not enabled.");
|
|
179
|
+
deps.print("");
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
// Written on every enable, so the age reports from the LATEST grant rather than
|
|
183
|
+
// the first — a re-enable is a fresh decision and dates from when it was made.
|
|
184
|
+
await deps.save({ at: new Date(deps.now()).toISOString() });
|
|
185
|
+
deps.print("");
|
|
186
|
+
deps.print(" Enabled. Reports may now be sent without a prompt.");
|
|
187
|
+
deps.print("");
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* `vincentt dx disable` — the obvious opposite command.
|
|
192
|
+
*
|
|
193
|
+
* Clears BOTH keys. Leaving the timestamp behind would make a later re-enable
|
|
194
|
+
* report its age from the first grant, which is the kind of thing that ships
|
|
195
|
+
* wrong and is never noticed. Needs no terminal and no confirmation: turning a
|
|
196
|
+
* capability OFF is always safe to do, and requiring a TTY here would strand a
|
|
197
|
+
* creator who wanted to revoke from a script.
|
|
198
|
+
*/
|
|
199
|
+
export async function dxDisableCommand(deps) {
|
|
200
|
+
await deps.save(undefined);
|
|
201
|
+
deps.print("");
|
|
202
|
+
deps.print(" Unattended sending is off. Reports need your confirmation again.");
|
|
203
|
+
deps.print("");
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
102
206
|
/**
|
|
103
207
|
* `vincentt dx --send`.
|
|
104
208
|
*
|
|
@@ -120,7 +224,8 @@ export async function dxSendCommand(deps, options) {
|
|
|
120
224
|
deps.print(`No friction detected in the last ${options.days} days — nothing to send.`);
|
|
121
225
|
return 0;
|
|
122
226
|
}
|
|
123
|
-
//
|
|
227
|
+
// SENDING requires either an interactive session or durable enablement — the
|
|
228
|
+
// gate is on --send, not on --yes.
|
|
124
229
|
//
|
|
125
230
|
// The nudge prints into an agent's transcript, and an agent that reads it has
|
|
126
231
|
// every incentive to act: it just failed four times. Its reflex for an
|
|
@@ -129,13 +234,30 @@ export async function dxSendCommand(deps, options) {
|
|
|
129
234
|
// past in a transcript they may not be reading — which is not the opt-in this
|
|
130
235
|
// feature is built around.
|
|
131
236
|
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
237
|
+
// The gate used to sit on --yes, on the premise that the bare path was safe
|
|
238
|
+
// because "the prompt reads EOF under a pipe and fails closed". That premise
|
|
239
|
+
// was false: an empty pipe abandoned the promise rather than refusing, and a
|
|
240
|
+
// pipe carrying a line answered it — `echo y | vincentt dx --send` transmitted
|
|
241
|
+
// with no terminal and no human. --yes was never the boundary; --send is, so
|
|
242
|
+
// the check sits on the act being authorized rather than on a flag that only
|
|
243
|
+
// modifies how it is confirmed.
|
|
244
|
+
//
|
|
245
|
+
// The second arm is the SUPPORTED unattended path: a person, at a terminal,
|
|
246
|
+
// ran `vincentt dx enable` on this machine. That is a prior deliberate act
|
|
247
|
+
// rather than a per-send answer, which is what makes it consent at all.
|
|
248
|
+
const enablement = deps.enablement?.() ?? { enabled: false };
|
|
249
|
+
const interactive = deps.interactive();
|
|
250
|
+
if (!interactive && !enablement.enabled) {
|
|
251
|
+
// This refusal NAMES the enabling command. A refusal is an answer to a
|
|
252
|
+
// command the caller typed, which makes it SOLICITED output — the
|
|
253
|
+
// prohibition is on UNBIDDEN output instructing a caller (the nudge names
|
|
254
|
+
// neither `enable` nor `--send`, and still must not). A refusal naming
|
|
255
|
+
// nothing satisfies the letter of that rule and leaves a human at exit 1
|
|
256
|
+
// with no path forward, for no security gain: guessability was never the
|
|
257
|
+
// threat.
|
|
258
|
+
deps.print("Sending a report needs your confirmation, and this session has no terminal");
|
|
259
|
+
deps.print("to ask in. Run `vincentt dx enable` yourself to allow reports from this");
|
|
260
|
+
deps.print("machine, or run `vincentt dx --send` in your own terminal.");
|
|
139
261
|
return 1;
|
|
140
262
|
}
|
|
141
263
|
const auth = await deps.auth();
|
|
@@ -144,10 +266,27 @@ export async function dxSendCommand(deps, options) {
|
|
|
144
266
|
return 1;
|
|
145
267
|
}
|
|
146
268
|
const report = buildReport(clusters, deps.cliVersion(), options.note);
|
|
269
|
+
// Above the payload, on the unattended path only: name that this is enabled,
|
|
270
|
+
// how old the grant is, and that nothing is going to ask.
|
|
271
|
+
const unattended = !interactive && enablement.enabled;
|
|
272
|
+
if (unattended) {
|
|
273
|
+
const line = enablementLine(enablement, deps.now());
|
|
274
|
+
if (line !== undefined) {
|
|
275
|
+
deps.print("");
|
|
276
|
+
deps.print(`${line}. Not asking.`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// renderPayload runs on EVERY path, including the unattended one. This is
|
|
280
|
+
// DISCLOSURE, NOT CONSENT: on the attended path it is what the creator answers
|
|
281
|
+
// to; unattended it is a forensic artifact that answers "what did we send"
|
|
282
|
+
// after the fact. Worth its one print, and not to be cited as carrying the
|
|
283
|
+
// weight the question used to. Only the ASK is ever skipped, never the SHOW.
|
|
147
284
|
for (const l of renderPayload(report))
|
|
148
285
|
deps.print(l);
|
|
149
286
|
deps.print("");
|
|
150
|
-
|
|
287
|
+
// The enabled machine does not ask. --yes still means only "skip the question"
|
|
288
|
+
// and is inert without one of the two arms above.
|
|
289
|
+
if (!unattended && !options.yes && !(await deps.confirm("Send this report?"))) {
|
|
151
290
|
deps.print("Not sent.");
|
|
152
291
|
return 0;
|
|
153
292
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The product-lifetime bound on an explicitly internal-beta capability. Past this
|
|
3
|
+
* date `enable` refuses AND an existing flag stops being honored, so FORGETTING
|
|
4
|
+
* THIS TURNS THE FEATURE OFF — the safe direction.
|
|
5
|
+
*
|
|
6
|
+
* This is deliberately a constant in shipped code rather than a CI test with a
|
|
7
|
+
* hard-coded date: such a test fires in an uninvolved engineer's lap at a moment
|
|
8
|
+
* with no context, and the cheapest green is bumping the constant. Reaching this
|
|
9
|
+
* date is a decision to make (extend, or remove the feature), not a number to
|
|
10
|
+
* raise.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DX_UNATTENDED_SUNSET: number;
|
|
13
|
+
export interface Enablement {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
/** The raw ISO8601 grant time. Present only when `enabled` is true. */
|
|
16
|
+
at?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Decide enablement from an already-parsed config object. Pure, and STRICT:
|
|
20
|
+
* identity against `true`, never truthiness.
|
|
21
|
+
*
|
|
22
|
+
* Truthiness fails OPEN on `"false"`, `"0"`, `{}`, `[]`, `1`, and `"garbage"` —
|
|
23
|
+
* every one of those is truthy in JavaScript. The worst is `"false"`, which is
|
|
24
|
+
* what a person hand-editing this file writes when they are trying to turn the
|
|
25
|
+
* feature OFF; `{}` is what a refactor grouping the two fields produces. A gate
|
|
26
|
+
* that reads a creator's "off" as "on" is inverted, not merely loose.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readEnablement(cfg: unknown, now: number): Enablement;
|
|
29
|
+
/**
|
|
30
|
+
* Read enablement from the real machine config.
|
|
31
|
+
*
|
|
32
|
+
* Wrapped in its OWN try/catch, independent of the auth read, because
|
|
33
|
+
* `readJsonIfExists` rethrows everything that is not ENOENT: a truncated
|
|
34
|
+
* config.json must DISABLE the relaxation, not throw through it and take an
|
|
35
|
+
* unrelated verb down with it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function loadEnablement(now: number): Promise<Enablement>;
|
|
38
|
+
/** Whole days elapsed, floored, never negative — a clock skew must not print `(-2 days)`. */
|
|
39
|
+
export declare function ageInDays(at: string, now: number): number;
|
|
40
|
+
/** The calendar day of the grant, in UTC. The time of day is noise at this age scale. */
|
|
41
|
+
export declare function grantedOn(at: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* The one visibility line, in the single pinned shape every human surface uses:
|
|
44
|
+
*
|
|
45
|
+
* Unattended send: ENABLED since 2026-04-01 (145 days)
|
|
46
|
+
*
|
|
47
|
+
* Returns undefined when not enabled. Nothing is printed in that case, on
|
|
48
|
+
* purpose: a line that prints unconditionally is a line readers learn to skip,
|
|
49
|
+
* which would cost exactly the attention the enabled line needs.
|
|
50
|
+
*/
|
|
51
|
+
export declare function enablementLine(e: Enablement, now: number): string | undefined;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable authorization for unattended `vincentt dx --send`.
|
|
3
|
+
*
|
|
4
|
+
* `--send` transmits only when the session is interactive OR this machine has
|
|
5
|
+
* been explicitly enabled by a person, at a terminal, via `vincentt dx enable`.
|
|
6
|
+
* The state is two fields in ~/.vincentt/config.json, set by `enable`, cleared by
|
|
7
|
+
* `disable` and by `logout`, and it has NO expiry — it stays on until the creator
|
|
8
|
+
* turns it off.
|
|
9
|
+
*
|
|
10
|
+
* Because there is no expiry, VISIBILITY is the entire mitigation, and the AGE is
|
|
11
|
+
* the load-bearing half of it: `ENABLED` alone reads as configuration, while
|
|
12
|
+
* `(145 days)` reads as drift and is the number that makes someone ask whether
|
|
13
|
+
* they still want it. That is why an unparseable timestamp fails CLOSED rather
|
|
14
|
+
* than printing `since Invalid Date (NaN days)` — a degraded mitigation is worse
|
|
15
|
+
* than a visible absence.
|
|
16
|
+
*/
|
|
17
|
+
import { loadMachineConfig } from "../shared/config.js";
|
|
18
|
+
/**
|
|
19
|
+
* The product-lifetime bound on an explicitly internal-beta capability. Past this
|
|
20
|
+
* date `enable` refuses AND an existing flag stops being honored, so FORGETTING
|
|
21
|
+
* THIS TURNS THE FEATURE OFF — the safe direction.
|
|
22
|
+
*
|
|
23
|
+
* This is deliberately a constant in shipped code rather than a CI test with a
|
|
24
|
+
* hard-coded date: such a test fires in an uninvolved engineer's lap at a moment
|
|
25
|
+
* with no context, and the cheapest green is bumping the constant. Reaching this
|
|
26
|
+
* date is a decision to make (extend, or remove the feature), not a number to
|
|
27
|
+
* raise.
|
|
28
|
+
*/
|
|
29
|
+
export const DX_UNATTENDED_SUNSET = Date.parse("2027-02-24T00:00:00.000Z");
|
|
30
|
+
const DISABLED = { enabled: false };
|
|
31
|
+
/**
|
|
32
|
+
* Decide enablement from an already-parsed config object. Pure, and STRICT:
|
|
33
|
+
* identity against `true`, never truthiness.
|
|
34
|
+
*
|
|
35
|
+
* Truthiness fails OPEN on `"false"`, `"0"`, `{}`, `[]`, `1`, and `"garbage"` —
|
|
36
|
+
* every one of those is truthy in JavaScript. The worst is `"false"`, which is
|
|
37
|
+
* what a person hand-editing this file writes when they are trying to turn the
|
|
38
|
+
* feature OFF; `{}` is what a refactor grouping the two fields produces. A gate
|
|
39
|
+
* that reads a creator's "off" as "on" is inverted, not merely loose.
|
|
40
|
+
*/
|
|
41
|
+
export function readEnablement(cfg, now) {
|
|
42
|
+
if (typeof cfg !== "object" || cfg === null)
|
|
43
|
+
return DISABLED;
|
|
44
|
+
const { dxSendEnabled, dxSendEnabledAt } = cfg;
|
|
45
|
+
if (dxSendEnabled !== true)
|
|
46
|
+
return DISABLED;
|
|
47
|
+
if (typeof dxSendEnabledAt !== "string")
|
|
48
|
+
return DISABLED;
|
|
49
|
+
if (!Number.isFinite(Date.parse(dxSendEnabledAt)))
|
|
50
|
+
return DISABLED;
|
|
51
|
+
if (now >= DX_UNATTENDED_SUNSET)
|
|
52
|
+
return DISABLED;
|
|
53
|
+
return { enabled: true, at: dxSendEnabledAt };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Read enablement from the real machine config.
|
|
57
|
+
*
|
|
58
|
+
* Wrapped in its OWN try/catch, independent of the auth read, because
|
|
59
|
+
* `readJsonIfExists` rethrows everything that is not ENOENT: a truncated
|
|
60
|
+
* config.json must DISABLE the relaxation, not throw through it and take an
|
|
61
|
+
* unrelated verb down with it.
|
|
62
|
+
*/
|
|
63
|
+
export async function loadEnablement(now) {
|
|
64
|
+
try {
|
|
65
|
+
return readEnablement(await loadMachineConfig(), now);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return DISABLED;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
72
|
+
/** Whole days elapsed, floored, never negative — a clock skew must not print `(-2 days)`. */
|
|
73
|
+
export function ageInDays(at, now) {
|
|
74
|
+
const t = Date.parse(at);
|
|
75
|
+
if (!Number.isFinite(t))
|
|
76
|
+
return 0;
|
|
77
|
+
return Math.max(0, Math.floor((now - t) / DAY_MS));
|
|
78
|
+
}
|
|
79
|
+
/** The calendar day of the grant, in UTC. The time of day is noise at this age scale. */
|
|
80
|
+
export function grantedOn(at) {
|
|
81
|
+
return new Date(Date.parse(at)).toISOString().slice(0, 10);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The one visibility line, in the single pinned shape every human surface uses:
|
|
85
|
+
*
|
|
86
|
+
* Unattended send: ENABLED since 2026-04-01 (145 days)
|
|
87
|
+
*
|
|
88
|
+
* Returns undefined when not enabled. Nothing is printed in that case, on
|
|
89
|
+
* purpose: a line that prints unconditionally is a line readers learn to skip,
|
|
90
|
+
* which would cost exactly the attention the enabled line needs.
|
|
91
|
+
*/
|
|
92
|
+
export function enablementLine(e, now) {
|
|
93
|
+
if (!e.enabled || e.at === undefined)
|
|
94
|
+
return undefined;
|
|
95
|
+
const days = ageInDays(e.at, now);
|
|
96
|
+
return `Unattended send: ENABLED since ${grantedOn(e.at)} (${days} ${days === 1 ? "day" : "days"})`;
|
|
97
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA fixtures for the dx-agent-consent delta (f7).
|
|
3
|
+
*
|
|
4
|
+
* Declared in the change's qa.md `## Fixtures` section. All three are
|
|
5
|
+
* machine-config state, which f7 has never had a fixture for — its existing
|
|
6
|
+
* fixtures (F7_JOURNAL_FRICTION, F7_REPORT_EXPIRED, …) are journal-side or
|
|
7
|
+
* server-side.
|
|
8
|
+
*
|
|
9
|
+
* F7_CONFIG_SIGNED_IN is written by driving the REAL writeMachineConfig(), never
|
|
10
|
+
* by hand-writing JSON. A hand-written config asserts against a shape the CLI
|
|
11
|
+
* never produces — the f1 orgs/memberships failure at file scope, where a mock
|
|
12
|
+
* of state the API could actually produce drifted from the API and stayed green.
|
|
13
|
+
*
|
|
14
|
+
* These are QA-owned (qa.* prefix) and deliberately separate from dev's test
|
|
15
|
+
* files in the same package.
|
|
16
|
+
*/
|
|
17
|
+
import type { JournalEntry } from "./journal.js";
|
|
18
|
+
/** The honest baseline config, as the CLI itself writes it. */
|
|
19
|
+
export declare const F7_CONFIG_SIGNED_IN: {
|
|
20
|
+
readonly apiUrl: "https://api.test";
|
|
21
|
+
readonly consoleUrl: "https://console.test";
|
|
22
|
+
readonly pat: "vct_alice";
|
|
23
|
+
readonly activeOrg: "alice";
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The instant every fixture is dated against. 145 days after the grant in
|
|
27
|
+
* F7_CONFIG_ENABLED, and before DX_UNATTENDED_SUNSET.
|
|
28
|
+
*/
|
|
29
|
+
export declare const F7_NOW: number;
|
|
30
|
+
/** 145 days before F7_NOW. The AGE is the point — see qa.md on F7_CONFIG_ENABLED. */
|
|
31
|
+
export declare const F7_ENABLED_AT = "2026-04-01T00:00:00.000Z";
|
|
32
|
+
/**
|
|
33
|
+
* The single pinned human-facing shape. Every human surface must render this
|
|
34
|
+
* byte-for-byte; the age is the load-bearing half, so it is pinned as a literal
|
|
35
|
+
* rather than computed (a computed expectation would follow a broken
|
|
36
|
+
* implementation wherever it went).
|
|
37
|
+
*/
|
|
38
|
+
export declare const F7_PINNED_ENABLEMENT_LINE = "Unattended send: ENABLED since 2026-04-01 (145 days)";
|
|
39
|
+
/** F7_CONFIG_SIGNED_IN + durable enablement, aged. */
|
|
40
|
+
export declare const F7_CONFIG_ENABLED: {
|
|
41
|
+
readonly dxSendEnabled: true;
|
|
42
|
+
readonly dxSendEnabledAt: "2026-04-01T00:00:00.000Z";
|
|
43
|
+
readonly apiUrl: "https://api.test";
|
|
44
|
+
readonly consoleUrl: "https://console.test";
|
|
45
|
+
readonly pat: "vct_alice";
|
|
46
|
+
readonly activeOrg: "alice";
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Every shape the enablement read must treat as DISABLED.
|
|
50
|
+
*
|
|
51
|
+
* Rows B, C, E, F, G are TRUTHY IN JAVASCRIPT — `if (cfg.dxSendEnabled)` passes
|
|
52
|
+
* every one of them. That is the entire reason this fixture exists, and why the
|
|
53
|
+
* read is specified as `=== true`. The worst is B ("false"): what a person
|
|
54
|
+
* hand-editing this file writes when trying to turn the feature OFF.
|
|
55
|
+
*
|
|
56
|
+
* `raw` rows (K, L) are written as bytes rather than as an object, because the
|
|
57
|
+
* property under test is that an UNPARSEABLE config fails closed instead of
|
|
58
|
+
* throwing through into an unrelated verb.
|
|
59
|
+
*/
|
|
60
|
+
export interface MalformedRow {
|
|
61
|
+
id: string;
|
|
62
|
+
why: string;
|
|
63
|
+
/** A config object to serialize, or undefined when `raw` carries the bytes. */
|
|
64
|
+
patch?: Record<string, unknown>;
|
|
65
|
+
/** Literal file bytes, for the rows that are not valid JSON at all. */
|
|
66
|
+
raw?: string;
|
|
67
|
+
}
|
|
68
|
+
export declare const F7_CONFIG_MALFORMED_ENABLEMENT: MalformedRow[];
|
|
69
|
+
/**
|
|
70
|
+
* F7_JOURNAL_FRICTION, as the record declares it: three runs of the same verb in
|
|
71
|
+
* a few minutes, which is a repetition cluster.
|
|
72
|
+
*/
|
|
73
|
+
export declare function f7JournalFriction(now?: number): JournalEntry[];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** The honest baseline config, as the CLI itself writes it. */
|
|
2
|
+
export const F7_CONFIG_SIGNED_IN = {
|
|
3
|
+
apiUrl: "https://api.test",
|
|
4
|
+
consoleUrl: "https://console.test",
|
|
5
|
+
pat: "vct_alice",
|
|
6
|
+
activeOrg: "alice",
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* The instant every fixture is dated against. 145 days after the grant in
|
|
10
|
+
* F7_CONFIG_ENABLED, and before DX_UNATTENDED_SUNSET.
|
|
11
|
+
*/
|
|
12
|
+
export const F7_NOW = Date.parse("2026-08-24T12:00:00.000Z");
|
|
13
|
+
/** 145 days before F7_NOW. The AGE is the point — see qa.md on F7_CONFIG_ENABLED. */
|
|
14
|
+
export const F7_ENABLED_AT = "2026-04-01T00:00:00.000Z";
|
|
15
|
+
/**
|
|
16
|
+
* The single pinned human-facing shape. Every human surface must render this
|
|
17
|
+
* byte-for-byte; the age is the load-bearing half, so it is pinned as a literal
|
|
18
|
+
* rather than computed (a computed expectation would follow a broken
|
|
19
|
+
* implementation wherever it went).
|
|
20
|
+
*/
|
|
21
|
+
export const F7_PINNED_ENABLEMENT_LINE = "Unattended send: ENABLED since 2026-04-01 (145 days)";
|
|
22
|
+
/** F7_CONFIG_SIGNED_IN + durable enablement, aged. */
|
|
23
|
+
export const F7_CONFIG_ENABLED = {
|
|
24
|
+
...F7_CONFIG_SIGNED_IN,
|
|
25
|
+
dxSendEnabled: true,
|
|
26
|
+
dxSendEnabledAt: F7_ENABLED_AT,
|
|
27
|
+
};
|
|
28
|
+
export const F7_CONFIG_MALFORMED_ENABLEMENT = [
|
|
29
|
+
{ id: "A", why: 'truthy STRING "true"', patch: { dxSendEnabled: "true", dxSendEnabledAt: F7_ENABLED_AT } },
|
|
30
|
+
{ id: "B", why: 'truthy in JS, means false to a human', patch: { dxSendEnabled: "false", dxSendEnabledAt: F7_ENABLED_AT } },
|
|
31
|
+
{ id: "C", why: 'truthy in JS ("0")', patch: { dxSendEnabled: "0", dxSendEnabledAt: F7_ENABLED_AT } },
|
|
32
|
+
{ id: "D", why: "truthy, not the boolean (1)", patch: { dxSendEnabled: 1, dxSendEnabledAt: F7_ENABLED_AT } },
|
|
33
|
+
{ id: "E", why: "truthy ({} — what a refactor grouping the fields produces)", patch: { dxSendEnabled: {}, dxSendEnabledAt: F7_ENABLED_AT } },
|
|
34
|
+
{ id: "F", why: "truthy ([])", patch: { dxSendEnabled: [], dxSendEnabledAt: F7_ENABLED_AT } },
|
|
35
|
+
{ id: "G", why: 'truthy ("garbage")', patch: { dxSendEnabled: "garbage", dxSendEnabledAt: F7_ENABLED_AT } },
|
|
36
|
+
{ id: "H", why: "key absent entirely", patch: {} },
|
|
37
|
+
{ id: "I", why: "enabled true, dxSendEnabledAt absent", patch: { dxSendEnabled: true } },
|
|
38
|
+
{ id: "J", why: 'enabled true, dxSendEnabledAt "not-a-date"', patch: { dxSendEnabled: true, dxSendEnabledAt: "not-a-date" } },
|
|
39
|
+
{ id: "K", why: "file truncated mid-JSON", raw: '{"pat":"vct_alice","dxSendEn' },
|
|
40
|
+
{ id: "L", why: "file empty (0 bytes)", raw: "" },
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* F7_JOURNAL_FRICTION, as the record declares it: three runs of the same verb in
|
|
44
|
+
* a few minutes, which is a repetition cluster.
|
|
45
|
+
*/
|
|
46
|
+
export function f7JournalFriction(now = F7_NOW) {
|
|
47
|
+
const entry = (minutesAgo) => ({
|
|
48
|
+
ts: new Date(now - minutesAgo * 60_000).toISOString(),
|
|
49
|
+
v: "0.7.0",
|
|
50
|
+
verb: "preview",
|
|
51
|
+
skel: "preview",
|
|
52
|
+
exit: 0,
|
|
53
|
+
ms: 800,
|
|
54
|
+
err: null,
|
|
55
|
+
wt: "abc123",
|
|
56
|
+
});
|
|
57
|
+
return [entry(3), entry(2), entry(1)];
|
|
58
|
+
}
|
package/dist/dx/skeleton.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import type { RegisterDeps } from "./initFrom.js";
|
|
2
|
+
import { type SourceRef } from "./sourceRef.js";
|
|
1
3
|
import type { Ask, Confirm } from "../publish/prompt.js";
|
|
2
4
|
export interface InitDeps {
|
|
3
5
|
print: (line: string) => void;
|
|
4
6
|
cwd: string;
|
|
5
|
-
scaffold?: (targetDir: string,
|
|
7
|
+
scaffold?: (targetDir: string, opts: ScaffoldCall) => Promise<void>;
|
|
6
8
|
install?: (targetDir: string) => Promise<string>;
|
|
7
9
|
isEmpty?: (targetDir: string) => Promise<boolean>;
|
|
8
10
|
/** Free-text prompt. Absent → not a TTY, so nothing may be asked. */
|
|
@@ -11,6 +13,23 @@ export interface InitDeps {
|
|
|
11
13
|
confirm?: Confirm;
|
|
12
14
|
/** Non-empty check for the resolved target; injected in tests. */
|
|
13
15
|
isNonEmpty?: (targetDir: string) => Promise<boolean>;
|
|
16
|
+
/**
|
|
17
|
+
* The registration half of `--from`. Injected whole so the combined path is
|
|
18
|
+
* drivable without a real API, a real browser, or a real HOME.
|
|
19
|
+
*/
|
|
20
|
+
register?: (deps: Pick<RegisterDeps, "print" | "targetDir" | "dirLabel" | "ask">, opts: {
|
|
21
|
+
name?: string;
|
|
22
|
+
slug?: string;
|
|
23
|
+
}) => Promise<number>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* What `InitDeps.scaffold` is called with. An OBJECT rather than a positional
|
|
27
|
+
* ref: `--from` needs to name a repo too, and a positional-to-object change
|
|
28
|
+
* compiles silently in a fake that ignores its second argument.
|
|
29
|
+
*/
|
|
30
|
+
export interface ScaffoldCall {
|
|
31
|
+
repo?: string;
|
|
32
|
+
ref?: string;
|
|
14
33
|
}
|
|
15
34
|
/** The folder name offered when `init` is run bare and the creator just hits Enter. */
|
|
16
35
|
export declare const DEFAULT_APP_NAME = "my-ar-app";
|
|
@@ -27,5 +46,13 @@ export interface InitOptions {
|
|
|
27
46
|
skipInstall?: boolean;
|
|
28
47
|
/** Template version to scaffold from; defaults to the latest release. */
|
|
29
48
|
template?: string;
|
|
49
|
+
/**
|
|
50
|
+
* A gallery source, already parsed. The combined path (f6): clone this repo at
|
|
51
|
+
* this tag, then register the project. NEVER installs dependencies (S-1).
|
|
52
|
+
*/
|
|
53
|
+
from?: SourceRef;
|
|
54
|
+
/** `--name` / `--slug`, accepted on `init` only when `--from` is present. */
|
|
55
|
+
name?: string;
|
|
56
|
+
slug?: string;
|
|
30
57
|
}
|
|
31
58
|
export declare function initCommand(deps: InitDeps, opts?: InitOptions): Promise<number>;
|