@ttctl/cli 0.1.0-rc.6 → 0.1.0-rc.8
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/commands/applications/availability-request.d.ts +47 -0
- package/dist/commands/applications/availability-request.d.ts.map +1 -0
- package/dist/commands/applications/availability-request.js +98 -0
- package/dist/commands/applications/availability-request.js.map +1 -0
- package/dist/commands/applications/confirm.d.ts +32 -2
- package/dist/commands/applications/confirm.d.ts.map +1 -1
- package/dist/commands/applications/confirm.js +194 -1
- package/dist/commands/applications/confirm.js.map +1 -1
- package/dist/commands/applications/index.d.ts +6 -2
- package/dist/commands/applications/index.d.ts.map +1 -1
- package/dist/commands/applications/index.js +112 -3
- package/dist/commands/applications/index.js.map +1 -1
- package/dist/commands/applications/interview.d.ts +159 -0
- package/dist/commands/applications/interview.d.ts.map +1 -0
- package/dist/commands/applications/interview.js +396 -0
- package/dist/commands/applications/interview.js.map +1 -0
- package/dist/commands/jobs/apply.d.ts +194 -0
- package/dist/commands/jobs/apply.d.ts.map +1 -0
- package/dist/commands/jobs/apply.js +505 -0
- package/dist/commands/jobs/apply.js.map +1 -0
- package/dist/commands/jobs/index.d.ts +19 -5
- package/dist/commands/jobs/index.d.ts.map +1 -1
- package/dist/commands/jobs/index.js +78 -6
- package/dist/commands/jobs/index.js.map +1 -1
- package/dist/commands/jobs/show.d.ts +63 -2
- package/dist/commands/jobs/show.d.ts.map +1 -1
- package/dist/commands/jobs/show.js +77 -5
- package/dist/commands/jobs/show.js.map +1 -1
- package/dist/commands/payments/index.d.ts +5 -2
- package/dist/commands/payments/index.d.ts.map +1 -1
- package/dist/commands/payments/index.js +27 -4
- package/dist/commands/payments/index.js.map +1 -1
- package/dist/commands/payments/rate.d.ts +16 -0
- package/dist/commands/payments/rate.d.ts.map +1 -1
- package/dist/commands/payments/rate.js +30 -0
- package/dist/commands/payments/rate.js.map +1 -1
- package/dist/commands/payments/summary.d.ts +17 -0
- package/dist/commands/payments/summary.d.ts.map +1 -0
- package/dist/commands/payments/summary.js +42 -0
- package/dist/commands/payments/summary.js.map +1 -0
- package/dist/commands/profile/employment/index.d.ts.map +1 -1
- package/dist/commands/profile/employment/index.js +23 -3
- package/dist/commands/profile/employment/index.js.map +1 -1
- package/dist/commands/profile/reviews/index.d.ts.map +1 -1
- package/dist/commands/profile/reviews/index.js +5 -1
- package/dist/commands/profile/reviews/index.js.map +1 -1
- package/dist/commands/profile/reviews/submit-for-review.d.ts +8 -0
- package/dist/commands/profile/reviews/submit-for-review.d.ts.map +1 -1
- package/dist/commands/profile/reviews/submit-for-review.js +15 -1
- package/dist/commands/profile/reviews/submit-for-review.js.map +1 -1
- package/dist/lib/json-input.d.ts +121 -0
- package/dist/lib/json-input.d.ts.map +1 -0
- package/dist/lib/json-input.js +245 -0
- package/dist/lib/json-input.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (C) 2026 Oleksii PELYKH
|
|
3
|
+
import { applications } from "@ttctl/core";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { getCliDryRun } from "../../lib/dry-run.js";
|
|
6
|
+
import { emitDryRunSuccess, emitErrorAndExit, emitUpdateSuccess } from "../../lib/envelopes.js";
|
|
7
|
+
import { JsonInputError, parseAsRecovered, readJsonInput } from "../../lib/json-input.js";
|
|
8
|
+
import { emitResult } from "../../lib/output.js";
|
|
9
|
+
import { handleApplicationsError, loadAuthTokenOrExit } from "../applications/shared.js";
|
|
10
|
+
const DECIMAL_PATTERN = /^\d+(\.\d+)?$/;
|
|
11
|
+
/**
|
|
12
|
+
* Stage-2 Zod schemas for `--answers-file` / `--pitch-file` payload
|
|
13
|
+
* validation (#438). Materialized once at module load and reused across
|
|
14
|
+
* parse invocations — the codegen factory pattern returns a fresh
|
|
15
|
+
* `z.object(...)` per call, so we cache the result.
|
|
16
|
+
*
|
|
17
|
+
* `.strict()` is mandatory at this boundary: codegen emits schemas with
|
|
18
|
+
* default "strip unknown" semantics; the AC requires extra unknown keys
|
|
19
|
+
* to reject with a field-path error.
|
|
20
|
+
*/
|
|
21
|
+
const STRICT_MATCHER_ANSWER_SCHEMA = applications.JobPositionAnswerInputSchema().strict();
|
|
22
|
+
const STRICT_EXPERTISE_ANSWER_SCHEMA = applications.JobExpertiseAnswerInputSchema().strict();
|
|
23
|
+
const STRICT_PITCH_INPUT_SCHEMA = applications.PitchInputSchema().strict();
|
|
24
|
+
const MATCHER_ANSWERS_ARRAY_SCHEMA = z.array(STRICT_MATCHER_ANSWER_SCHEMA);
|
|
25
|
+
const EXPERTISE_ANSWERS_ARRAY_SCHEMA = z.array(STRICT_EXPERTISE_ANSWER_SCHEMA);
|
|
26
|
+
/**
|
|
27
|
+
* Action handler for `ttctl jobs apply <id>` (#430).
|
|
28
|
+
*
|
|
29
|
+
* Issues the direct `JobApply` mutation via `applications.apply()` (per
|
|
30
|
+
* ADR-008 § Decision Part 5: the service module is `applications`; the
|
|
31
|
+
* user-facing verb lives on `jobs`). The handler enforces three
|
|
32
|
+
* pre-wire gates in order:
|
|
33
|
+
*
|
|
34
|
+
* 1. **`--show-questions` preview** — when set, fetches `applyData`
|
|
35
|
+
* + `applyQuestions` in parallel and emits the projection;
|
|
36
|
+
* returns BEFORE the consent gate (read-only path).
|
|
37
|
+
* 2. **Consent gate** — `--consent` is REQUIRED per ADR-008 § Decision
|
|
38
|
+
* Part 4. Absence raises `CONSENT_REQUIRED` with no wire call
|
|
39
|
+
* issued. The service's own runtime check at `apply()` is
|
|
40
|
+
* defense-in-depth.
|
|
41
|
+
* 3. **`--rate` validation** — decimal-string format enforced via
|
|
42
|
+
* {@link DECIMAL_PATTERN} (mirrors the `applications confirm`
|
|
43
|
+
* pattern). Bad input refuses with `MUTATION_ERROR`.
|
|
44
|
+
*
|
|
45
|
+
* `--answers-file` / `--pitch-file` are loaded + wrapper-shape-validated
|
|
46
|
+
* BEFORE the apply call (mirrors #428 confirm semantics): malformed
|
|
47
|
+
* JSON or wrong wrapper shape refuses with `VALIDATION_ERROR` and no
|
|
48
|
+
* mutation is issued.
|
|
49
|
+
*
|
|
50
|
+
* **DESTRUCTIVE** — applying to a job creates a `JobApplication`
|
|
51
|
+
* record. No `withdraw` operation is available on the wire (per ADR-008
|
|
52
|
+
* § What We're NOT Solving). Prefer `--dry-run` to preview the wire
|
|
53
|
+
* payload first; the AC scenarios pin the dry-run preview shape.
|
|
54
|
+
*/
|
|
55
|
+
export async function runJobsApply(id, opts) {
|
|
56
|
+
const token = await loadAuthTokenOrExit("jobs apply", opts.output);
|
|
57
|
+
const dryRun = getCliDryRun();
|
|
58
|
+
// ---- 1. --show-questions preview (BEFORE consent gate) ----
|
|
59
|
+
// Read-only path: fetches the pre-apply context the user would need
|
|
60
|
+
// to author an answers-file payload (REQ-Q3). Skips both the consent
|
|
61
|
+
// gate AND the JobApply mutation; matches the AC scenario
|
|
62
|
+
// "--show-questions issues pre-fetch but skips mutation".
|
|
63
|
+
if (opts.showQuestions === true) {
|
|
64
|
+
let preApply;
|
|
65
|
+
let questions;
|
|
66
|
+
try {
|
|
67
|
+
[preApply, questions] = await Promise.all([
|
|
68
|
+
applications.applyData(token, id),
|
|
69
|
+
applications.applyQuestions(token, id),
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
handleApplicationsError("jobs apply", err, opts.output);
|
|
74
|
+
}
|
|
75
|
+
const projection = {
|
|
76
|
+
jobId: id,
|
|
77
|
+
canApply: preApply.canApply,
|
|
78
|
+
applyErrors: preApply.applyErrors,
|
|
79
|
+
suggestedRate: preApply.suggestedRate,
|
|
80
|
+
rateValidation: preApply.rateValidation,
|
|
81
|
+
matcherQuestions: questions.matcherQuestions,
|
|
82
|
+
expertiseQuestions: questions.expertiseQuestions,
|
|
83
|
+
};
|
|
84
|
+
// --suggest-answers + --show-questions: fetch the suggestions
|
|
85
|
+
// alongside the question inventory. Falls into the same graceful
|
|
86
|
+
// degradation pattern as the apply-path branch — failures of the
|
|
87
|
+
// suggestion fetch emit a stderr warning and the preview output
|
|
88
|
+
// omits the `suggestions` section. The standard `applyQuestions`
|
|
89
|
+
// failure path (above) already short-circuited via
|
|
90
|
+
// handleApplicationsError, so reaching here means the inventory
|
|
91
|
+
// resolved.
|
|
92
|
+
if (opts.suggestAnswers === true) {
|
|
93
|
+
const suggestions = await fetchSuggestionsOrWarn(token, id);
|
|
94
|
+
if (suggestions !== null)
|
|
95
|
+
projection.suggestions = suggestions;
|
|
96
|
+
}
|
|
97
|
+
emitResult(projection, opts.output, {
|
|
98
|
+
pretty: (data) => formatShowQuestions(data),
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// ---- 2. Consent gate (CLI-level refusal; no wire call) ----
|
|
103
|
+
// The service's own check (`applications.apply` line 2598) is
|
|
104
|
+
// defense-in-depth. Refusing at the CLI layer keeps the unit-test
|
|
105
|
+
// signal clean — `applications.apply` is never called when consent
|
|
106
|
+
// is absent, matching the AC "no JobApply wire mutation is sent".
|
|
107
|
+
if (opts.consent !== true) {
|
|
108
|
+
emitErrorAndExit({
|
|
109
|
+
operation: "jobs.apply",
|
|
110
|
+
format: opts.output,
|
|
111
|
+
errors: [
|
|
112
|
+
{
|
|
113
|
+
code: "CONSENT_REQUIRED",
|
|
114
|
+
message: "--consent is required to apply: this flag represents your acceptance of Toptal's apply terms (a legal-compliance attestation). Auto-filling on your behalf is forbidden per ADR-008.",
|
|
115
|
+
hint: "Re-run with --consent to attest you have read and accepted Toptal's apply terms. Use --dry-run to preview the wire payload first.",
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
prettySummary: "jobs apply failed (CONSENT_REQUIRED): --consent is required to apply.",
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
// ---- 3. Build ApplyInput (with --rate validation + file loads) ----
|
|
122
|
+
const input = { consentIssued: true };
|
|
123
|
+
if (opts.rate !== undefined) {
|
|
124
|
+
if (!DECIMAL_PATTERN.test(opts.rate)) {
|
|
125
|
+
handleApplicationsError("jobs apply", new applications.ApplicationsError("MUTATION_ERROR", `--rate must be a non-negative decimal (got "${opts.rate}").`), opts.output);
|
|
126
|
+
}
|
|
127
|
+
input.requestedHourlyRate = opts.rate;
|
|
128
|
+
}
|
|
129
|
+
if (opts.message !== undefined)
|
|
130
|
+
input.message = opts.message;
|
|
131
|
+
// Load + parse answers / pitch JSON BEFORE any wire call (mirrors the
|
|
132
|
+
// #428 confirm pattern). Parse / file failures short-circuit with a
|
|
133
|
+
// `VALIDATION_ERROR` envelope; no mutation is issued.
|
|
134
|
+
if (opts.answersFile !== undefined) {
|
|
135
|
+
const payload = await loadJsonInputOrExit(opts.answersFile, "answers-file", opts.output);
|
|
136
|
+
const answers = narrowAnswersPayload(payload, opts.output);
|
|
137
|
+
if (answers.matcherAnswers !== undefined)
|
|
138
|
+
input.matcherAnswers = answers.matcherAnswers;
|
|
139
|
+
if (answers.expertiseAnswers !== undefined)
|
|
140
|
+
input.expertiseAnswers = answers.expertiseAnswers;
|
|
141
|
+
}
|
|
142
|
+
if (opts.pitchFile !== undefined) {
|
|
143
|
+
const payload = await loadJsonInputOrExit(opts.pitchFile, "pitch-file", opts.output);
|
|
144
|
+
input.pitchData = narrowPitchPayload(payload, opts.output);
|
|
145
|
+
}
|
|
146
|
+
// ---- 4. Issue apply (or dry-run preview) ----
|
|
147
|
+
let outcome;
|
|
148
|
+
try {
|
|
149
|
+
outcome = await applications.apply(token, id, input, { dryRun });
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
handleApplicationsError("jobs apply", err, opts.output);
|
|
153
|
+
}
|
|
154
|
+
if (outcome.kind === "preview") {
|
|
155
|
+
// --suggest-answers + --dry-run: suppress the suggestion fetch
|
|
156
|
+
// (the apply mutation also issued no wire call under dry-run;
|
|
157
|
+
// matching that posture). The preview envelope is unchanged
|
|
158
|
+
// from #430.
|
|
159
|
+
emitDryRunSuccess({
|
|
160
|
+
operation: "jobs.apply",
|
|
161
|
+
format: opts.output,
|
|
162
|
+
preview: outcome.preview,
|
|
163
|
+
});
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// --suggest-answers (apply path): issue the suggestion fetch
|
|
167
|
+
// alongside the JobApply mutation result. The fetch runs AFTER
|
|
168
|
+
// the apply succeeded so a suggestion-fetch failure cannot block
|
|
169
|
+
// the apply (matches AC scenario "Suggestion query fails — apply
|
|
170
|
+
// continues without suggestions"). Failures emit a stderr warning
|
|
171
|
+
// and surface no `suggestions` section in the output.
|
|
172
|
+
const result = outcome.result;
|
|
173
|
+
const summaryParts = [`status=${result.statusV2.value}`];
|
|
174
|
+
if (result.requestedHourlyRate !== null)
|
|
175
|
+
summaryParts.push(`rate=${result.requestedHourlyRate.decimal}`);
|
|
176
|
+
if (opts.suggestAnswers === true) {
|
|
177
|
+
const suggestions = await fetchSuggestionsOrWarn(token, id);
|
|
178
|
+
const projection = {
|
|
179
|
+
application: result,
|
|
180
|
+
suggestions: suggestions ?? [],
|
|
181
|
+
};
|
|
182
|
+
emitUpdateSuccess({
|
|
183
|
+
operation: "jobs.apply",
|
|
184
|
+
format: opts.output,
|
|
185
|
+
updated: projection,
|
|
186
|
+
prettySummary: `Applied to job ${id}: application ${result.id} (${summaryParts.join(", ")})`,
|
|
187
|
+
prettyEntity: (data) => formatApplyWithSuggestions(data),
|
|
188
|
+
});
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
emitUpdateSuccess({
|
|
192
|
+
operation: "jobs.apply",
|
|
193
|
+
format: opts.output,
|
|
194
|
+
updated: result,
|
|
195
|
+
prettySummary: `Applied to job ${id}: application ${result.id} (${summaryParts.join(", ")})`,
|
|
196
|
+
prettyEntity: (data) => formatJobApplicationRecord(data),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Issue `applications.similarAnswers(token, jobId)` and surface a
|
|
201
|
+
* stderr warning instead of crashing on failure (#452 scenario
|
|
202
|
+
* "Suggestion query fails — apply continues without suggestions").
|
|
203
|
+
* Returns `null` on failure so callers can omit the suggestions
|
|
204
|
+
* section from the output payload.
|
|
205
|
+
*
|
|
206
|
+
* NOT an envelope-emitting handler — failures don't terminate the
|
|
207
|
+
* process; the apply path continues. Used by both the
|
|
208
|
+
* `--show-questions` branch (preview-only) and the apply branch
|
|
209
|
+
* (post-apply), so the warning text is generic.
|
|
210
|
+
*/
|
|
211
|
+
async function fetchSuggestionsOrWarn(token, jobId) {
|
|
212
|
+
try {
|
|
213
|
+
return await applications.similarAnswers(token, jobId);
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
217
|
+
process.stderr.write(`warning: --suggest-answers fetch failed (${msg}); continuing without suggestions.\n`);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Read + parse a JSON file (or stdin) via {@link readJsonInput}. On any
|
|
223
|
+
* {@link JsonInputError} surface a structured `VALIDATION_ERROR` envelope
|
|
224
|
+
* and exit non-zero — the AC pins the envelope code, not the inner
|
|
225
|
+
* JsonInputError code (which appears in the message instead). Non-Error
|
|
226
|
+
* throws are re-raised as-is so the outer handler catches them. Returns
|
|
227
|
+
* the parsed value as `unknown` (Stage-1 opaque pass-through).
|
|
228
|
+
*
|
|
229
|
+
* Sibling to `loadJsonInputOrExit` in `applications/confirm.ts` —
|
|
230
|
+
* domain-local twin per the project's one-copy-per-CLI-surface
|
|
231
|
+
* convention (no cross-domain import; the two callers share the
|
|
232
|
+
* `JsonInputError` namespace but render `operation: "jobs.apply"` vs
|
|
233
|
+
* `operation: "applications.confirm"` distinctly).
|
|
234
|
+
*/
|
|
235
|
+
async function loadJsonInputOrExit(rawPath, flagName, format) {
|
|
236
|
+
try {
|
|
237
|
+
return await readJsonInput(rawPath, { flagName });
|
|
238
|
+
}
|
|
239
|
+
catch (err) {
|
|
240
|
+
if (err instanceof JsonInputError) {
|
|
241
|
+
emitErrorAndExit({
|
|
242
|
+
operation: "jobs.apply",
|
|
243
|
+
format,
|
|
244
|
+
errors: [{ code: "VALIDATION_ERROR", message: err.message, hint: hintForJsonInputCode(err.code) }],
|
|
245
|
+
prettySummary: `jobs apply failed (VALIDATION_ERROR): ${err.message}`,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
throw err;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Map a {@link JsonInputError} code to an actionable recovery hint
|
|
253
|
+
* surfaced through the `VALIDATION_ERROR` envelope's `hint:` field.
|
|
254
|
+
* Sibling to the same function in `applications/confirm.ts` —
|
|
255
|
+
* domain-local twin per the project's one-copy-per-CLI-surface
|
|
256
|
+
* convention.
|
|
257
|
+
*/
|
|
258
|
+
function hintForJsonInputCode(code) {
|
|
259
|
+
switch (code) {
|
|
260
|
+
case "FILE_NOT_FOUND":
|
|
261
|
+
return "Verify the path exists; the absolute path is in the error message.";
|
|
262
|
+
case "FILE_READ_ERROR":
|
|
263
|
+
return "Check filesystem permissions on the file (and its parent directory).";
|
|
264
|
+
case "PARSE_ERROR":
|
|
265
|
+
return "Fix the JSON syntax at the cited line/column; the file shape should be { matcherAnswers: [...], expertiseAnswers: [...] }.";
|
|
266
|
+
case "SCHEMA_ERROR":
|
|
267
|
+
return "Fix the payload to match the recovered shape — matcher answers use { id, answer }; expertise answers use { questionId, other, subjectId }; pitch uses PitchInput.";
|
|
268
|
+
case "STDIN_UNAVAILABLE":
|
|
269
|
+
return "Pipe JSON into stdin (e.g. `cat answers.json | ttctl ...`) or pass a file path.";
|
|
270
|
+
case "STDIN_DOUBLE_CLAIM":
|
|
271
|
+
return "Only one of --answers-file and --pitch-file may read from stdin per invocation; pass a file path for the other.";
|
|
272
|
+
default:
|
|
273
|
+
return "Inspect the input and retry.";
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Narrow the parsed JSON from `--answers-file` into the
|
|
278
|
+
* {@link AnswersFilePayload} shape. Surfaces a `VALIDATION_ERROR`
|
|
279
|
+
* envelope (NOT a wire call) when the top-level wrapper shape is wrong
|
|
280
|
+
* — e.g. a bare array, a string, or a non-object — OR when the inner
|
|
281
|
+
* arrays fail the recovered Zod schemas (per #438 Stage-2 tightening).
|
|
282
|
+
* Each inner schema is wrapped with `.strict()` so extra unknown keys
|
|
283
|
+
* surface as a field-path Zod issue (e.g. `matcherAnswers[2].questionId:
|
|
284
|
+
* unrecognized_keys (Unrecognized key(s) in object)`) — the AC's
|
|
285
|
+
* behavioral scenario 2 "Extra unknown key in payload rejected with
|
|
286
|
+
* field-path error".
|
|
287
|
+
*
|
|
288
|
+
* Sibling to `narrowAnswersPayload` in `applications/confirm.ts` —
|
|
289
|
+
* domain-local twin per the project's one-copy-per-CLI-surface
|
|
290
|
+
* convention. The two copies stay structurally identical; the only
|
|
291
|
+
* delta is the `operation:` envelope field and the `prettySummary`
|
|
292
|
+
* verb prefix.
|
|
293
|
+
*/
|
|
294
|
+
function narrowAnswersPayload(payload, format) {
|
|
295
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
296
|
+
emitErrorAndExit({
|
|
297
|
+
operation: "jobs.apply",
|
|
298
|
+
format,
|
|
299
|
+
errors: [
|
|
300
|
+
{
|
|
301
|
+
code: "VALIDATION_ERROR",
|
|
302
|
+
message: "--answers-file: expected a JSON object with `matcherAnswers` and/or `expertiseAnswers` arrays.",
|
|
303
|
+
hint: "File shape: { matcherAnswers: [...], expertiseAnswers: [...] } — both keys are optional but the top-level value must be an object.",
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
prettySummary: "jobs apply failed (VALIDATION_ERROR): --answers-file shape is not a JSON object.",
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
const record = payload;
|
|
310
|
+
const result = {};
|
|
311
|
+
if (record["matcherAnswers"] !== undefined) {
|
|
312
|
+
result.matcherAnswers = validateRecoveredOrExit(record["matcherAnswers"], MATCHER_ANSWERS_ARRAY_SCHEMA, "answers-file:matcherAnswers", format);
|
|
313
|
+
}
|
|
314
|
+
if (record["expertiseAnswers"] !== undefined) {
|
|
315
|
+
result.expertiseAnswers = validateRecoveredOrExit(record["expertiseAnswers"], EXPERTISE_ANSWERS_ARRAY_SCHEMA, "answers-file:expertiseAnswers", format);
|
|
316
|
+
}
|
|
317
|
+
return result;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Narrow the parsed JSON from `--pitch-file` into the `pitchData`
|
|
321
|
+
* shape. Stage-2 (#438): the inner shape is validated against the
|
|
322
|
+
* recovered `PitchInput` Zod schema (wrapped with `.strict()` so extra
|
|
323
|
+
* unknown keys reject with a field-path error). Wrong top-level shape
|
|
324
|
+
* (array / string / null) still surfaces as a CLI-level
|
|
325
|
+
* `VALIDATION_ERROR` envelope BEFORE the recovered-schema parse runs.
|
|
326
|
+
*
|
|
327
|
+
* Sibling to `narrowPitchPayload` in `applications/confirm.ts`.
|
|
328
|
+
*/
|
|
329
|
+
function narrowPitchPayload(payload, format) {
|
|
330
|
+
if (payload === null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
331
|
+
emitErrorAndExit({
|
|
332
|
+
operation: "jobs.apply",
|
|
333
|
+
format,
|
|
334
|
+
errors: [
|
|
335
|
+
{
|
|
336
|
+
code: "VALIDATION_ERROR",
|
|
337
|
+
message: "--pitch-file: expected a JSON object matching the `PitchInput` shape.",
|
|
338
|
+
hint: "File shape: a JSON object matching the `PitchInput` schema (see `packages/core/src/__generated__/zod-schemas.ts` § PitchInputSchema).",
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
prettySummary: "jobs apply failed (VALIDATION_ERROR): --pitch-file shape is not a JSON object.",
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return validateRecoveredOrExit(payload, STRICT_PITCH_INPUT_SCHEMA, "pitch-file", format);
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Wrapper around {@link parseAsRecovered} that maps the typed
|
|
348
|
+
* `SCHEMA_ERROR` from {@link JsonInputError} to the structured
|
|
349
|
+
* `VALIDATION_ERROR` envelope this command emits. Mirrors
|
|
350
|
+
* {@link loadJsonInputOrExit} — failure path is uniform across all
|
|
351
|
+
* input-validation errors (parse, syntax, schema).
|
|
352
|
+
*/
|
|
353
|
+
function validateRecoveredOrExit(value, schema, flagName, format) {
|
|
354
|
+
try {
|
|
355
|
+
return parseAsRecovered(value, schema, flagName);
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
if (err instanceof JsonInputError) {
|
|
359
|
+
emitErrorAndExit({
|
|
360
|
+
operation: "jobs.apply",
|
|
361
|
+
format,
|
|
362
|
+
errors: [
|
|
363
|
+
{
|
|
364
|
+
code: "VALIDATION_ERROR",
|
|
365
|
+
message: err.message,
|
|
366
|
+
hint: hintForJsonInputCode(err.code),
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
prettySummary: `jobs apply failed (VALIDATION_ERROR): ${err.message}`,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
throw err;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Render the post-apply `JobApplicationRecord` as the indented entity
|
|
377
|
+
* preview inside the success-update envelope's pretty block. Pure —
|
|
378
|
+
* directly unit-testable.
|
|
379
|
+
*
|
|
380
|
+
* Fields surfaced: status (value + verbose), requested rate (when
|
|
381
|
+
* present), the wrapping activity-item id so the user can chain
|
|
382
|
+
* `applications show <activity-item-id>` to view the new row.
|
|
383
|
+
*/
|
|
384
|
+
export function formatJobApplicationRecord(result) {
|
|
385
|
+
const lines = [];
|
|
386
|
+
lines.push(`Application: ${result.id}`);
|
|
387
|
+
lines.push(`Status: ${result.statusV2.verbose} (${result.statusV2.value})`);
|
|
388
|
+
if (result.requestedHourlyRate !== null) {
|
|
389
|
+
lines.push(`Rate: ${result.requestedHourlyRate.decimal}`);
|
|
390
|
+
}
|
|
391
|
+
lines.push(`Activity item: ${result.jobActivityItemId}`);
|
|
392
|
+
lines.push(`(View: ttctl applications show ${result.jobActivityItemId})`);
|
|
393
|
+
return lines.join("\n");
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Render the `--show-questions` projection as a sectioned multi-line
|
|
397
|
+
* block. Surfaces apply readiness (canApply, applyErrors,
|
|
398
|
+
* suggestedRate) AND the question inventories so the user can decide
|
|
399
|
+
* whether to apply AND author the answers-file payload in one glance.
|
|
400
|
+
*
|
|
401
|
+
* Each question renders as ` • <identifier>: <prompt>` (mirrors the
|
|
402
|
+
* `jobs show --with-questions` / #437 format). Empty inventories
|
|
403
|
+
* surface the zero count in the section header so the user reads
|
|
404
|
+
* "Toptal returned an empty inventory" rather than "the CLI silently
|
|
405
|
+
* dropped the section". Pure — directly unit-testable.
|
|
406
|
+
*/
|
|
407
|
+
export function formatShowQuestions(projection) {
|
|
408
|
+
const lines = [];
|
|
409
|
+
lines.push(`Job ${projection.jobId} — Apply Preview`);
|
|
410
|
+
lines.push("");
|
|
411
|
+
lines.push(`Can apply: ${projection.canApply ? "yes" : "no"}`);
|
|
412
|
+
if (projection.applyErrors.length > 0) {
|
|
413
|
+
lines.push("Apply errors:");
|
|
414
|
+
for (const err of projection.applyErrors) {
|
|
415
|
+
lines.push(` • ${err.code}: ${err.message}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (projection.suggestedRate !== null) {
|
|
419
|
+
lines.push(`Suggested rate: ${projection.suggestedRate}`);
|
|
420
|
+
}
|
|
421
|
+
if (projection.rateValidation !== null) {
|
|
422
|
+
lines.push(`Rate bounds: min=${projection.rateValidation.minRate}, step=${projection.rateValidation.rateStep.toString()}`);
|
|
423
|
+
}
|
|
424
|
+
lines.push("");
|
|
425
|
+
lines.push(`Matcher Questions (${projection.matcherQuestions.length.toString()})`);
|
|
426
|
+
for (const q of projection.matcherQuestions) {
|
|
427
|
+
lines.push(formatQuestionEntry(q));
|
|
428
|
+
}
|
|
429
|
+
lines.push("");
|
|
430
|
+
lines.push(`Expertise Questions (${projection.expertiseQuestions.length.toString()})`);
|
|
431
|
+
for (const q of projection.expertiseQuestions) {
|
|
432
|
+
lines.push(formatQuestionEntry(q));
|
|
433
|
+
}
|
|
434
|
+
// Suggestion section (#452) — only rendered when --suggest-answers
|
|
435
|
+
// was passed AND the fetch succeeded. Per AC scenario "Opt-in with
|
|
436
|
+
// past similar answers — suggestions displayed".
|
|
437
|
+
if (projection.suggestions !== undefined) {
|
|
438
|
+
lines.push("");
|
|
439
|
+
lines.push(`Suggestions (${projection.suggestions.length.toString()} question(s))`);
|
|
440
|
+
if (projection.suggestions.length === 0) {
|
|
441
|
+
lines.push(" (no suggestions — neither matcher nor expertise questions had similar-job history)");
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
for (const group of projection.suggestions) {
|
|
445
|
+
lines.push(formatSuggestionGroup(group));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return lines.join("\n");
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Render the apply-success envelope's pretty body when
|
|
453
|
+
* `--suggest-answers` was passed alongside the actual apply (#452).
|
|
454
|
+
* Combines the post-apply `JobApplicationRecord` section with the
|
|
455
|
+
* advisory suggestion inventory beneath it; the suggestions are
|
|
456
|
+
* displayed AFTER the apply confirmation so the user reads "apply
|
|
457
|
+
* succeeded" first and then sees what they could have used.
|
|
458
|
+
*
|
|
459
|
+
* Pure — directly unit-testable.
|
|
460
|
+
*/
|
|
461
|
+
export function formatApplyWithSuggestions(projection) {
|
|
462
|
+
const lines = [];
|
|
463
|
+
lines.push(formatJobApplicationRecord(projection.application));
|
|
464
|
+
lines.push("");
|
|
465
|
+
lines.push(`Suggestions (${projection.suggestions.length.toString()} question(s))`);
|
|
466
|
+
if (projection.suggestions.length === 0) {
|
|
467
|
+
lines.push(" (no suggestions returned — your account may have no similar-job history)");
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
for (const group of projection.suggestions) {
|
|
471
|
+
lines.push(formatSuggestionGroup(group));
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return lines.join("\n");
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Render one {@link applications.SimilarJobAnswerGroup} as an indented
|
|
478
|
+
* sub-block: the question identifier header followed by each
|
|
479
|
+
* historical answer as ` - "<answer>" (<createdAt>)`. Empty
|
|
480
|
+
* `suggestions` arrays surface the per-question header + an explicit
|
|
481
|
+
* `(none)` indicator so the user sees "this question was checked, no
|
|
482
|
+
* matches" rather than "the question vanished from the inventory".
|
|
483
|
+
*
|
|
484
|
+
* Long answer strings are NOT truncated — the talent wrote them, and
|
|
485
|
+
* truncating obscures the value. JSON output carries the full payload
|
|
486
|
+
* regardless.
|
|
487
|
+
*/
|
|
488
|
+
function formatSuggestionGroup(group) {
|
|
489
|
+
const lines = [];
|
|
490
|
+
lines.push(` • ${group.questionId} (${group.suggestions.length.toString()} suggestion(s))`);
|
|
491
|
+
if (group.suggestions.length === 0) {
|
|
492
|
+
lines.push(" (none)");
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
for (const s of group.suggestions) {
|
|
496
|
+
lines.push(` - "${s.answer}" (${s.createdAt})`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return lines.join("\n");
|
|
500
|
+
}
|
|
501
|
+
function formatQuestionEntry(q) {
|
|
502
|
+
const tail = q.prompt === "" ? "" : ` ${q.prompt}`;
|
|
503
|
+
return ` • ${q.identifier}:${tail}`;
|
|
504
|
+
}
|
|
505
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../../src/commands/jobs/apply.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
/**
|
|
3
|
-
* Build the `ttctl jobs` command tree (#148
|
|
4
|
-
* across the top-level group and one nested
|
|
3
|
+
* Build the `ttctl jobs` command tree (#148; `apply` added in #430).
|
|
4
|
+
* Surfaces eleven verbs across the top-level group and one nested
|
|
5
|
+
* sub-group (`search`):
|
|
5
6
|
*
|
|
6
7
|
* | Leaf | Description |
|
|
7
8
|
* |-------------------------------------------------------|--------------------------------------------|
|
|
8
9
|
* | `list [filters]` | Browse current job opportunities |
|
|
9
10
|
* | `show <id>` | Job detail view |
|
|
11
|
+
* | `apply <id> --consent [...]` | Direct-apply to a job (DESTRUCTIVE — see ADR-008) |
|
|
10
12
|
* | `save <id>` | Mark a job as saved (bookmark) |
|
|
11
13
|
* | `unsave <id>` | Clear interest flags (the wire's only unsave path; also clears not-interested) |
|
|
12
14
|
* | `saved` | List saved jobs |
|
|
@@ -44,9 +46,21 @@ import { Command } from "commander";
|
|
|
44
46
|
* integers). All four share `JobsListResponse` and the
|
|
45
47
|
* `eligibleJobs(page, pageSize)` wire path.
|
|
46
48
|
*
|
|
47
|
-
* **
|
|
48
|
-
*
|
|
49
|
-
*
|
|
49
|
+
* **Application funnel write-side** (ADR-008, #430): `jobs apply <id>`
|
|
50
|
+
* is the user-facing direct-apply verb; the underlying service module
|
|
51
|
+
* is `applications.apply()` per ADR-008 § Decision Part 5 (the verb
|
|
52
|
+
* lives on `jobs` for readability while the funnel-crossing
|
|
53
|
+
* implementation lives on `applications`). The relaxation of the #15
|
|
54
|
+
* read-only stance is tracked in
|
|
55
|
+
* `hq/engineering/adr/ADR-008-application-funnel-write-side.md`.
|
|
56
|
+
*
|
|
57
|
+
* **Still out of scope** (per #148 + ADR-008 § What We're NOT Solving):
|
|
58
|
+
* - `JobApplication.withdraw` / `JobApplication.edit` — separate
|
|
59
|
+
* scope; Toptal support is uncertain.
|
|
60
|
+
* - Bulk-apply / bulk-save / bulk-dismiss (single-id only; matches
|
|
61
|
+
* the safety boundary established by #411 for IR ops).
|
|
62
|
+
* - Interview accept / reject — separate scope, separate catalog
|
|
63
|
+
* (`InterviewRejectReason`).
|
|
50
64
|
*/
|
|
51
65
|
export declare function buildJobsCommand(): Command;
|
|
52
66
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/jobs/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAgC,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/jobs/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAgC,MAAM,WAAW,CAAC;AAkClE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAyX1C"}
|