openlimiter 0.4.0 → 1.2.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/README.md +21 -0
- package/dist/bin.js +41 -10
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts +18 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +214 -7
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/ingest.d.ts +10 -2
- package/dist/ingest.d.ts.map +1 -1
- package/dist/ingest.js +59 -5
- package/dist/ingest.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +33 -16
- package/dist/render.js.map +1 -1
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +155 -155
- package/dist/smoke.d.ts +107 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +327 -0
- package/dist/smoke.js.map +1 -0
- package/dist/statusline-wrapper.d.ts +32 -0
- package/dist/statusline-wrapper.d.ts.map +1 -0
- package/dist/statusline-wrapper.js +121 -0
- package/dist/statusline-wrapper.js.map +1 -0
- package/dist/statusline.d.ts.map +1 -1
- package/dist/statusline.js +3 -0
- package/dist/statusline.js.map +1 -1
- package/package.json +8 -6
package/dist/smoke.d.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type ConnectionStatus, type ProviderCode } from "@openlimiter/core";
|
|
2
|
+
/** The environment variable that turns this on. Nothing else does. */
|
|
3
|
+
export declare const LIVE_ENVIRONMENT_FLAG = "OPENLIMITER_LIVE";
|
|
4
|
+
/** What the command prints when the flag is absent. */
|
|
5
|
+
export declare const SKIPPED_NOTE: string;
|
|
6
|
+
/**
|
|
7
|
+
* Where a person points this at a payload they captured themselves.
|
|
8
|
+
*
|
|
9
|
+
* `OPENLIMITER_SMOKE_PAYLOAD_CLAUDE=/path/to/statusline.json` hands that file
|
|
10
|
+
* to the Claude reader. This is how a live response reaches the real parser
|
|
11
|
+
* without this module ever making the call: the person makes it, with their own
|
|
12
|
+
* credential, in their own shell, and hands over the result.
|
|
13
|
+
*/
|
|
14
|
+
export declare const PAYLOAD_ENVIRONMENT_PREFIX = "OPENLIMITER_SMOKE_PAYLOAD_";
|
|
15
|
+
/** Where the evidence goes, when a person wants it somewhere else. */
|
|
16
|
+
export declare const EVIDENCE_DIRECTORY_ENVIRONMENT = "OPENLIMITER_EVIDENCE_DIR";
|
|
17
|
+
/** Largest payload file this harness will read. */
|
|
18
|
+
export declare const MAX_PAYLOAD_BYTES = 262144;
|
|
19
|
+
/** The first thing that must not be here, or null when the text is clean. */
|
|
20
|
+
export declare function containsSecret(text: string): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* The directory the evidence is written into.
|
|
23
|
+
*
|
|
24
|
+
* The mission workspace, `launch-2026-09-01/providers`, which sits beside the
|
|
25
|
+
* checkout rather than inside it. It is found by walking up from the repository
|
|
26
|
+
* root, because a worktree may be checked out at `launch-2026-09-01/wt/<lane>`
|
|
27
|
+
* or beside the workspace, and a fixed number of parent steps is right in one
|
|
28
|
+
* of those layouts and silently wrong in the other. The environment variable
|
|
29
|
+
* wins over both, for a person who wants the file somewhere else entirely.
|
|
30
|
+
*/
|
|
31
|
+
export declare function evidenceDirectory(repositoryRoot: string, environment?: Readonly<Record<string, string | undefined>>): string;
|
|
32
|
+
/** The evidence file for one provider on one day. */
|
|
33
|
+
export declare function evidenceFileName(provider: ProviderCode, day: string): string;
|
|
34
|
+
/** The calendar day an instant falls on, in UTC, so a file name never drifts. */
|
|
35
|
+
export declare function evidenceDay(now: string): string;
|
|
36
|
+
export interface SmokeMeter {
|
|
37
|
+
readonly meter: string;
|
|
38
|
+
readonly value: number;
|
|
39
|
+
readonly unit: string;
|
|
40
|
+
readonly resetAt: string | null;
|
|
41
|
+
readonly state: "fresh" | "stale" | "unknown";
|
|
42
|
+
readonly source: string;
|
|
43
|
+
readonly precision: string;
|
|
44
|
+
}
|
|
45
|
+
export interface SmokeProvider {
|
|
46
|
+
readonly provider: ProviderCode;
|
|
47
|
+
readonly connector: string;
|
|
48
|
+
readonly maturity: "stable" | "beta";
|
|
49
|
+
readonly detected: boolean;
|
|
50
|
+
/** Where the reading came from: a payload file, the local cache, or nothing. */
|
|
51
|
+
readonly via: "payload_file" | "snapshot_cache" | "none";
|
|
52
|
+
readonly connection: ConnectionStatus | null;
|
|
53
|
+
readonly meters: readonly SmokeMeter[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* One printed line per provider per meter, in the shape a human scans.
|
|
57
|
+
*
|
|
58
|
+
* Percent and reset, because those are the two things a person is checking, and
|
|
59
|
+
* nothing else, because every other field on a snapshot is either a constant or
|
|
60
|
+
* a thing that could carry an identity.
|
|
61
|
+
*/
|
|
62
|
+
export declare function smokeLine(entry: SmokeProvider, meter: SmokeMeter): string;
|
|
63
|
+
/** The line for a provider that produced nothing, which is still a result. */
|
|
64
|
+
export declare function silentLine(entry: SmokeProvider): string;
|
|
65
|
+
/**
|
|
66
|
+
* The evidence document, with nothing in it that could name a person.
|
|
67
|
+
*
|
|
68
|
+
* Built by naming every field that goes IN rather than by deleting fields that
|
|
69
|
+
* must stay out. An allow list cannot be defeated by a provider adding a field,
|
|
70
|
+
* and a deny list can, which is the whole argument.
|
|
71
|
+
*/
|
|
72
|
+
export declare function sanitizeEvidence(entry: SmokeProvider, now: string): Record<string, unknown>;
|
|
73
|
+
export interface SmokeDependencies {
|
|
74
|
+
readonly environment: Readonly<Record<string, string | undefined>>;
|
|
75
|
+
readonly stateDirectory: string;
|
|
76
|
+
readonly now: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Read every provider this machine has, through the real readers.
|
|
80
|
+
*
|
|
81
|
+
* A payload file wins when one was supplied, because that is a reading taken
|
|
82
|
+
* today. Otherwise the local snapshot cache stands in: those rows were written
|
|
83
|
+
* by the real collectors on this machine, so they still answer the question
|
|
84
|
+
* this harness exists for, and the document says which of the two it was.
|
|
85
|
+
*/
|
|
86
|
+
export declare function collectSmoke(dependencies: SmokeDependencies): Promise<readonly SmokeProvider[]>;
|
|
87
|
+
export interface SmokeOutcome {
|
|
88
|
+
readonly lines: readonly string[];
|
|
89
|
+
readonly written: readonly string[];
|
|
90
|
+
readonly refused: readonly string[];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Run the harness and write one evidence file per provider that reported.
|
|
94
|
+
*
|
|
95
|
+
* A provider with no reading writes no file: an evidence file saying nothing
|
|
96
|
+
* happened is a file somebody will one day cite as proof that something did.
|
|
97
|
+
*/
|
|
98
|
+
export declare function runSmoke(dependencies: SmokeDependencies, directory: string): Promise<SmokeOutcome>;
|
|
99
|
+
/**
|
|
100
|
+
* The command, including the half of it that does nothing.
|
|
101
|
+
*
|
|
102
|
+
* Returns the exit code rather than calling `process.exit`, so the whole thing
|
|
103
|
+
* stays testable and the skipped path can be asserted to be exactly what it
|
|
104
|
+
* claims: no reads, no writes, exit zero.
|
|
105
|
+
*/
|
|
106
|
+
export declare function smokeMain(repositoryRoot: string, environment?: Readonly<Record<string, string | undefined>>, write?: (line: string) => void): Promise<number>;
|
|
107
|
+
//# sourceMappingURL=smoke.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.d.ts","sourceRoot":"","sources":["../src/smoke.ts"],"names":[],"mappings":"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"}
|
package/dist/smoke.js
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The live smoke harness: read this machine's real providers, once, on purpose.
|
|
3
|
+
*
|
|
4
|
+
* Every other test in this repository runs against a frozen fixture, which is
|
|
5
|
+
* the right default and has one blind spot it can never close: a fixture proves
|
|
6
|
+
* the parser reads the shape it was given, and says nothing about whether that
|
|
7
|
+
* shape is still what a provider sends today. This is the one path that answers
|
|
8
|
+
* the other question, and the reason it is a separate command rather than a
|
|
9
|
+
* test is that it needs a real account and produces evidence a human reads.
|
|
10
|
+
*
|
|
11
|
+
* Three rules hold it in place.
|
|
12
|
+
*
|
|
13
|
+
* OFF BY DEFAULT. Nothing here runs unless `OPENLIMITER_LIVE=1` is set in the
|
|
14
|
+
* environment. Without it the command prints one line saying so and exits zero,
|
|
15
|
+
* because a smoke harness that could run by accident in CI is a smoke harness
|
|
16
|
+
* that will one day read somebody's account in CI.
|
|
17
|
+
*
|
|
18
|
+
* NO NETWORK, EVER, FROM HERE. This module opens no sockets. It reads what the
|
|
19
|
+
* real readers already wrote to this machine, and payload FILES a person points
|
|
20
|
+
* it at, and it runs both through the real connector contracts. Fetching would
|
|
21
|
+
* make this a second collection implementation, and a second implementation is
|
|
22
|
+
* exactly what the browser mirror and the desktop reader exist to avoid.
|
|
23
|
+
*
|
|
24
|
+
* NOTHING IDENTIFYING LEAVES. The evidence file carries provider codes, meter
|
|
25
|
+
* codes, numbers, instants and connection states. It carries no token, no
|
|
26
|
+
* email, no key, no account alias and no path. `containsSecret` below is the
|
|
27
|
+
* last gate: it reads the finished document back as text and refuses to write
|
|
28
|
+
* it if anything that looks like a credential or a machine path survived.
|
|
29
|
+
*/
|
|
30
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
31
|
+
import path from "node:path";
|
|
32
|
+
import { connectors } from "@openlimiter/connectors";
|
|
33
|
+
import { connectorMaturity, freshness, normalizeMeters, readSnapshotCache, resolveStateDirectory } from "@openlimiter/core";
|
|
34
|
+
/** The environment variable that turns this on. Nothing else does. */
|
|
35
|
+
export const LIVE_ENVIRONMENT_FLAG = "OPENLIMITER_LIVE";
|
|
36
|
+
/** What the command prints when the flag is absent. */
|
|
37
|
+
export const SKIPPED_NOTE = "openlimiter smoke: skipped. Set " + LIVE_ENVIRONMENT_FLAG +
|
|
38
|
+
"=1 to read this machine's real providers.";
|
|
39
|
+
/**
|
|
40
|
+
* Where a person points this at a payload they captured themselves.
|
|
41
|
+
*
|
|
42
|
+
* `OPENLIMITER_SMOKE_PAYLOAD_CLAUDE=/path/to/statusline.json` hands that file
|
|
43
|
+
* to the Claude reader. This is how a live response reaches the real parser
|
|
44
|
+
* without this module ever making the call: the person makes it, with their own
|
|
45
|
+
* credential, in their own shell, and hands over the result.
|
|
46
|
+
*/
|
|
47
|
+
export const PAYLOAD_ENVIRONMENT_PREFIX = "OPENLIMITER_SMOKE_PAYLOAD_";
|
|
48
|
+
/** Where the evidence goes, when a person wants it somewhere else. */
|
|
49
|
+
export const EVIDENCE_DIRECTORY_ENVIRONMENT = "OPENLIMITER_EVIDENCE_DIR";
|
|
50
|
+
/** Largest payload file this harness will read. */
|
|
51
|
+
export const MAX_PAYLOAD_BYTES = 262_144;
|
|
52
|
+
/**
|
|
53
|
+
* Anything that must never appear in an evidence file.
|
|
54
|
+
*
|
|
55
|
+
* Written as a refusal rather than a redaction on purpose. A redactor that
|
|
56
|
+
* misses a pattern writes the secret; a gate that misses a pattern writes the
|
|
57
|
+
* secret too, but a gate that FIRES is loud, and the failure mode of "we wrote
|
|
58
|
+
* nothing and said why" is the only acceptable one here.
|
|
59
|
+
*/
|
|
60
|
+
const SECRET_PATTERNS = [
|
|
61
|
+
["an email address", /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/u],
|
|
62
|
+
["a JSON web token", /\beyJ[A-Za-z0-9_-]{6,}/u],
|
|
63
|
+
["a bearer token", /\bBearer\s+\S/iu],
|
|
64
|
+
["an api key", /\b(?:sk|pk|xai|sess)-[A-Za-z0-9]{8,}/u],
|
|
65
|
+
/* Case blind: Windows paths are, so the lower case spelling names the
|
|
66
|
+
same directory and leaks the same name. A case sensitive pattern here
|
|
67
|
+
caught the tidy spelling and let the one a shell prints straight by. */
|
|
68
|
+
["a windows user path", /[A-Za-z]:\\+Users\\+[^\\\\"]+/iu],
|
|
69
|
+
["a unix home path", /\/(?:home|Users)\/[^/"]+/u],
|
|
70
|
+
["an authorization header", /"?authorization"?\s*[:=]/iu],
|
|
71
|
+
["a cookie", /\b(?:set-)?cookie\b/iu]
|
|
72
|
+
];
|
|
73
|
+
/** The first thing that must not be here, or null when the text is clean. */
|
|
74
|
+
export function containsSecret(text) {
|
|
75
|
+
for (const [reason, pattern] of SECRET_PATTERNS) {
|
|
76
|
+
if (pattern.test(text))
|
|
77
|
+
return reason;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The directory the evidence is written into.
|
|
83
|
+
*
|
|
84
|
+
* The mission workspace, `launch-2026-09-01/providers`, which sits beside the
|
|
85
|
+
* checkout rather than inside it. It is found by walking up from the repository
|
|
86
|
+
* root, because a worktree may be checked out at `launch-2026-09-01/wt/<lane>`
|
|
87
|
+
* or beside the workspace, and a fixed number of parent steps is right in one
|
|
88
|
+
* of those layouts and silently wrong in the other. The environment variable
|
|
89
|
+
* wins over both, for a person who wants the file somewhere else entirely.
|
|
90
|
+
*/
|
|
91
|
+
export function evidenceDirectory(repositoryRoot, environment = process.env) {
|
|
92
|
+
const override = environment[EVIDENCE_DIRECTORY_ENVIRONMENT];
|
|
93
|
+
if (override !== undefined && override !== "")
|
|
94
|
+
return path.resolve(override);
|
|
95
|
+
let directory = path.resolve(repositoryRoot);
|
|
96
|
+
for (let step = 0; step < 6; step += 1) {
|
|
97
|
+
const parent = path.dirname(directory);
|
|
98
|
+
if (parent === directory)
|
|
99
|
+
break;
|
|
100
|
+
if (path.basename(parent) === "launch-2026-09-01") {
|
|
101
|
+
return path.join(parent, "providers");
|
|
102
|
+
}
|
|
103
|
+
directory = parent;
|
|
104
|
+
}
|
|
105
|
+
return path.resolve(repositoryRoot, "..", "..", "launch-2026-09-01", "providers");
|
|
106
|
+
}
|
|
107
|
+
/** The evidence file for one provider on one day. */
|
|
108
|
+
export function evidenceFileName(provider, day) {
|
|
109
|
+
return provider.toLowerCase().replace(/_/gu, "-") + "-" + day + ".json";
|
|
110
|
+
}
|
|
111
|
+
/** The calendar day an instant falls on, in UTC, so a file name never drifts. */
|
|
112
|
+
export function evidenceDay(now) {
|
|
113
|
+
const parsed = Date.parse(now);
|
|
114
|
+
return Number.isFinite(parsed)
|
|
115
|
+
? new Date(parsed).toISOString().slice(0, 10)
|
|
116
|
+
: "unknown";
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* One printed line per provider per meter, in the shape a human scans.
|
|
120
|
+
*
|
|
121
|
+
* Percent and reset, because those are the two things a person is checking, and
|
|
122
|
+
* nothing else, because every other field on a snapshot is either a constant or
|
|
123
|
+
* a thing that could carry an identity.
|
|
124
|
+
*/
|
|
125
|
+
export function smokeLine(entry, meter) {
|
|
126
|
+
return [
|
|
127
|
+
entry.provider.padEnd(12),
|
|
128
|
+
meter.meter.padEnd(22),
|
|
129
|
+
(meter.value.toFixed(1) + "%").padStart(7),
|
|
130
|
+
meter.state.padEnd(8),
|
|
131
|
+
meter.resetAt ?? "no reset stated"
|
|
132
|
+
].join(" ");
|
|
133
|
+
}
|
|
134
|
+
/** The line for a provider that produced nothing, which is still a result. */
|
|
135
|
+
export function silentLine(entry) {
|
|
136
|
+
const detail = entry.connection === null
|
|
137
|
+
? entry.detected ? "detected, no reading" : "not configured"
|
|
138
|
+
: entry.connection.state + ", " + entry.connection.instruction;
|
|
139
|
+
return entry.provider.padEnd(12) + " " + detail;
|
|
140
|
+
}
|
|
141
|
+
function snapshotMeter(snapshot, now) {
|
|
142
|
+
return {
|
|
143
|
+
meter: snapshot.meter,
|
|
144
|
+
value: snapshot.value,
|
|
145
|
+
unit: snapshot.unit,
|
|
146
|
+
resetAt: snapshot.resetAt,
|
|
147
|
+
state: freshness(snapshot.observedAt, snapshot.expiresAt, now),
|
|
148
|
+
source: snapshot.source,
|
|
149
|
+
precision: snapshot.precision
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The evidence document, with nothing in it that could name a person.
|
|
154
|
+
*
|
|
155
|
+
* Built by naming every field that goes IN rather than by deleting fields that
|
|
156
|
+
* must stay out. An allow list cannot be defeated by a provider adding a field,
|
|
157
|
+
* and a deny list can, which is the whole argument.
|
|
158
|
+
*/
|
|
159
|
+
export function sanitizeEvidence(entry, now) {
|
|
160
|
+
return {
|
|
161
|
+
schema: "openlimiter.smoke.evidence.v1",
|
|
162
|
+
provider: entry.provider,
|
|
163
|
+
connector: entry.connector,
|
|
164
|
+
maturity: entry.maturity,
|
|
165
|
+
observedAt: now,
|
|
166
|
+
detected: entry.detected,
|
|
167
|
+
readVia: entry.via,
|
|
168
|
+
connection: entry.connection === null
|
|
169
|
+
? null
|
|
170
|
+
: {
|
|
171
|
+
state: entry.connection.state,
|
|
172
|
+
reason: entry.connection.reason,
|
|
173
|
+
instruction: entry.connection.instruction
|
|
174
|
+
},
|
|
175
|
+
meterCount: entry.meters.length,
|
|
176
|
+
meters: entry.meters.map((meter) => ({
|
|
177
|
+
meter: meter.meter,
|
|
178
|
+
value: meter.value,
|
|
179
|
+
unit: meter.unit,
|
|
180
|
+
resetAt: meter.resetAt,
|
|
181
|
+
state: meter.state,
|
|
182
|
+
source: meter.source,
|
|
183
|
+
precision: meter.precision
|
|
184
|
+
}))
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/** A payload file a person pointed this harness at, or null. */
|
|
188
|
+
async function readPayloadFile(connectorId, encoding, environment) {
|
|
189
|
+
const key = PAYLOAD_ENVIRONMENT_PREFIX + connectorId.toUpperCase();
|
|
190
|
+
const file = environment[key];
|
|
191
|
+
if (file === undefined || file === "")
|
|
192
|
+
return null;
|
|
193
|
+
let text;
|
|
194
|
+
try {
|
|
195
|
+
text = await readFile(path.resolve(file), "utf8");
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return { ok: false, reason: "could not be read" };
|
|
199
|
+
}
|
|
200
|
+
if (text.length > MAX_PAYLOAD_BYTES)
|
|
201
|
+
return { ok: false, reason: "is too large" };
|
|
202
|
+
if (encoding === "text")
|
|
203
|
+
return { ok: true, payload: text };
|
|
204
|
+
try {
|
|
205
|
+
return { ok: true, payload: JSON.parse(text) };
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return { ok: false, reason: "is not valid JSON" };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Read every provider this machine has, through the real readers.
|
|
213
|
+
*
|
|
214
|
+
* A payload file wins when one was supplied, because that is a reading taken
|
|
215
|
+
* today. Otherwise the local snapshot cache stands in: those rows were written
|
|
216
|
+
* by the real collectors on this machine, so they still answer the question
|
|
217
|
+
* this harness exists for, and the document says which of the two it was.
|
|
218
|
+
*/
|
|
219
|
+
export async function collectSmoke(dependencies) {
|
|
220
|
+
const cached = await readSnapshotCache(dependencies.stateDirectory);
|
|
221
|
+
const cachedRows = cached.ok ? cached.snapshots : [];
|
|
222
|
+
const entries = [];
|
|
223
|
+
for (const connector of connectors) {
|
|
224
|
+
const provider = connector.id.toUpperCase();
|
|
225
|
+
const detected = connector.detect(dependencies.environment);
|
|
226
|
+
const supplied = await readPayloadFile(connector.id, connector.encoding, dependencies.environment);
|
|
227
|
+
let connection = null;
|
|
228
|
+
let meters = [];
|
|
229
|
+
let via = "none";
|
|
230
|
+
if (supplied !== null && supplied.ok) {
|
|
231
|
+
const result = await connector.read({
|
|
232
|
+
payload: supplied.payload,
|
|
233
|
+
now: dependencies.now,
|
|
234
|
+
environment: dependencies.environment
|
|
235
|
+
});
|
|
236
|
+
connection = result.connection ?? null;
|
|
237
|
+
if (result.ok) {
|
|
238
|
+
via = "payload_file";
|
|
239
|
+
meters = normalizeMeters(result.meters)
|
|
240
|
+
.map((snapshot) => snapshotMeter(snapshot, dependencies.now));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (meters.length === 0) {
|
|
244
|
+
const rows = cachedRows.filter((row) => row.provider === provider);
|
|
245
|
+
if (rows.length > 0) {
|
|
246
|
+
via = "snapshot_cache";
|
|
247
|
+
meters = rows.map((row) => snapshotMeter(row, dependencies.now));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
entries.push({
|
|
251
|
+
provider,
|
|
252
|
+
connector: connector.id,
|
|
253
|
+
maturity: connectorMaturity(connector),
|
|
254
|
+
detected,
|
|
255
|
+
via,
|
|
256
|
+
connection,
|
|
257
|
+
meters
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return entries;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Run the harness and write one evidence file per provider that reported.
|
|
264
|
+
*
|
|
265
|
+
* A provider with no reading writes no file: an evidence file saying nothing
|
|
266
|
+
* happened is a file somebody will one day cite as proof that something did.
|
|
267
|
+
*/
|
|
268
|
+
export async function runSmoke(dependencies, directory) {
|
|
269
|
+
const entries = await collectSmoke(dependencies);
|
|
270
|
+
const lines = [];
|
|
271
|
+
const written = [];
|
|
272
|
+
const refused = [];
|
|
273
|
+
const day = evidenceDay(dependencies.now);
|
|
274
|
+
let created = false;
|
|
275
|
+
for (const entry of entries) {
|
|
276
|
+
if (entry.meters.length === 0) {
|
|
277
|
+
lines.push(silentLine(entry));
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
for (const meter of entry.meters)
|
|
281
|
+
lines.push(smokeLine(entry, meter));
|
|
282
|
+
const document = JSON.stringify(sanitizeEvidence(entry, dependencies.now), null, 2);
|
|
283
|
+
const secret = containsSecret(document);
|
|
284
|
+
if (secret !== null) {
|
|
285
|
+
refused.push(entry.provider + ": evidence withheld, it contained " + secret);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!created) {
|
|
289
|
+
await mkdir(directory, { recursive: true });
|
|
290
|
+
created = true;
|
|
291
|
+
}
|
|
292
|
+
const file = path.join(directory, evidenceFileName(entry.provider, day));
|
|
293
|
+
await writeFile(file, document + "\n", "utf8");
|
|
294
|
+
written.push(evidenceFileName(entry.provider, day));
|
|
295
|
+
}
|
|
296
|
+
return { lines, written, refused };
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* The command, including the half of it that does nothing.
|
|
300
|
+
*
|
|
301
|
+
* Returns the exit code rather than calling `process.exit`, so the whole thing
|
|
302
|
+
* stays testable and the skipped path can be asserted to be exactly what it
|
|
303
|
+
* claims: no reads, no writes, exit zero.
|
|
304
|
+
*/
|
|
305
|
+
export async function smokeMain(repositoryRoot, environment = process.env, write = (line) => process.stdout.write(line + "\n")) {
|
|
306
|
+
if (environment[LIVE_ENVIRONMENT_FLAG] !== "1") {
|
|
307
|
+
write(SKIPPED_NOTE);
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
const directory = evidenceDirectory(repositoryRoot, environment);
|
|
311
|
+
const outcome = await runSmoke({
|
|
312
|
+
environment,
|
|
313
|
+
stateDirectory: resolveStateDirectory({ environment }),
|
|
314
|
+
now: new Date().toISOString()
|
|
315
|
+
}, directory);
|
|
316
|
+
write("PROVIDER METER PERCENT STATE RESETS AT");
|
|
317
|
+
for (const line of outcome.lines)
|
|
318
|
+
write(line);
|
|
319
|
+
for (const refusal of outcome.refused)
|
|
320
|
+
write("WITHHELD " + refusal);
|
|
321
|
+
write(outcome.written.length === 0
|
|
322
|
+
? "No evidence written: no provider reported a reading."
|
|
323
|
+
: "Wrote " + String(outcome.written.length) + " evidence file(s): " +
|
|
324
|
+
outcome.written.join(", "));
|
|
325
|
+
return 0;
|
|
326
|
+
}
|
|
327
|
+
//# sourceMappingURL=smoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"smoke.js","sourceRoot":"","sources":["../src/smoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EACL,iBAAiB,EACjB,SAAS,EACT,eAAe,EACf,iBAAiB,EACjB,qBAAqB,EAItB,MAAM,mBAAmB,CAAC;AAE3B,sEAAsE;AACtE,MAAM,CAAC,MAAM,qBAAqB,GAAG,kBAAkB,CAAC;AAExD,uDAAuD;AACvD,MAAM,CAAC,MAAM,YAAY,GACvB,kCAAkC,GAAG,qBAAqB;IAC1D,2CAA2C,CAAC;AAE9C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,4BAA4B,CAAC;AAEvE,sEAAsE;AACtE,MAAM,CAAC,MAAM,8BAA8B,GAAG,0BAA0B,CAAC;AAEzE,mDAAmD;AACnD,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAEzC;;;;;;;GAOG;AACH,MAAM,eAAe,GAA2C;IAC9D,CAAC,kBAAkB,EAAE,iDAAiD,CAAC;IACvE,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;IAC/C,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;IACrC,CAAC,YAAY,EAAE,uCAAuC,CAAC;IACvD;;8EAE0E;IAC1E,CAAC,qBAAqB,EAAE,iCAAiC,CAAC;IAC1D,CAAC,kBAAkB,EAAE,2BAA2B,CAAC;IACjD,CAAC,yBAAyB,EAAE,4BAA4B,CAAC;IACzD,CAAC,UAAU,EAAE,uBAAuB,CAAC;CACtC,CAAC;AAEF,6EAA6E;AAC7E,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,eAAe,EAAE,CAAC;QAChD,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,MAAM,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAC/B,cAAsB,EACtB,cAA4D,OAAO,CAAC,GAAG;IAEvE,MAAM,QAAQ,GAAG,WAAW,CAAC,8BAA8B,CAAC,CAAC;IAC7D,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7E,IAAI,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7C,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM;QAChC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,mBAAmB,EAAE,CAAC;YAClD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QACxC,CAAC;QACD,SAAS,GAAG,MAAM,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,EAAE,mBAAmB,EAAE,WAAW,CAAC,CAAC;AACpF,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,gBAAgB,CAAC,QAAsB,EAAE,GAAW;IAClE,OAAO,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,OAAO,CAAC;AAC1E,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC5B,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QAC7C,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC;AAuBD;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,KAAoB,EAAE,KAAiB;IAC/D,OAAO;QACL,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1C,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QACrB,KAAK,CAAC,OAAO,IAAI,iBAAiB;KACnC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,KAAoB;IAC7C,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,KAAK,IAAI;QACtC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,gBAAgB;QAC5D,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC;IACjE,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,GAAG,GAAG,MAAM,CAAC;AAClD,CAAC;AAED,SAAS,aAAa,CAAC,QAAkB,EAAE,GAAW;IACpD,OAAO;QACL,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,OAAO,EAAE,QAAQ,CAAC,OAAO;QACzB,KAAK,EAAE,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC;QAC9D,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,SAAS,EAAE,QAAQ,CAAC,SAAS;KAC9B,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAC9B,KAAoB,EACpB,GAAW;IAEX,OAAO;QACL,MAAM,EAAE,+BAA+B;QACvC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU,EAAE,GAAG;QACf,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,GAAG;QAClB,UAAU,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI;YACnC,CAAC,CAAC,IAAI;YACN,CAAC,CAAC;gBACE,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,KAAK;gBAC7B,MAAM,EAAE,KAAK,CAAC,UAAU,CAAC,MAAM;gBAC/B,WAAW,EAAE,KAAK,CAAC,UAAU,CAAC,WAAW;aAC1C;QACL,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC/B,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACnC,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,SAAS,EAAE,KAAK,CAAC,SAAS;SAC3B,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC;AAED,gEAAgE;AAChE,KAAK,UAAU,eAAe,CAC5B,WAAmB,EACnB,QAAyB,EACzB,WAAyD;IAEzD,MAAM,GAAG,GAAG,0BAA0B,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;IACnE,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACnD,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IACpD,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,iBAAiB;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IAClF,IAAI,QAAQ,KAAK,MAAM;QAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC5D,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,EAAE,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IACpD,CAAC;AACH,CAAC;AAQD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,YAA+B;IAE/B,MAAM,MAAM,GAAG,MAAM,iBAAiB,CAAC,YAAY,CAAC,cAAc,CAAC,CAAC;IACpE,MAAM,UAAU,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;IACrD,MAAM,OAAO,GAAoB,EAAE,CAAC;IACpC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,QAAQ,GAAG,SAAS,CAAC,EAAE,CAAC,WAAW,EAAkB,CAAC;QAC5D,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAC5D,MAAM,QAAQ,GAAG,MAAM,eAAe,CACpC,SAAS,CAAC,EAAE,EACZ,SAAS,CAAC,QAAQ,EAClB,YAAY,CAAC,WAAW,CACzB,CAAC;QACF,IAAI,UAAU,GAA4B,IAAI,CAAC;QAC/C,IAAI,MAAM,GAAiB,EAAE,CAAC;QAC9B,IAAI,GAAG,GAAyB,MAAM,CAAC;QACvC,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC;gBAClC,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,GAAG,EAAE,YAAY,CAAC,GAAG;gBACrB,WAAW,EAAE,YAAY,CAAC,WAAW;aACtC,CAAC,CAAC;YACH,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,IAAI,CAAC;YACvC,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;gBACd,GAAG,GAAG,cAAc,CAAC;gBACrB,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,MAAM,CAAC;qBACpC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;YAClE,CAAC;QACH,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC;YACnE,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACpB,GAAG,GAAG,gBAAgB,CAAC;gBACvB,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;QACD,OAAO,CAAC,IAAI,CAAC;YACX,QAAQ;YACR,SAAS,EAAE,SAAS,CAAC,EAAE;YACvB,QAAQ,EAAE,iBAAiB,CAAC,SAAS,CAAC;YACtC,QAAQ;YACR,GAAG;YACH,UAAU;YACV,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAQD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,YAA+B,EAC/B,SAAiB;IAEjB,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,CAAC;IACjD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,WAAW,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;IAC1C,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;YAC9B,SAAS;QACX,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACtE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACpF,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,oCAAoC,GAAG,MAAM,CAAC,CAAC;YAC7E,SAAS;QACX,CAAC;QACD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5C,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;QACzE,MAAM,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,cAAsB,EACtB,cAA4D,OAAO,CAAC,GAAG,EACvE,QAAgC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;IAE3E,IAAI,WAAW,CAAC,qBAAqB,CAAC,KAAK,GAAG,EAAE,CAAC;QAC/C,KAAK,CAAC,YAAY,CAAC,CAAC;QACpB,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,SAAS,GAAG,iBAAiB,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;IACjE,MAAM,OAAO,GAAG,MAAM,QAAQ,CAC5B;QACE,WAAW;QACX,cAAc,EAAE,qBAAqB,CAAC,EAAE,WAAW,EAAE,CAAC;QACtD,GAAG,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KAC9B,EACD,SAAS,CACV,CAAC;IACF,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACxE,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9C,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,OAAO;QAAE,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC,CAAC;IACpE,KAAK,CACH,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;QAC1B,CAAC,CAAC,sDAAsD;QACxD,CAAC,CAAC,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,qBAAqB;YACjE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAC/B,CAAC;IACF,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
+
/** A foreign status line may not hold Claude's prompt open indefinitely. */
|
|
3
|
+
export declare const STATUSLINE_WRAPPER_TIMEOUT_MILLISECONDS = 2000;
|
|
4
|
+
/** Quota capture is best effort and never gets a longer budget than the prompt. */
|
|
5
|
+
export declare const STATUSLINE_WRAPPER_INGEST_TIMEOUT_MILLISECONDS = 1000;
|
|
6
|
+
export interface StatuslineWrapperResult {
|
|
7
|
+
exitCode: number;
|
|
8
|
+
stdout: Buffer;
|
|
9
|
+
stderr: Buffer;
|
|
10
|
+
timedOut: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface StatuslineWrapperOptions {
|
|
13
|
+
ingest: (payload: Buffer) => Promise<unknown>;
|
|
14
|
+
timeoutMilliseconds?: number;
|
|
15
|
+
ingestTimeoutMilliseconds?: number;
|
|
16
|
+
spawnCommand?: StatuslineWrapperSpawn;
|
|
17
|
+
}
|
|
18
|
+
export type StatuslineWrapperSpawn = (command: string) => ChildProcessWithoutNullStreams;
|
|
19
|
+
/**
|
|
20
|
+
* The settings writer uses this alphabet because every character is inert in
|
|
21
|
+
* both POSIX shells and cmd.exe. The decoded text is still passed to the shell
|
|
22
|
+
* as one untouched command string, so this transport does not reinterpret it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function encodeWrappedStatuslineCommand(command: string): string;
|
|
25
|
+
/** Decode only canonical, unpadded base64url carrying valid UTF-8. */
|
|
26
|
+
export declare function decodeWrappedStatuslineCommand(encoded: string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Capture Claude's payload and preserve the foreign status line as the primary
|
|
29
|
+
* operation. OpenLimiter ingestion runs beside it, is bounded, and is silent.
|
|
30
|
+
*/
|
|
31
|
+
export declare function runStatuslineWrapper(payload: Buffer, originalCommand: string, options: StatuslineWrapperOptions): Promise<StatuslineWrapperResult>;
|
|
32
|
+
//# sourceMappingURL=statusline-wrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statusline-wrapper.d.ts","sourceRoot":"","sources":["../src/statusline-wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,8BAA8B,EACpC,MAAM,oBAAoB,CAAC;AAE5B,4EAA4E;AAC5E,eAAO,MAAM,uCAAuC,OAAQ,CAAC;AAE7D,mFAAmF;AACnF,eAAO,MAAM,8CAA8C,OAAQ,CAAC;AAEpE,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,YAAY,CAAC,EAAE,sBAAsB,CAAC;CACvC;AAED,MAAM,MAAM,sBAAsB,GAAG,CACnC,OAAO,EAAE,MAAM,KACZ,8BAA8B,CAAC;AAEpC;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED,sEAAsE;AACtE,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAW7E;AA6ED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,EACvB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CA0BlC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
/** A foreign status line may not hold Claude's prompt open indefinitely. */
|
|
3
|
+
export const STATUSLINE_WRAPPER_TIMEOUT_MILLISECONDS = 2_000;
|
|
4
|
+
/** Quota capture is best effort and never gets a longer budget than the prompt. */
|
|
5
|
+
export const STATUSLINE_WRAPPER_INGEST_TIMEOUT_MILLISECONDS = 1_000;
|
|
6
|
+
/**
|
|
7
|
+
* The settings writer uses this alphabet because every character is inert in
|
|
8
|
+
* both POSIX shells and cmd.exe. The decoded text is still passed to the shell
|
|
9
|
+
* as one untouched command string, so this transport does not reinterpret it.
|
|
10
|
+
*/
|
|
11
|
+
export function encodeWrappedStatuslineCommand(command) {
|
|
12
|
+
return Buffer.from(command, "utf8").toString("base64url");
|
|
13
|
+
}
|
|
14
|
+
/** Decode only canonical, unpadded base64url carrying valid UTF-8. */
|
|
15
|
+
export function decodeWrappedStatuslineCommand(encoded) {
|
|
16
|
+
if (encoded === "" || !/^[A-Za-z0-9_-]+$/.test(encoded))
|
|
17
|
+
return null;
|
|
18
|
+
try {
|
|
19
|
+
const bytes = Buffer.from(encoded, "base64url");
|
|
20
|
+
if (bytes.toString("base64url") !== encoded)
|
|
21
|
+
return null;
|
|
22
|
+
const command = bytes.toString("utf8");
|
|
23
|
+
if (command === "" || !Buffer.from(command, "utf8").equals(bytes))
|
|
24
|
+
return null;
|
|
25
|
+
return command;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function quietDeadline(promise, milliseconds) {
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
let settled = false;
|
|
34
|
+
const finish = () => {
|
|
35
|
+
if (settled)
|
|
36
|
+
return;
|
|
37
|
+
settled = true;
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
resolve();
|
|
40
|
+
};
|
|
41
|
+
const timer = setTimeout(finish, milliseconds);
|
|
42
|
+
void promise.then(finish, finish);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function runOriginalCommand(command, payload, timeoutMilliseconds, spawnCommand) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const stdout = [];
|
|
48
|
+
const stderr = [];
|
|
49
|
+
let settled = false;
|
|
50
|
+
let child;
|
|
51
|
+
let timer;
|
|
52
|
+
const finish = (exitCode, timedOut) => {
|
|
53
|
+
if (settled)
|
|
54
|
+
return;
|
|
55
|
+
settled = true;
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
resolve({
|
|
58
|
+
exitCode,
|
|
59
|
+
stdout: Buffer.concat(stdout),
|
|
60
|
+
stderr: Buffer.concat(stderr),
|
|
61
|
+
timedOut
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
try {
|
|
65
|
+
child = spawnCommand(command);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
resolve({
|
|
69
|
+
exitCode: 0,
|
|
70
|
+
stdout: Buffer.alloc(0),
|
|
71
|
+
stderr: Buffer.alloc(0),
|
|
72
|
+
timedOut: false
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
child.stdout.on("data", (chunk) => stdout.push(Buffer.from(chunk)));
|
|
77
|
+
child.stderr.on("data", (chunk) => stderr.push(Buffer.from(chunk)));
|
|
78
|
+
child.stdin.on("error", () => {
|
|
79
|
+
/* An original command that closes standard input early is still allowed. */
|
|
80
|
+
});
|
|
81
|
+
child.on("error", () => finish(0, false));
|
|
82
|
+
child.on("close", (code) => finish(code ?? 0, false));
|
|
83
|
+
timer = setTimeout(() => {
|
|
84
|
+
/* Stop waiting before attempting cleanup. Destroying the inherited pipes
|
|
85
|
+
prevents a grandchild from keeping this wrapper alive after its shell
|
|
86
|
+
has been terminated. */
|
|
87
|
+
child.stdin.destroy();
|
|
88
|
+
child.stdout.destroy();
|
|
89
|
+
child.stderr.destroy();
|
|
90
|
+
child.kill();
|
|
91
|
+
child.unref();
|
|
92
|
+
finish(0, true);
|
|
93
|
+
}, timeoutMilliseconds);
|
|
94
|
+
child.stdin.end(payload);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Capture Claude's payload and preserve the foreign status line as the primary
|
|
99
|
+
* operation. OpenLimiter ingestion runs beside it, is bounded, and is silent.
|
|
100
|
+
*/
|
|
101
|
+
export async function runStatuslineWrapper(payload, originalCommand, options) {
|
|
102
|
+
const ingestion = Promise.resolve()
|
|
103
|
+
.then(() => options.ingest(payload))
|
|
104
|
+
.catch(() => undefined);
|
|
105
|
+
const original = runOriginalCommand(originalCommand, payload, options.timeoutMilliseconds ?? STATUSLINE_WRAPPER_TIMEOUT_MILLISECONDS,
|
|
106
|
+
// A statusline command is a user configured shell command string (pipes, quoting,
|
|
107
|
+
// env expansion), not an argv array: shell:true is required to honour it unchanged,
|
|
108
|
+
// exactly as encodeWrappedStatuslineCommand's contract above documents. The string
|
|
109
|
+
// is never built from untrusted network input; it is the user's own local setting.
|
|
110
|
+
// nosemgrep: javascript.lang.security.audit.spawn-shell-true.spawn-shell-true, javascript.lang.security.detect-child-process.detect-child-process
|
|
111
|
+
options.spawnCommand ?? ((command) => spawn(command, {
|
|
112
|
+
shell: true,
|
|
113
|
+
windowsHide: true,
|
|
114
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
115
|
+
})));
|
|
116
|
+
const result = await original;
|
|
117
|
+
await quietDeadline(ingestion, options.ingestTimeoutMilliseconds ??
|
|
118
|
+
STATUSLINE_WRAPPER_INGEST_TIMEOUT_MILLISECONDS);
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=statusline-wrapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"statusline-wrapper.js","sourceRoot":"","sources":["../src/statusline-wrapper.ts"],"names":[],"mappings":"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"}
|
package/dist/statusline.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"statusline.d.ts","sourceRoot":"","sources":["../src/statusline.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"statusline.d.ts","sourceRoot":"","sources":["../src/statusline.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,QAAQ,EACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA8CrE,eAAO,MAAM,sBAAsB,EAAE,SAAS,YAAY,EAGzD,CAAC;AAIF;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,SAAS,YAAY,EAAE,CAazB;AAID,MAAM,MAAM,UAAU,GAClB,SAAS,GACT,OAAO,GACP,QAAQ,GACR,SAAS,GACT,SAAS,GACT,OAAO,CAAC;AAyCZ;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,UAAU,CAazD;AAID,MAAM,WAAW,cAAc;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,6EAA6E;AAC7E,eAAO,MAAM,QAAQ,OAAO,CAAC;AA4C7B;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,QAAQ,EAAE,EAC9B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,YAAY,EAAE,EAC9B,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAClC,KAAK,EAAE,OAAO,EACd,IAAI,CAAC,EAAE,OAAO,GACb,SAAS,cAAc,EAAE,CAwB3B;AAID;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAQrD;AA8ED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,QAAQ,EAAE,CAAC;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,gBAAgB,CAAC;IACzB,KAAK,EAAE,OAAO,CAAC;IACf;;;;;;OAMG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,mEAAmE;AACnE,eAAO,MAAM,kBAAkB,wBAAwB,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,qBAAqB,GAAG,MAAM,CA2B3E;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,eAAe,EACxB,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,EACzD,SAAS,EAAE,OAAO,GACjB,OAAO,CAKT"}
|
package/dist/statusline.js
CHANGED
|
@@ -35,7 +35,10 @@ const PROVIDER_CLASS = {
|
|
|
35
35
|
CLAUDE: "subscription",
|
|
36
36
|
CODEX: "subscription",
|
|
37
37
|
ANTIGRAVITY: "subscription",
|
|
38
|
+
GEMINI_CLI: "subscription",
|
|
38
39
|
OPENCODE: "subscription",
|
|
40
|
+
GROK: "subscription",
|
|
41
|
+
KIMI: "subscription",
|
|
39
42
|
MANUAL: "subscription",
|
|
40
43
|
OPENROUTER: "api"
|
|
41
44
|
};
|