@ttctl/core 0.0.0 → 0.1.0-rc.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 +49 -9
- package/dist/__generated__/gateway.d.ts +4546 -0
- package/dist/__generated__/gateway.d.ts.map +1 -0
- package/dist/__generated__/gateway.js +9 -0
- package/dist/__generated__/gateway.js.map +1 -0
- package/dist/__generated__/talent-profile-zod-schemas.d.ts +1187 -0
- package/dist/__generated__/talent-profile-zod-schemas.d.ts.map +1 -0
- package/dist/__generated__/talent-profile-zod-schemas.js +1136 -0
- package/dist/__generated__/talent-profile-zod-schemas.js.map +1 -0
- package/dist/__generated__/talent-profile.d.ts +1397 -0
- package/dist/__generated__/talent-profile.d.ts.map +1 -0
- package/dist/__generated__/talent-profile.js +9 -0
- package/dist/__generated__/talent-profile.js.map +1 -0
- package/dist/__generated__/zod-schemas.d.ts +2895 -0
- package/dist/__generated__/zod-schemas.d.ts.map +1 -0
- package/dist/__generated__/zod-schemas.js +3121 -0
- package/dist/__generated__/zod-schemas.js.map +1 -0
- package/dist/__tests__/fixtures/profile/builders.d.ts +74 -0
- package/dist/__tests__/fixtures/profile/builders.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/builders.js +196 -0
- package/dist/__tests__/fixtures/profile/builders.js.map +1 -0
- package/dist/__tests__/fixtures/profile/data.d.ts +39 -0
- package/dist/__tests__/fixtures/profile/data.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/data.js +230 -0
- package/dist/__tests__/fixtures/profile/data.js.map +1 -0
- package/dist/__tests__/fixtures/profile/index.d.ts +9 -0
- package/dist/__tests__/fixtures/profile/index.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/index.js +10 -0
- package/dist/__tests__/fixtures/profile/index.js.map +1 -0
- package/dist/__tests__/fixtures/profile/types.d.ts +53 -0
- package/dist/__tests__/fixtures/profile/types.d.ts.map +1 -0
- package/dist/__tests__/fixtures/profile/types.js +4 -0
- package/dist/__tests__/fixtures/profile/types.js.map +1 -0
- package/dist/auth/errors.d.ts +82 -0
- package/dist/auth/errors.d.ts.map +1 -0
- package/dist/auth/errors.js +68 -0
- package/dist/auth/errors.js.map +1 -0
- package/dist/auth.d.ts +192 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +294 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.d.ts +212 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +349 -0
- package/dist/config.js.map +1 -0
- package/dist/configLock.d.ts +50 -0
- package/dist/configLock.d.ts.map +1 -0
- package/dist/configLock.js +88 -0
- package/dist/configLock.js.map +1 -0
- package/dist/configWriter.d.ts +97 -0
- package/dist/configWriter.d.ts.map +1 -0
- package/dist/configWriter.js +687 -0
- package/dist/configWriter.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/kill-switch.d.ts +161 -0
- package/dist/kill-switch.d.ts.map +1 -0
- package/dist/kill-switch.js +235 -0
- package/dist/kill-switch.js.map +1 -0
- package/dist/lib/date.d.ts +58 -0
- package/dist/lib/date.d.ts.map +1 -0
- package/dist/lib/date.js +104 -0
- package/dist/lib/date.js.map +1 -0
- package/dist/lib/diagnostic-log.d.ts +159 -0
- package/dist/lib/diagnostic-log.d.ts.map +1 -0
- package/dist/lib/diagnostic-log.js +186 -0
- package/dist/lib/diagnostic-log.js.map +1 -0
- package/dist/lib/package-version.d.ts +19 -0
- package/dist/lib/package-version.d.ts.map +1 -0
- package/dist/lib/package-version.js +38 -0
- package/dist/lib/package-version.js.map +1 -0
- package/dist/lib/redact.d.ts +153 -0
- package/dist/lib/redact.d.ts.map +1 -0
- package/dist/lib/redact.js +207 -0
- package/dist/lib/redact.js.map +1 -0
- package/dist/lib/text.d.ts +14 -0
- package/dist/lib/text.d.ts.map +1 -0
- package/dist/lib/text.js +21 -0
- package/dist/lib/text.js.map +1 -0
- package/dist/lib/wire-shape.d.ts +131 -0
- package/dist/lib/wire-shape.d.ts.map +1 -0
- package/dist/lib/wire-shape.js +376 -0
- package/dist/lib/wire-shape.js.map +1 -0
- package/dist/onepassword.d.ts +29 -0
- package/dist/onepassword.d.ts.map +1 -0
- package/dist/onepassword.js +112 -0
- package/dist/onepassword.js.map +1 -0
- package/dist/services/_shared/transport.d.ts +148 -0
- package/dist/services/_shared/transport.d.ts.map +1 -0
- package/dist/services/_shared/transport.js +102 -0
- package/dist/services/_shared/transport.js.map +1 -0
- package/dist/services/applications/index.d.ts +210 -0
- package/dist/services/applications/index.d.ts.map +1 -0
- package/dist/services/applications/index.js +240 -0
- package/dist/services/applications/index.js.map +1 -0
- package/dist/services/availability/index.d.ts +254 -0
- package/dist/services/availability/index.d.ts.map +1 -0
- package/dist/services/availability/index.js +310 -0
- package/dist/services/availability/index.js.map +1 -0
- package/dist/services/contracts/index.d.ts +132 -0
- package/dist/services/contracts/index.d.ts.map +1 -0
- package/dist/services/contracts/index.js +211 -0
- package/dist/services/contracts/index.js.map +1 -0
- package/dist/services/engagements/index.d.ts +504 -0
- package/dist/services/engagements/index.d.ts.map +1 -0
- package/dist/services/engagements/index.js +613 -0
- package/dist/services/engagements/index.js.map +1 -0
- package/dist/services/jobs/index.d.ts +490 -0
- package/dist/services/jobs/index.d.ts.map +1 -0
- package/dist/services/jobs/index.js +753 -0
- package/dist/services/jobs/index.js.map +1 -0
- package/dist/services/payments/index.d.ts +415 -0
- package/dist/services/payments/index.d.ts.map +1 -0
- package/dist/services/payments/index.js +636 -0
- package/dist/services/payments/index.js.map +1 -0
- package/dist/services/profile/__tests__/fixtures.d.ts +214 -0
- package/dist/services/profile/__tests__/fixtures.d.ts.map +1 -0
- package/dist/services/profile/__tests__/fixtures.js +176 -0
- package/dist/services/profile/__tests__/fixtures.js.map +1 -0
- package/dist/services/profile/basic/index.d.ts +390 -0
- package/dist/services/profile/basic/index.d.ts.map +1 -0
- package/dist/services/profile/basic/index.js +1007 -0
- package/dist/services/profile/basic/index.js.map +1 -0
- package/dist/services/profile/certifications/index.d.ts +74 -0
- package/dist/services/profile/certifications/index.d.ts.map +1 -0
- package/dist/services/profile/certifications/index.js +169 -0
- package/dist/services/profile/certifications/index.js.map +1 -0
- package/dist/services/profile/education/index.d.ts +73 -0
- package/dist/services/profile/education/index.d.ts.map +1 -0
- package/dist/services/profile/education/index.js +168 -0
- package/dist/services/profile/education/index.js.map +1 -0
- package/dist/services/profile/employment/index.d.ts +111 -0
- package/dist/services/profile/employment/index.d.ts.map +1 -0
- package/dist/services/profile/employment/index.js +202 -0
- package/dist/services/profile/employment/index.js.map +1 -0
- package/dist/services/profile/external/index.d.ts +219 -0
- package/dist/services/profile/external/index.d.ts.map +1 -0
- package/dist/services/profile/external/index.js +560 -0
- package/dist/services/profile/external/index.js.map +1 -0
- package/dist/services/profile/index.d.ts +24 -0
- package/dist/services/profile/index.d.ts.map +1 -0
- package/dist/services/profile/index.js +26 -0
- package/dist/services/profile/index.js.map +1 -0
- package/dist/services/profile/industries/index.d.ts +130 -0
- package/dist/services/profile/industries/index.d.ts.map +1 -0
- package/dist/services/profile/industries/index.js +292 -0
- package/dist/services/profile/industries/index.js.map +1 -0
- package/dist/services/profile/portfolio/index.d.ts +352 -0
- package/dist/services/profile/portfolio/index.d.ts.map +1 -0
- package/dist/services/profile/portfolio/index.js +833 -0
- package/dist/services/profile/portfolio/index.js.map +1 -0
- package/dist/services/profile/resume/index.d.ts +60 -0
- package/dist/services/profile/resume/index.d.ts.map +1 -0
- package/dist/services/profile/resume/index.js +212 -0
- package/dist/services/profile/resume/index.js.map +1 -0
- package/dist/services/profile/reviews/index.d.ts +137 -0
- package/dist/services/profile/reviews/index.d.ts.map +1 -0
- package/dist/services/profile/reviews/index.js +431 -0
- package/dist/services/profile/reviews/index.js.map +1 -0
- package/dist/services/profile/shared.d.ts +127 -0
- package/dist/services/profile/shared.d.ts.map +1 -0
- package/dist/services/profile/shared.js +155 -0
- package/dist/services/profile/shared.js.map +1 -0
- package/dist/services/profile/skills/index.d.ts +212 -0
- package/dist/services/profile/skills/index.d.ts.map +1 -0
- package/dist/services/profile/skills/index.js +461 -0
- package/dist/services/profile/skills/index.js.map +1 -0
- package/dist/services/profile/visas/index.d.ts +74 -0
- package/dist/services/profile/visas/index.d.ts.map +1 -0
- package/dist/services/profile/visas/index.js +306 -0
- package/dist/services/profile/visas/index.js.map +1 -0
- package/dist/services/timesheet/index.d.ts +326 -0
- package/dist/services/timesheet/index.d.ts.map +1 -0
- package/dist/services/timesheet/index.js +324 -0
- package/dist/services/timesheet/index.js.map +1 -0
- package/dist/services/translations.d.ts +79 -0
- package/dist/services/translations.d.ts.map +1 -0
- package/dist/services/translations.js +136 -0
- package/dist/services/translations.js.map +1 -0
- package/dist/transport-resilience.d.ts +136 -0
- package/dist/transport-resilience.d.ts.map +1 -0
- package/dist/transport-resilience.js +247 -0
- package/dist/transport-resilience.js.map +1 -0
- package/dist/transport.d.ts +408 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +691 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -12
- package/index.js +0 -7
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Oleksii PELYKH
|
|
3
|
+
/**
|
|
4
|
+
* Maximum rendered length of a {@link WireShapeDiffEntry.value} field
|
|
5
|
+
* (per `docs/wire-validation-error-format.md` § Diff entries).
|
|
6
|
+
* Truncation appends `…` (a single ellipsis character, not three
|
|
7
|
+
* dots) to mark overflow.
|
|
8
|
+
*/
|
|
9
|
+
export const MAX_VALUE_LENGTH = 32;
|
|
10
|
+
/**
|
|
11
|
+
* Verbatim hint string emitted alongside every `WIRE_SHAPE_ERROR`.
|
|
12
|
+
* Lifted into the CLI envelope's `hint` slot and the MCP error-text
|
|
13
|
+
* `Hint:` block; identical across all surfaces so an operator pattern-
|
|
14
|
+
* matches the message regardless of where it's encountered. The text
|
|
15
|
+
* comes directly from `docs/wire-validation-error-format.md` § Code +
|
|
16
|
+
* base fields (M2 / #281).
|
|
17
|
+
*/
|
|
18
|
+
export const WIRE_SHAPE_HINT = "wire shape doesn't match expected — this typically means Toptal changed the API; please file an issue at https://github.com/alexey-pelykh/ttctl/issues with the operation name and timestamp.";
|
|
19
|
+
/**
|
|
20
|
+
* Walk a JSON path against a wire snapshot, returning the value at
|
|
21
|
+
* that path or {@link MISSING} when any segment doesn't resolve. The
|
|
22
|
+
* sentinel is distinct from `undefined` so callers can distinguish
|
|
23
|
+
* "value is undefined on the wire" (data present, field absent) from
|
|
24
|
+
* "path doesn't resolve" (entire branch missing or wireData omitted).
|
|
25
|
+
*/
|
|
26
|
+
const MISSING = Symbol("wire-shape:missing");
|
|
27
|
+
function walkPath(wireData, path) {
|
|
28
|
+
let current = wireData;
|
|
29
|
+
for (const segment of path) {
|
|
30
|
+
if (current === null || current === undefined)
|
|
31
|
+
return MISSING;
|
|
32
|
+
if (typeof segment === "number") {
|
|
33
|
+
if (!Array.isArray(current))
|
|
34
|
+
return MISSING;
|
|
35
|
+
if (segment < 0 || segment >= current.length)
|
|
36
|
+
return MISSING;
|
|
37
|
+
current = current[segment];
|
|
38
|
+
}
|
|
39
|
+
else if (typeof segment === "string") {
|
|
40
|
+
if (typeof current !== "object" || Array.isArray(current))
|
|
41
|
+
return MISSING;
|
|
42
|
+
const key = segment;
|
|
43
|
+
const obj = current;
|
|
44
|
+
if (!(key in obj))
|
|
45
|
+
return MISSING;
|
|
46
|
+
current = obj[key];
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Symbol — Zod permits in record/map keys though wire payloads
|
|
50
|
+
// never carry symbol keys. Fail closed: cannot walk a symbol
|
|
51
|
+
// path against a JSON wire snapshot.
|
|
52
|
+
return MISSING;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return current;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Render an unknown value into a short type tag for the `expected` /
|
|
59
|
+
* `actual` slots of a {@link WireShapeDiffEntry}. Pre-`typeof` array
|
|
60
|
+
* branch keeps `[]` from rendering as `"object"`.
|
|
61
|
+
*/
|
|
62
|
+
function typeNameOf(value) {
|
|
63
|
+
if (value === null)
|
|
64
|
+
return "null";
|
|
65
|
+
if (Array.isArray(value))
|
|
66
|
+
return "array";
|
|
67
|
+
return typeof value;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Render an unknown wire value into a string for the
|
|
71
|
+
* {@link WireShapeDiffEntry.value} slot. Strings round-trip verbatim
|
|
72
|
+
* (no surrounding quotes — the pretty renderer adds them); primitives
|
|
73
|
+
* use `String(value)`; arrays/objects use `JSON.stringify`. Output is
|
|
74
|
+
* truncated to {@link MAX_VALUE_LENGTH} characters with `…` on
|
|
75
|
+
* overflow.
|
|
76
|
+
*/
|
|
77
|
+
function renderValue(value) {
|
|
78
|
+
let rendered;
|
|
79
|
+
if (typeof value === "string") {
|
|
80
|
+
rendered = value;
|
|
81
|
+
}
|
|
82
|
+
else if (value === null || value === undefined || typeof value === "boolean" || typeof value === "number") {
|
|
83
|
+
rendered = String(value);
|
|
84
|
+
}
|
|
85
|
+
else if (typeof value === "bigint") {
|
|
86
|
+
rendered = `${value.toString()}n`;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
try {
|
|
90
|
+
rendered = JSON.stringify(value);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
rendered = "[unstringifiable]";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (rendered.length <= MAX_VALUE_LENGTH)
|
|
97
|
+
return rendered;
|
|
98
|
+
return `${rendered.slice(0, MAX_VALUE_LENGTH - 1)}…`;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Format a Zod issue path (array of `PropertyKey` segments) into the
|
|
102
|
+
* JSON path syntax required by {@link WireShapeDiffEntry.path}.
|
|
103
|
+
* Numeric segments fold into the preceding key as bracket-notation
|
|
104
|
+
* indices (`["records", 0, "duration"]` → `"records[0].duration"`).
|
|
105
|
+
*
|
|
106
|
+
* The empty-path case (top-level / refinement errors) returns the
|
|
107
|
+
* empty string. Callers handle prepending a base path when applicable
|
|
108
|
+
* (used by the `unrecognized_keys` branch in {@link issueToDiffEntries}).
|
|
109
|
+
*/
|
|
110
|
+
function formatJsonPath(path) {
|
|
111
|
+
if (path.length === 0)
|
|
112
|
+
return "";
|
|
113
|
+
const parts = [];
|
|
114
|
+
for (const segment of path) {
|
|
115
|
+
if (typeof segment === "number") {
|
|
116
|
+
const head = parts.length === 0 ? "" : (parts[parts.length - 1] ?? "");
|
|
117
|
+
const next = `${head}[${segment.toString()}]`;
|
|
118
|
+
if (parts.length === 0) {
|
|
119
|
+
parts.push(next);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
parts[parts.length - 1] = next;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (typeof segment === "string") {
|
|
126
|
+
parts.push(segment);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Symbol — Zod allows it in record/map keys though wire payloads
|
|
130
|
+
// never carry symbol keys. Render via .toString() so the message
|
|
131
|
+
// is still readable rather than dropping the segment.
|
|
132
|
+
parts.push(segment.toString());
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return parts.join(".");
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Parse a path string back into segments for deterministic sorting.
|
|
139
|
+
* Numeric segments compare numerically (`[2]` before `[10]`); string
|
|
140
|
+
* segments compare lexicographically. The string `"records[0].duration"`
|
|
141
|
+
* decomposes into `["records", 0, "duration"]`.
|
|
142
|
+
*/
|
|
143
|
+
function parsePathSegments(path) {
|
|
144
|
+
if (path.length === 0)
|
|
145
|
+
return [];
|
|
146
|
+
const segments = [];
|
|
147
|
+
for (const chunk of path.split(".")) {
|
|
148
|
+
const headEnd = chunk.indexOf("[");
|
|
149
|
+
const head = headEnd === -1 ? chunk : chunk.slice(0, headEnd);
|
|
150
|
+
if (head.length > 0)
|
|
151
|
+
segments.push(head);
|
|
152
|
+
const indexMatcher = /\[(\d+)\]/g;
|
|
153
|
+
let match;
|
|
154
|
+
while ((match = indexMatcher.exec(chunk)) !== null) {
|
|
155
|
+
segments.push(Number(match[1]));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return segments;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Compare two diff entries by `path` for deterministic ordering.
|
|
162
|
+
* Numeric path segments compare numerically (`records[2]` before
|
|
163
|
+
* `records[10]`); string segments compare lexicographically. When
|
|
164
|
+
* paths differ only in trailing segments, the shorter path sorts
|
|
165
|
+
* first. The result is byte-identical output across runs — essential
|
|
166
|
+
* for snapshot tests and human pattern-matching of repeat failures
|
|
167
|
+
* across logs.
|
|
168
|
+
*/
|
|
169
|
+
function compareDiffEntries(a, b) {
|
|
170
|
+
const segA = parsePathSegments(a.path);
|
|
171
|
+
const segB = parsePathSegments(b.path);
|
|
172
|
+
const minLen = Math.min(segA.length, segB.length);
|
|
173
|
+
for (let i = 0; i < minLen; i++) {
|
|
174
|
+
const sa = segA[i];
|
|
175
|
+
const sb = segB[i];
|
|
176
|
+
if (sa === undefined || sb === undefined)
|
|
177
|
+
break;
|
|
178
|
+
if (typeof sa === "number" && typeof sb === "number") {
|
|
179
|
+
if (sa !== sb)
|
|
180
|
+
return sa - sb;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const saStr = String(sa);
|
|
184
|
+
const sbStr = String(sb);
|
|
185
|
+
if (saStr !== sbStr)
|
|
186
|
+
return saStr < sbStr ? -1 : 1;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (segA.length !== segB.length)
|
|
190
|
+
return segA.length - segB.length;
|
|
191
|
+
// Paths equal — preserve insertion order via tie-break on the diff
|
|
192
|
+
// entry fields so the comparator is total (required by `Array.sort`
|
|
193
|
+
// contract for stable cross-engine output).
|
|
194
|
+
if (a.op !== b.op)
|
|
195
|
+
return a.op < b.op ? -1 : 1;
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Extract `received <type>` from a Zod v4 `invalid_type` message
|
|
200
|
+
* (`"Invalid input: expected number, received string"`). Returns the
|
|
201
|
+
* received-type string when present, or `null` when the message
|
|
202
|
+
* doesn't match the expected pattern (locale change, custom error
|
|
203
|
+
* map). Used as a fallback when the wire snapshot isn't available
|
|
204
|
+
* via {@link walkPath}.
|
|
205
|
+
*/
|
|
206
|
+
function extractReceivedFromMessage(message) {
|
|
207
|
+
const m = /received\s+(\S+)/.exec(message);
|
|
208
|
+
return m ? (m[1] ?? null) : null;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Project a single Zod issue into one or more {@link WireShapeDiffEntry}
|
|
212
|
+
* rows. The mapping is per `docs/wire-validation-error-format.md` §
|
|
213
|
+
* Diff entries:
|
|
214
|
+
*
|
|
215
|
+
* - `invalid_type` where the wire value at the issue path is
|
|
216
|
+
* `undefined` (or unresolvable) → `-` (schema-required field
|
|
217
|
+
* missing on wire). `value` omitted.
|
|
218
|
+
* - `invalid_type` otherwise → `~` (type mismatch). `value` is the
|
|
219
|
+
* raw wire value (extracted from the snapshot), truncated.
|
|
220
|
+
* - `unrecognized_keys` (strict-mode objects) → one `+` entry per
|
|
221
|
+
* unknown key. `value` omitted (Zod surfaces the key list but not
|
|
222
|
+
* each key's value).
|
|
223
|
+
* - `invalid_value`, `invalid_union`, and other codes → `~` with
|
|
224
|
+
* wire snapshot-derived `actual` / `value`.
|
|
225
|
+
*
|
|
226
|
+
* Fallback for unrecognized codes is `~` rather than skipping the
|
|
227
|
+
* issue — a future Zod release adding a new issue code shouldn't drop
|
|
228
|
+
* the diagnostic.
|
|
229
|
+
*/
|
|
230
|
+
function issueToDiffEntries(issue, wireData) {
|
|
231
|
+
const basePath = formatJsonPath(issue.path);
|
|
232
|
+
if (issue.code === "unrecognized_keys") {
|
|
233
|
+
return issue.keys.map((key) => ({
|
|
234
|
+
op: "+",
|
|
235
|
+
path: basePath.length === 0 ? key : `${basePath}.${key}`,
|
|
236
|
+
expected: "<unset>",
|
|
237
|
+
actual: "unknown",
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
const resolved = walkPath(wireData, issue.path);
|
|
241
|
+
if (issue.code === "invalid_type") {
|
|
242
|
+
// Field absent on wire OR resolved value is undefined → "-".
|
|
243
|
+
if (resolved === MISSING || resolved === undefined) {
|
|
244
|
+
const actualFromMessage = extractReceivedFromMessage(issue.message);
|
|
245
|
+
return [
|
|
246
|
+
{
|
|
247
|
+
op: "-",
|
|
248
|
+
path: basePath,
|
|
249
|
+
expected: issue.expected,
|
|
250
|
+
actual: actualFromMessage ?? "undefined",
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
}
|
|
254
|
+
return [
|
|
255
|
+
{
|
|
256
|
+
op: "~",
|
|
257
|
+
path: basePath,
|
|
258
|
+
expected: issue.expected,
|
|
259
|
+
actual: typeNameOf(resolved),
|
|
260
|
+
value: renderValue(resolved),
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
}
|
|
264
|
+
if (issue.code === "invalid_value") {
|
|
265
|
+
const expected = issue.values.length === 1 ? renderValue(issue.values[0]) : issue.values.map((v) => renderValue(v)).join(" | ");
|
|
266
|
+
if (resolved === MISSING) {
|
|
267
|
+
return [
|
|
268
|
+
{
|
|
269
|
+
op: "~",
|
|
270
|
+
path: basePath,
|
|
271
|
+
expected,
|
|
272
|
+
actual: "unknown",
|
|
273
|
+
},
|
|
274
|
+
];
|
|
275
|
+
}
|
|
276
|
+
return [
|
|
277
|
+
{
|
|
278
|
+
op: "~",
|
|
279
|
+
path: basePath,
|
|
280
|
+
expected,
|
|
281
|
+
actual: typeNameOf(resolved),
|
|
282
|
+
value: renderValue(resolved),
|
|
283
|
+
},
|
|
284
|
+
];
|
|
285
|
+
}
|
|
286
|
+
// invalid_union, invalid_format, too_big, too_small, custom, …
|
|
287
|
+
// Fall back to a structural diff entry. `actual` reflects the wire
|
|
288
|
+
// shape when resolvable; `expected` lifts the issue message so the
|
|
289
|
+
// operator sees the constraint that failed.
|
|
290
|
+
if (resolved === MISSING) {
|
|
291
|
+
return [
|
|
292
|
+
{
|
|
293
|
+
op: "~",
|
|
294
|
+
path: basePath,
|
|
295
|
+
expected: issue.message,
|
|
296
|
+
actual: "unknown",
|
|
297
|
+
},
|
|
298
|
+
];
|
|
299
|
+
}
|
|
300
|
+
return [
|
|
301
|
+
{
|
|
302
|
+
op: "~",
|
|
303
|
+
path: basePath,
|
|
304
|
+
expected: issue.message,
|
|
305
|
+
actual: typeNameOf(resolved),
|
|
306
|
+
value: renderValue(resolved),
|
|
307
|
+
},
|
|
308
|
+
];
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Build the field-level diff from a {@link z.ZodError}. Issues are
|
|
312
|
+
* projected per {@link issueToDiffEntries} and sorted deterministically
|
|
313
|
+
* per {@link compareDiffEntries} so two runs against the same drifted
|
|
314
|
+
* wire response produce byte-identical output (load-bearing for
|
|
315
|
+
* snapshot tests and CI grep-fu).
|
|
316
|
+
*
|
|
317
|
+
* `wireData` is the original `body.data` snapshot that failed
|
|
318
|
+
* validation — walking it against each issue's `path` recovers the
|
|
319
|
+
* actual wire value (Zod v4 strips `input` from public issues; see
|
|
320
|
+
* file header). Pass `undefined` only in tests / migration paths
|
|
321
|
+
* where the snapshot is unavailable; entries will degrade to path-
|
|
322
|
+
* only render in that case.
|
|
323
|
+
*
|
|
324
|
+
* Exported for unit tests; callers should prefer
|
|
325
|
+
* {@link buildWireShapeError} which composes the full payload.
|
|
326
|
+
*/
|
|
327
|
+
export function projectZodErrorToDiff(error, wireData) {
|
|
328
|
+
const entries = [];
|
|
329
|
+
for (const issue of error.issues) {
|
|
330
|
+
entries.push(...issueToDiffEntries(issue, wireData));
|
|
331
|
+
}
|
|
332
|
+
entries.sort(compareDiffEntries);
|
|
333
|
+
return entries;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Compose the `message` line of a `WIRE_SHAPE_ERROR` per
|
|
337
|
+
* `docs/wire-validation-error-format.md` § Code + base fields. The
|
|
338
|
+
* pluralisation is wire-stable (`1 field issue` vs `2 field issues`)
|
|
339
|
+
* so snapshot tests don't need locale-aware matchers.
|
|
340
|
+
*
|
|
341
|
+
* Exported for unit tests; callers should prefer
|
|
342
|
+
* {@link buildWireShapeError} which composes the full payload.
|
|
343
|
+
*/
|
|
344
|
+
export function buildWireShapeMessage(operationName, diffEntryCount) {
|
|
345
|
+
const noun = diffEntryCount === 1 ? "issue" : "issues";
|
|
346
|
+
return `Wire shape doesn't match expected schema for operation \`${operationName}\` (${diffEntryCount.toString()} field ${noun}).`;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Convert a {@link z.ZodError} from a failed `schema.parse(body.data)`
|
|
350
|
+
* call into the full {@link WireShapeErrorPayload}. The payload is
|
|
351
|
+
* stable across surfaces (CLI envelope, MCP error text) and round-
|
|
352
|
+
* trips through JSON without redaction (body.data never carries the
|
|
353
|
+
* bearer).
|
|
354
|
+
*
|
|
355
|
+
* Each service's `callGateway` / `callTalentProfile` helper wraps the
|
|
356
|
+
* payload's `message` into its own domain-error class with
|
|
357
|
+
* `code: "WIRE_SHAPE_ERROR"` and `cause: zodError`; the CLI / MCP
|
|
358
|
+
* layers reconstruct the diff from `cause` when rendering.
|
|
359
|
+
*
|
|
360
|
+
* Pass `wireData` (the original `body.data` snapshot) so the diff
|
|
361
|
+
* can recover actual wire values via path walk — Zod v4 strips the
|
|
362
|
+
* `input` field from public issues, so the snapshot is the only way
|
|
363
|
+
* to render `actual` / `value` for non-`unrecognized_keys` entries.
|
|
364
|
+
*
|
|
365
|
+
* Z-3 (#286) wires the mechanism; no production op has `schema`
|
|
366
|
+
* passed in yet (Z-4 / #288 ships the first beachhead).
|
|
367
|
+
*/
|
|
368
|
+
export function buildWireShapeError(operationName, error, wireData) {
|
|
369
|
+
const diff = projectZodErrorToDiff(error, wireData);
|
|
370
|
+
return {
|
|
371
|
+
message: buildWireShapeMessage(operationName, diff.length),
|
|
372
|
+
hint: WIRE_SHAPE_HINT,
|
|
373
|
+
diff,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
//# sourceMappingURL=wire-shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-shape.js","sourceRoot":"","sources":["../../src/lib/wire-shape.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Credentials } from "./types.js";
|
|
2
|
+
export declare class OnePasswordError extends Error {
|
|
3
|
+
readonly name = "OnePasswordError";
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Resolve credentials from a 1Password item reference.
|
|
7
|
+
*
|
|
8
|
+
* Accepted forms:
|
|
9
|
+
* - `op://VAULT/ITEM` (2-segment) — `op` CLI uses its default account or
|
|
10
|
+
* `OP_ACCOUNT` env.
|
|
11
|
+
* - `op://ACCOUNT/VAULT/ITEM` (3-segment) — `--account ACCOUNT` is forwarded
|
|
12
|
+
* to `op item get` so users with multiple configured accounts can select
|
|
13
|
+
* one explicitly. ACCOUNT may be a UUID, shorthand, or sign-in email; `op`
|
|
14
|
+
* itself validates the value.
|
|
15
|
+
*
|
|
16
|
+
* Mechanism: shells out to `op item get ITEM --vault VAULT [--account ACCOUNT]
|
|
17
|
+
* --format json`, then matches credential fields by `purpose` (`USERNAME` /
|
|
18
|
+
* `PASSWORD`) — the canonical semantic identifier 1Password sets automatically
|
|
19
|
+
* for LOGIN-category items. The 1Password Desktop app brokers authentication
|
|
20
|
+
* (typically via biometric prompt) — no service-account token is required.
|
|
21
|
+
*
|
|
22
|
+
* Field-matching by `purpose` rather than `label` is necessary because
|
|
23
|
+
* browser-autosaved items inherit their `label` from the HTML form input name
|
|
24
|
+
* (e.g. `user[email]`, `user[password]`) — only `purpose` is canonical. The
|
|
25
|
+
* item must be a LOGIN-category item (any item with both USERNAME and PASSWORD
|
|
26
|
+
* purposes set).
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveOnePasswordReference(ref: string): Credentials;
|
|
29
|
+
//# sourceMappingURL=onepassword.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onepassword.d.ts","sourceRoot":"","sources":["../src/onepassword.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAgB9C,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,SAAkB,IAAI,sBAAsB;CAC7C;AA+BD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAqDpE"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Oleksii PELYKH
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
/**
|
|
6
|
+
* Shape of `op item get --format json` output (subset; only fields we use).
|
|
7
|
+
*/
|
|
8
|
+
const OpItemSchema = z.object({
|
|
9
|
+
fields: z.array(z.object({
|
|
10
|
+
id: z.string().optional(),
|
|
11
|
+
label: z.string().optional(),
|
|
12
|
+
value: z.string().optional(),
|
|
13
|
+
purpose: z.string().optional(),
|
|
14
|
+
})),
|
|
15
|
+
});
|
|
16
|
+
export class OnePasswordError extends Error {
|
|
17
|
+
name = "OnePasswordError";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse `op://[ACCOUNT/]VAULT/ITEM` into its components. Returns `null` for any
|
|
21
|
+
* shape outside the 2- or 3-segment grammar (matches `OnePasswordItemRefSchema`
|
|
22
|
+
* in `config.ts`).
|
|
23
|
+
*
|
|
24
|
+
* Splitting on `/` after stripping the `op://` prefix avoids regex capture-group
|
|
25
|
+
* contortions and produces clear branching on segment count.
|
|
26
|
+
*/
|
|
27
|
+
function parseReference(ref) {
|
|
28
|
+
if (!ref.startsWith("op://"))
|
|
29
|
+
return null;
|
|
30
|
+
const segments = ref.slice("op://".length).split("/");
|
|
31
|
+
if (segments.some((s) => s.length === 0))
|
|
32
|
+
return null;
|
|
33
|
+
if (segments.length === 2) {
|
|
34
|
+
const [vault, item] = segments;
|
|
35
|
+
return { vault, item };
|
|
36
|
+
}
|
|
37
|
+
if (segments.length === 3) {
|
|
38
|
+
const [account, vault, item] = segments;
|
|
39
|
+
return { account, vault, item };
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve credentials from a 1Password item reference.
|
|
45
|
+
*
|
|
46
|
+
* Accepted forms:
|
|
47
|
+
* - `op://VAULT/ITEM` (2-segment) — `op` CLI uses its default account or
|
|
48
|
+
* `OP_ACCOUNT` env.
|
|
49
|
+
* - `op://ACCOUNT/VAULT/ITEM` (3-segment) — `--account ACCOUNT` is forwarded
|
|
50
|
+
* to `op item get` so users with multiple configured accounts can select
|
|
51
|
+
* one explicitly. ACCOUNT may be a UUID, shorthand, or sign-in email; `op`
|
|
52
|
+
* itself validates the value.
|
|
53
|
+
*
|
|
54
|
+
* Mechanism: shells out to `op item get ITEM --vault VAULT [--account ACCOUNT]
|
|
55
|
+
* --format json`, then matches credential fields by `purpose` (`USERNAME` /
|
|
56
|
+
* `PASSWORD`) — the canonical semantic identifier 1Password sets automatically
|
|
57
|
+
* for LOGIN-category items. The 1Password Desktop app brokers authentication
|
|
58
|
+
* (typically via biometric prompt) — no service-account token is required.
|
|
59
|
+
*
|
|
60
|
+
* Field-matching by `purpose` rather than `label` is necessary because
|
|
61
|
+
* browser-autosaved items inherit their `label` from the HTML form input name
|
|
62
|
+
* (e.g. `user[email]`, `user[password]`) — only `purpose` is canonical. The
|
|
63
|
+
* item must be a LOGIN-category item (any item with both USERNAME and PASSWORD
|
|
64
|
+
* purposes set).
|
|
65
|
+
*/
|
|
66
|
+
export function resolveOnePasswordReference(ref) {
|
|
67
|
+
const parsed = parseReference(ref);
|
|
68
|
+
if (!parsed) {
|
|
69
|
+
throw new OnePasswordError(`Invalid reference: ${ref}. Expected op://[account/]vault/item.`);
|
|
70
|
+
}
|
|
71
|
+
const { account, vault, item } = parsed;
|
|
72
|
+
const args = ["item", "get", item, "--vault", vault];
|
|
73
|
+
if (account !== undefined)
|
|
74
|
+
args.push("--account", account);
|
|
75
|
+
args.push("--format", "json");
|
|
76
|
+
let raw;
|
|
77
|
+
try {
|
|
78
|
+
raw = execFileSync("op", args, {
|
|
79
|
+
encoding: "utf8",
|
|
80
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
const e = err;
|
|
85
|
+
if (e.code === "ENOENT") {
|
|
86
|
+
throw new OnePasswordError("1Password CLI (`op`) not found. Install: https://developer.1password.com/docs/cli/get-started/");
|
|
87
|
+
}
|
|
88
|
+
throw new OnePasswordError(`op item get failed: ${e.message}`);
|
|
89
|
+
}
|
|
90
|
+
let parsedJson;
|
|
91
|
+
try {
|
|
92
|
+
parsedJson = JSON.parse(raw);
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
throw new OnePasswordError(`op returned non-JSON output: ${err.message}`);
|
|
96
|
+
}
|
|
97
|
+
// `op item get --format json` returns the full item object with a `fields`
|
|
98
|
+
// array. Older `op` CLI versions returned a bare fields array instead;
|
|
99
|
+
// normalize both shapes into the wrapped form before schema validation.
|
|
100
|
+
const normalized = Array.isArray(parsedJson) ? { fields: parsedJson } : parsedJson;
|
|
101
|
+
const result = OpItemSchema.safeParse(normalized);
|
|
102
|
+
if (!result.success) {
|
|
103
|
+
throw new OnePasswordError(`Unexpected op output shape: ${result.error.message}`);
|
|
104
|
+
}
|
|
105
|
+
const username = result.data.fields.find((f) => f.purpose === "USERNAME")?.value;
|
|
106
|
+
const password = result.data.fields.find((f) => f.purpose === "PASSWORD")?.value;
|
|
107
|
+
if (!username || !password) {
|
|
108
|
+
throw new OnePasswordError(`Item ${vault}/${item} must have fields with USERNAME and PASSWORD purposes (LOGIN-category items have these by default).`);
|
|
109
|
+
}
|
|
110
|
+
return { email: username, password };
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=onepassword.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"onepassword.js","sourceRoot":"","sources":["../src/onepassword.ts"],"names":[],"mappings":"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"}
|