@wport/cli 0.9.0 → 0.9.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/dist/index.js CHANGED
@@ -27,6 +27,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
27
  var import_commander = require("commander");
28
28
 
29
29
  // src/lib/errors.ts
30
+ var import_core = require("@wport/core");
30
31
  var ExitCode = {
31
32
  Success: 0,
32
33
  InvalidArgument: 2,
@@ -48,27 +49,19 @@ var InvalidArgumentError = class extends CliError {
48
49
  this.name = "InvalidArgumentError";
49
50
  }
50
51
  };
51
- var ServerClientHttpError = class extends CliError {
52
- status;
53
- body;
54
- constructor(message, status, body) {
55
- super(message, ExitCode.ServerClientError);
56
- this.name = "ServerClientHttpError";
57
- this.status = status;
58
- this.body = body;
59
- }
60
- };
61
- var NetworkError = class extends CliError {
62
- cause;
63
- constructor(message, cause) {
64
- super(message, ExitCode.ServerOrNetworkError);
65
- this.name = "NetworkError";
66
- this.cause = cause;
67
- }
68
- };
69
52
  function isCliError(err) {
70
53
  return err instanceof CliError;
71
54
  }
55
+ function exitCodeForError(err) {
56
+ if (err instanceof import_core.WportInvalidArgumentError) return ExitCode.InvalidArgument;
57
+ if (err instanceof import_core.WportHttpError) {
58
+ return err.status >= 400 && err.status < 500 ? ExitCode.ServerClientError : ExitCode.ServerOrNetworkError;
59
+ }
60
+ if (err instanceof import_core.WportNetworkError) return ExitCode.ServerOrNetworkError;
61
+ return ExitCode.ServerOrNetworkError;
62
+ }
63
+ var ServerClientHttpError = import_core.WportHttpError;
64
+ var NetworkError = import_core.WportNetworkError;
72
65
 
73
66
  // src/lib/output.ts
74
67
  var import_cli_table3 = __toESM(require("cli-table3"));
@@ -146,96 +139,7 @@ function dim(text, color) {
146
139
 
147
140
  // src/commands/jobs/search.ts
148
141
  var import_node_fs2 = require("fs");
149
-
150
- // src/lib/api-client.ts
151
- var import_openapi_fetch = __toESM(require("openapi-fetch"));
152
- function createApiClient(opts) {
153
- return (0, import_openapi_fetch.default)({
154
- baseUrl: opts.baseUrl,
155
- headers: {
156
- "Accept-Language": opts.locale,
157
- "User-Agent": buildUserAgent(),
158
- Accept: "application/json"
159
- },
160
- fetch: (request) => fetchWithTimeout(request, opts.timeoutMs)
161
- });
162
- }
163
- function fetchWithTimeout(request, timeoutMs) {
164
- const timedRequest = new Request(request, { signal: AbortSignal.timeout(timeoutMs) });
165
- return fetch(timedRequest).catch((err) => {
166
- if (isTimeoutAbort(err)) {
167
- throw new NetworkError(`Request timed out after ${timeoutMs}ms`, err);
168
- }
169
- const code = err?.cause?.code;
170
- const detail = code ? code : err?.message ?? String(err);
171
- throw new NetworkError(`Cannot reach upstream: ${detail}`, err);
172
- });
173
- }
174
- function isTimeoutAbort(err) {
175
- if (err && typeof err === "object" && "name" in err) {
176
- const name = err.name;
177
- return name === "TimeoutError" || name === "AbortError";
178
- }
179
- return false;
180
- }
181
- function buildUserAgent() {
182
- return `wport-cli/${"0.9.0"} (node ${process.version}; ${process.platform})`;
183
- }
184
- function unwrapDataResponse(body) {
185
- if (body && typeof body === "object" && "success" in body && "data" in body) {
186
- return body.data;
187
- }
188
- throw new CliError("Unexpected response shape: missing { success, data } wrapper", ExitCode.ServerOrNetworkError);
189
- }
190
- function unwrapDataArray(body) {
191
- const data = unwrapDataResponse(body);
192
- if (!Array.isArray(data)) {
193
- throw new CliError("Unexpected response shape: `data` is not an array", ExitCode.ServerOrNetworkError);
194
- }
195
- return data;
196
- }
197
- function asPaginatedBody(body) {
198
- if (!body || typeof body !== "object") {
199
- throw new CliError("Unexpected response shape: not an object", ExitCode.ServerOrNetworkError);
200
- }
201
- const b = body;
202
- if (!Array.isArray(b.data)) {
203
- throw new CliError("Unexpected response shape: missing `data` array", ExitCode.ServerOrNetworkError);
204
- }
205
- for (const key of ["currentPage", "totalPages", "pageSize", "totalCount"]) {
206
- if (typeof b[key] !== "number") {
207
- throw new CliError(`Unexpected response shape: missing or non-numeric "${key}"`, ExitCode.ServerOrNetworkError);
208
- }
209
- }
210
- return body;
211
- }
212
- function throwForHttpStatus(status, body) {
213
- const message = extractErrorMessage(body) ?? `HTTP ${status}`;
214
- if (status >= 400 && status < 500) {
215
- throw new ServerClientHttpError(message, status, body);
216
- }
217
- throw new CliError(message, ExitCode.ServerOrNetworkError);
218
- }
219
- function extractErrorMessage(body) {
220
- if (typeof body === "string") return body;
221
- if (body && typeof body === "object") {
222
- const obj = body;
223
- if (Array.isArray(obj.message)) {
224
- const parts = obj.message.filter((m) => typeof m === "string");
225
- if (parts.length > 0) return parts.join("; ");
226
- }
227
- if (typeof obj.message === "string") return obj.message;
228
- if (obj.message && typeof obj.message === "object") {
229
- try {
230
- return JSON.stringify(obj.message);
231
- } catch {
232
- }
233
- }
234
- if (typeof obj.path === "string") return obj.path;
235
- if (typeof obj.error === "string") return obj.error;
236
- }
237
- return null;
238
- }
142
+ var import_core2 = require("@wport/core");
239
143
 
240
144
  // src/lib/config-store.ts
241
145
  var import_node_fs = require("fs");
@@ -366,8 +270,26 @@ function validateAndCoerce(key, value) {
366
270
  }
367
271
  }
368
272
 
273
+ // src/lib/channel.ts
274
+ var CHANNEL_BASE_URL = {
275
+ prod: "https://api.wport.me",
276
+ dev: "https://developers.wport.me/v2"
277
+ };
278
+ function currentChannel() {
279
+ return true ? "prod" : "prod";
280
+ }
281
+ function channelBaseUrl(channel) {
282
+ return CHANNEL_BASE_URL[channel] ?? CHANNEL_BASE_URL.prod;
283
+ }
284
+ function channelBanner() {
285
+ const channel = currentChannel();
286
+ if (channel === "prod") return null;
287
+ return `[${channel}] targeting the dev backend
288
+ `;
289
+ }
290
+
369
291
  // src/lib/global-opts.ts
370
- var DEFAULT_BASE_URL = "https://api.wport.me";
292
+ var DEFAULT_BASE_URL = channelBaseUrl(currentChannel());
371
293
  var API_BASE_ENV_VAR = "WPORT_API_BASE";
372
294
  var DEFAULT_LOCALE = "zh-TW";
373
295
  var DEFAULT_TIMEOUT_MS = 1e4;
@@ -460,16 +382,17 @@ function registerJobsSearch(parent) {
460
382
  const ctx = resolveContext(command);
461
383
  const fields = resolveSearchFields(flags);
462
384
  const query = buildQuery(flags);
463
- const client = createApiClient({
385
+ const client = (0, import_core2.createApiClient)({
464
386
  baseUrl: ctx.baseUrl,
465
387
  locale: ctx.locale,
466
- timeoutMs: ctx.timeoutMs
388
+ timeoutMs: ctx.timeoutMs,
389
+ userAgent: (0, import_core2.buildUserAgent)("wport-cli", "0.9.1")
467
390
  });
468
391
  const { data, error, response } = await client.GET("/api/jobs/search", {
469
392
  params: { query }
470
393
  });
471
- if (!response.ok) throwForHttpStatus(response.status, error);
472
- const paged = asPaginatedBody(data);
394
+ if (!response.ok) (0, import_core2.throwForHttpStatus)(response.status, error);
395
+ const paged = (0, import_core2.asPaginatedBody)(data);
473
396
  if (ctx.format === "json") {
474
397
  const body = fields ? { ...paged, data: paged.data.map((item) => pickPaths(item, fields)) } : paged;
475
398
  printJson(body);
@@ -546,21 +469,9 @@ function formatDate(s) {
546
469
  return m ? m[1] : s;
547
470
  }
548
471
 
549
- // src/lib/concurrency.ts
550
- async function mapWithConcurrency(items, limit, fn) {
551
- const results = new Array(items.length);
552
- let cursor = 0;
553
- async function worker() {
554
- for (; ; ) {
555
- const index = cursor++;
556
- if (index >= items.length) return;
557
- results[index] = await fn(items[index], index);
558
- }
559
- }
560
- const workerCount = Math.min(Math.max(1, limit), items.length);
561
- await Promise.all(Array.from({ length: workerCount }, () => worker()));
562
- return results;
563
- }
472
+ // src/commands/jobs/view.ts
473
+ var import_core3 = require("@wport/core");
474
+ var import_core4 = require("@wport/core");
564
475
 
565
476
  // src/lib/io-helpers.ts
566
477
  var import_node_fs3 = require("fs");
@@ -715,10 +626,11 @@ function registerJobsView(parent) {
715
626
  ExitCode.InvalidArgument
716
627
  );
717
628
  }
718
- const client = createApiClient({
629
+ const client = (0, import_core3.createApiClient)({
719
630
  baseUrl: ctx.baseUrl,
720
631
  locale: ctx.locale,
721
- timeoutMs: ctx.timeoutMs
632
+ timeoutMs: ctx.timeoutMs,
633
+ userAgent: (0, import_core3.buildUserAgent)("wport-cli", "0.9.1")
722
634
  });
723
635
  if (flags.batch) {
724
636
  await runBatchView(encIdArg, flags, client, ctx.timeoutMs);
@@ -731,8 +643,8 @@ function registerJobsView(parent) {
731
643
  const { data, error, response } = await client.GET("/api/jobs/{encId}/view", {
732
644
  params: { path: { encId } }
733
645
  });
734
- if (!response.ok) throwForHttpStatus(response.status, error);
735
- const job = unwrapDataResponse(data);
646
+ if (!response.ok) (0, import_core3.throwForHttpStatus)(response.status, error);
647
+ const job = (0, import_core3.unwrapDataResponse)(data);
736
648
  if (flags.fields) {
737
649
  printJson(pickPaths(job, parseFieldsList(flags.fields)));
738
650
  return;
@@ -770,11 +682,11 @@ async function fetchJob(client, encId) {
770
682
  const { data, error, response } = await client.GET("/api/jobs/{encId}/view", {
771
683
  params: { path: { encId } }
772
684
  });
773
- if (!response.ok) throwForHttpStatus(response.status, error);
774
- return unwrapDataResponse(data);
685
+ if (!response.ok) (0, import_core3.throwForHttpStatus)(response.status, error);
686
+ return (0, import_core3.unwrapDataResponse)(data);
775
687
  }
776
688
  async function runBatch(encIds, concurrency, fetchOne, project) {
777
- return mapWithConcurrency(encIds, concurrency, async (encId) => {
689
+ return (0, import_core4.mapWithConcurrency)(encIds, concurrency, async (encId) => {
778
690
  try {
779
691
  const job = await fetchOne(encId);
780
692
  return { enc_id: encId, ok: true, data: project(job) };
@@ -971,6 +883,7 @@ function registerConfigCommand(program2) {
971
883
 
972
884
  // src/commands/doctor.ts
973
885
  var import_node_fs6 = require("fs");
886
+ var import_core7 = require("@wport/core");
974
887
 
975
888
  // src/lib/credentials-store.ts
976
889
  var import_node_fs5 = require("fs");
@@ -981,7 +894,8 @@ var KEY_PREFIX = "wpk_live_";
981
894
  var KEY_MIN_LENGTH = KEY_PREFIX.length + 32;
982
895
  var paths2 = (0, import_env_paths2.default)("wport", { suffix: "" });
983
896
  function getCredentialsPath() {
984
- return (0, import_node_path2.join)(paths2.config, "credentials.json");
897
+ const fileName = currentChannel() === "dev" ? "credentials-dev.json" : "credentials.json";
898
+ return (0, import_node_path2.join)(paths2.config, fileName);
985
899
  }
986
900
  function isValidKeyFormat(key) {
987
901
  return key.startsWith(KEY_PREFIX) && key.length >= KEY_MIN_LENGTH && !/\s/.test(key);
@@ -1146,24 +1060,9 @@ function ensureFormat(key, source) {
1146
1060
  }
1147
1061
  }
1148
1062
 
1149
- // src/lib/personal-types.ts
1150
- var PERSONAL_RESUMES_BASE = "/api/v1/personal/resumes";
1151
- var PERSONAL_APPLICATIONS_BASE = "/api/v1/personal/applications";
1152
- var OAUTH_BASE = "/api/oauth";
1153
- var OAUTH_SESSIONS_BASE = `${OAUTH_BASE}/sessions`;
1154
- var SECTION_WRITE_PLAN = {
1155
- education: { kind: "per-item-post", path: "/education" },
1156
- work_experience: { kind: "work-experience", path: "/work-experience" },
1157
- certificate: { kind: "per-item-post", path: "/certificate" },
1158
- language: { kind: "per-item-post", path: "/language" },
1159
- professional_skills: { kind: "single-post", path: "/professional-skills" },
1160
- autobiography: { kind: "single-post", path: "/autobiography" },
1161
- job_condition: { kind: "single-post", path: "/job-condition" },
1162
- portfolio_links: { kind: "bulk-put", path: "/portfolio-links" },
1163
- background: { kind: "single-post", path: "/background" }
1164
- };
1165
-
1166
1063
  // src/lib/oauth.ts
1064
+ var import_core5 = require("@wport/core");
1065
+ var import_core6 = require("@wport/core");
1167
1066
  var EXPIRED_MESSAGE = "The device code expired before authorization completed. Run `wport login` to try again.";
1168
1067
  async function oauthPost(opts, path, body) {
1169
1068
  const url = new URL(`${opts.baseUrl}${path}`);
@@ -1171,13 +1070,13 @@ async function oauthPost(opts, path, body) {
1171
1070
  method: "POST",
1172
1071
  headers: {
1173
1072
  "Accept-Language": opts.locale,
1174
- "User-Agent": buildUserAgent(),
1073
+ "User-Agent": (0, import_core5.buildUserAgent)("wport-cli", "0.9.1"),
1175
1074
  Accept: "application/json",
1176
1075
  "Content-Type": "application/json"
1177
1076
  },
1178
1077
  body: JSON.stringify(body)
1179
1078
  });
1180
- const res = await fetchWithTimeout(request, opts.timeoutMs);
1079
+ const res = await (0, import_core5.fetchWithTimeout)(request, opts.timeoutMs);
1181
1080
  const respBody = await res.json().catch(() => null);
1182
1081
  return { status: res.status, body: respBody };
1183
1082
  }
@@ -1190,15 +1089,15 @@ function oauthErrorCode(body) {
1190
1089
  async function requestDeviceCode(opts, deviceName) {
1191
1090
  const body = {};
1192
1091
  if (deviceName) body.device_name = deviceName;
1193
- const { status, body: respBody } = await oauthPost(opts, `${OAUTH_BASE}/device/code`, body);
1194
- if (status < 200 || status >= 300) throwForHttpStatus(status, respBody);
1092
+ const { status, body: respBody } = await oauthPost(opts, `${import_core6.OAUTH_BASE}/device/code`, body);
1093
+ if (status < 200 || status >= 300) (0, import_core5.throwForHttpStatus)(status, respBody);
1195
1094
  return respBody;
1196
1095
  }
1197
1096
  async function pollForToken(opts, device, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
1198
1097
  let intervalSec = device.interval;
1199
1098
  const deadline = Date.now() + device.expires_in * 1e3;
1200
1099
  for (; ; ) {
1201
- const { status, body } = await oauthPost(opts, `${OAUTH_BASE}/token`, {
1100
+ const { status, body } = await oauthPost(opts, `${import_core6.OAUTH_BASE}/token`, {
1202
1101
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
1203
1102
  device_code: device.device_code
1204
1103
  });
@@ -1211,14 +1110,14 @@ async function pollForToken(opts, device, sleep = (ms) => new Promise((resolve)
1211
1110
  } else if (code === "expired_token") {
1212
1111
  throw new CliError(EXPIRED_MESSAGE, ExitCode.ServerClientError);
1213
1112
  } else if (code !== "authorization_pending") {
1214
- throwForHttpStatus(status, body);
1113
+ (0, import_core5.throwForHttpStatus)(status, body);
1215
1114
  }
1216
1115
  if (Date.now() >= deadline) throw new CliError(EXPIRED_MESSAGE, ExitCode.ServerClientError);
1217
1116
  await sleep(intervalSec * 1e3);
1218
1117
  }
1219
1118
  }
1220
1119
  async function refreshAccessToken(opts, refreshToken) {
1221
- const { status, body } = await oauthPost(opts, `${OAUTH_BASE}/token`, {
1120
+ const { status, body } = await oauthPost(opts, `${import_core6.OAUTH_BASE}/token`, {
1222
1121
  grant_type: "refresh_token",
1223
1122
  refresh_token: refreshToken
1224
1123
  });
@@ -1226,11 +1125,11 @@ async function refreshAccessToken(opts, refreshToken) {
1226
1125
  if (oauthErrorCode(body) === "invalid_grant") {
1227
1126
  throw new CliError("Your session is no longer valid. Run `wport login` to sign in again.", ExitCode.ServerClientError);
1228
1127
  }
1229
- throwForHttpStatus(status, body);
1128
+ (0, import_core5.throwForHttpStatus)(status, body);
1230
1129
  }
1231
1130
  async function revokeRefreshToken(opts, refreshToken) {
1232
- const { status, body } = await oauthPost(opts, `${OAUTH_BASE}/revoke`, { token: refreshToken });
1233
- if (status < 200 || status >= 300) throwForHttpStatus(status, body);
1131
+ const { status, body } = await oauthPost(opts, `${import_core6.OAUTH_BASE}/revoke`, { token: refreshToken });
1132
+ if (status < 200 || status >= 300) (0, import_core5.throwForHttpStatus)(status, body);
1234
1133
  }
1235
1134
 
1236
1135
  // src/commands/doctor.ts
@@ -1253,11 +1152,12 @@ function registerDoctorCommand(program2) {
1253
1152
  }
1254
1153
  async function runDoctor(ctx) {
1255
1154
  const line = (s = "") => process.stdout.write(s + "\n");
1256
- line(`wport-cli ${"0.9.0"}`);
1155
+ line(`wport-cli ${"0.9.1"}`);
1257
1156
  line(` bundled schema fingerprint: ${"839e8a891dfb"}`);
1258
1157
  line("");
1259
1158
  line("Resolved configuration:");
1260
1159
  line(` API base URL: ${ctx.baseUrl}`);
1160
+ line(` channel: ${currentChannel()}`);
1261
1161
  line(` locale: ${ctx.locale}`);
1262
1162
  line(` timeout: ${ctx.timeoutMs}ms`);
1263
1163
  const cfgPath = getConfigPath();
@@ -1308,7 +1208,12 @@ function describePersonalLoginLines() {
1308
1208
  }
1309
1209
  async function probeServer(ctx, line) {
1310
1210
  try {
1311
- const client = createApiClient({ baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs });
1211
+ const client = (0, import_core7.createApiClient)({
1212
+ baseUrl: ctx.baseUrl,
1213
+ locale: ctx.locale,
1214
+ timeoutMs: ctx.timeoutMs,
1215
+ userAgent: (0, import_core7.buildUserAgent)("wport-cli", "0.9.1")
1216
+ });
1312
1217
  const { response } = await client.GET("/api/jobs/search", { params: { query: { pageSize: 1 } } });
1313
1218
  if (response.ok) {
1314
1219
  line(` \u2713 reachable (HTTP ${response.status})`);
@@ -1333,81 +1238,39 @@ async function probeAuthServer(ctx, line) {
1333
1238
  }
1334
1239
 
1335
1240
  // src/lib/enterprise-client.ts
1336
- var ENTERPRISE_PREFIX = "/api/v1/enterprise";
1337
- async function enterpriseGet(opts, path, query) {
1338
- const url = new URL(`${opts.baseUrl}${ENTERPRISE_PREFIX}${path}`);
1339
- for (const [k, v] of Object.entries(query ?? {})) {
1340
- if (v !== void 0) url.searchParams.set(k, String(v));
1341
- }
1342
- const request = new Request(url, {
1343
- headers: {
1344
- Authorization: `Bearer ${opts.apiKey}`,
1345
- "Accept-Language": opts.locale,
1346
- "User-Agent": buildUserAgent(),
1347
- Accept: "application/json"
1348
- }
1349
- });
1350
- const res = await fetchWithTimeout(request, opts.timeoutMs);
1351
- const body = await res.json().catch(() => null);
1352
- if (!res.ok) throwEnterpriseHttpError(res.status, body);
1353
- warnIfRateLimitLow(res.headers);
1354
- return { body, headers: res.headers };
1355
- }
1356
- async function enterpriseWrite(method, opts, path, body, extra) {
1357
- const url = new URL(`${opts.baseUrl}${ENTERPRISE_PREFIX}${path}`);
1358
- const headers = {
1359
- Authorization: `Bearer ${opts.apiKey}`,
1360
- "Accept-Language": opts.locale,
1361
- "User-Agent": buildUserAgent(),
1362
- Accept: "application/json"
1363
- };
1364
- if (body !== void 0) headers["Content-Type"] = "application/json";
1365
- if (extra?.idempotencyKey) headers["Idempotency-Key"] = extra.idempotencyKey;
1366
- if (extra?.ifMatch) headers["If-Match"] = extra.ifMatch;
1367
- const request = new Request(url, {
1368
- method,
1369
- headers,
1370
- body: body !== void 0 ? JSON.stringify(body) : void 0
1371
- });
1372
- const res = await fetchWithTimeout(request, opts.timeoutMs);
1373
- const respBody = await res.json().catch(() => null);
1374
- if (!res.ok) throwEnterpriseHttpError(res.status, respBody);
1375
- warnIfRateLimitLow(res.headers);
1376
- return { body: respBody, headers: res.headers };
1377
- }
1378
- function enterprisePost(opts, path, body, extra) {
1379
- return enterpriseWrite("POST", opts, path, body, extra);
1380
- }
1381
- function enterprisePatch(opts, path, body, extra) {
1382
- return enterpriseWrite("PATCH", opts, path, body, extra);
1383
- }
1384
- function enterpriseDelete(opts, path, extra) {
1385
- return enterpriseWrite("DELETE", opts, path, void 0, extra);
1386
- }
1387
- function throwEnterpriseHttpError(status, body) {
1388
- const base = extractErrorMessage(body) ?? `HTTP ${status}`;
1389
- if (status === 401) {
1390
- throw new CliError(
1241
+ var import_core8 = require("@wport/core");
1242
+ var transport = __toESM(require("@wport/core"));
1243
+ function withUserAgent(opts) {
1244
+ return { ...opts, userAgent: (0, import_core8.buildUserAgent)("wport-cli", "0.9.1") };
1245
+ }
1246
+ function decorateEnterpriseError(err) {
1247
+ if (!(err instanceof import_core8.WportHttpError)) return err;
1248
+ const base = err.message;
1249
+ if (err.status === 401) {
1250
+ return new import_core8.WportHttpError(
1391
1251
  `${base} \u2014 If your key has expired, rotate it in place: \`wport enterprise keys rotate <enc_id>\` (an expired key is still accepted for rotate). If it was revoked or is incorrect, obtain a valid key and run \`wport enterprise login\`.`,
1392
- ExitCode.ServerClientError
1252
+ err.status,
1253
+ err.body
1393
1254
  );
1394
1255
  }
1395
- if (status === 403) {
1396
- throw new CliError(
1256
+ if (err.status === 403) {
1257
+ return new import_core8.WportHttpError(
1397
1258
  `${base} \u2014 Your key may lack the required scope; rotate or issue a key that includes it. If your company account has been suspended, please contact support.`,
1398
- ExitCode.ServerClientError
1259
+ err.status,
1260
+ err.body
1399
1261
  );
1400
1262
  }
1401
- if (status === 400) {
1402
- const missingFields = extractMissingFields(body);
1263
+ if (err.status === 400) {
1264
+ const missingFields = extractMissingFields(err.body);
1403
1265
  if (missingFields.length > 0) {
1404
- throw new CliError(
1266
+ return new import_core8.WportHttpError(
1405
1267
  `${base} \u2014 Missing required fields: ${missingFields.join(", ")}. Fill them via \`wport enterprise jobs update <enc_id> ...\` (or the web console), then publish.`,
1406
- ExitCode.ServerClientError
1268
+ err.status,
1269
+ err.body
1407
1270
  );
1408
1271
  }
1409
1272
  }
1410
- throwForHttpStatus(status, body);
1273
+ return err;
1411
1274
  }
1412
1275
  function extractMissingFields(body) {
1413
1276
  if (!body || typeof body !== "object") return [];
@@ -1424,16 +1287,40 @@ function warnIfRateLimitLow(headers) {
1424
1287
  printWarn(`Rate limit nearly exhausted: ${remaining}/${limit} requests remaining this window.`, false);
1425
1288
  }
1426
1289
  }
1290
+ async function wrap(p) {
1291
+ let res;
1292
+ try {
1293
+ res = await p;
1294
+ } catch (err) {
1295
+ throw decorateEnterpriseError(err);
1296
+ }
1297
+ warnIfRateLimitLow(res.headers);
1298
+ return res;
1299
+ }
1300
+ function enterpriseGet2(opts, path, query) {
1301
+ return wrap(transport.enterpriseGet(withUserAgent(opts), path, query));
1302
+ }
1303
+ function enterprisePost2(opts, path, body, extra) {
1304
+ return wrap(transport.enterprisePost(withUserAgent(opts), path, body, extra));
1305
+ }
1306
+ function enterprisePatch2(opts, path, body, extra) {
1307
+ return wrap(transport.enterprisePatch(withUserAgent(opts), path, body, extra));
1308
+ }
1309
+ function enterpriseDelete2(opts, path, extra) {
1310
+ return wrap(transport.enterpriseDelete(withUserAgent(opts), path, extra));
1311
+ }
1427
1312
 
1428
1313
  // src/commands/enterprise/login.ts
1429
1314
  async function performLogin(ctx, key) {
1315
+ const banner = channelBanner();
1316
+ if (banner) process.stderr.write(banner);
1430
1317
  if (!isValidKeyFormat(key)) {
1431
1318
  throw new CliError(
1432
1319
  `That does not look like a valid ${KEY_PREFIX} key. Nothing was saved.`,
1433
1320
  ExitCode.InvalidArgument
1434
1321
  );
1435
1322
  }
1436
- const { body } = await enterpriseGet({ ...ctx, apiKey: key }, "/me");
1323
+ const { body } = await enterpriseGet2({ ...ctx, apiKey: key }, "/me");
1437
1324
  saveCredentials({
1438
1325
  api_key: key,
1439
1326
  company_name: extractCompanyName(body),
@@ -1498,15 +1385,16 @@ function registerEnterpriseWhoami(parent) {
1498
1385
  }
1499
1386
 
1500
1387
  // src/commands/enterprise/usage.ts
1388
+ var import_core9 = require("@wport/core");
1501
1389
  function num(value) {
1502
1390
  return typeof value === "number" && Number.isFinite(value) ? String(value) : "\u2014";
1503
1391
  }
1504
1392
  async function runUsage(ctx, apiKey) {
1505
- const { body } = await enterpriseGet(
1393
+ const { body } = await enterpriseGet2(
1506
1394
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1507
1395
  "/usage"
1508
1396
  );
1509
- const usage = unwrapDataResponse(body);
1397
+ const usage = (0, import_core9.unwrapDataResponse)(body);
1510
1398
  if (ctx.format === "json") {
1511
1399
  printJson(usage);
1512
1400
  return;
@@ -1533,6 +1421,7 @@ function registerEnterpriseUsage(parent) {
1533
1421
  }
1534
1422
 
1535
1423
  // src/commands/enterprise/jobs/list.ts
1424
+ var import_core10 = require("@wport/core");
1536
1425
  var STATUS_MAP = { published: 1, unpublished: 0 };
1537
1426
  var MINIMAL_LIST_FIELDS = ["enc_id", "job_title", "status", "updated_at"];
1538
1427
  function mapStatusFlag(raw) {
@@ -1558,7 +1447,7 @@ async function runEnterpriseJobsList(ctx, apiKey, flags) {
1558
1447
  if (flags.fields && flags.minimal) {
1559
1448
  throw new CliError("Use either --fields or --minimal, not both", ExitCode.InvalidArgument);
1560
1449
  }
1561
- const { body } = await enterpriseGet(
1450
+ const { body } = await enterpriseGet2(
1562
1451
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1563
1452
  "/jobs",
1564
1453
  {
@@ -1568,7 +1457,7 @@ async function runEnterpriseJobsList(ctx, apiKey, flags) {
1568
1457
  status: mapStatusFlag(flags.status)
1569
1458
  }
1570
1459
  );
1571
- const paged = asPaginatedBody(body);
1460
+ const paged = (0, import_core10.asPaginatedBody)(body);
1572
1461
  const projection = flags.minimal ? MINIMAL_LIST_FIELDS : flags.fields ? parseFieldsList(flags.fields) : void 0;
1573
1462
  if (projection || ctx.format === "json") {
1574
1463
  printJson(projection ? { ...paged, data: paged.data.map((row) => pickPaths(row, projection)) } : paged);
@@ -1600,6 +1489,7 @@ function registerEnterpriseJobsList(parent) {
1600
1489
  }
1601
1490
 
1602
1491
  // src/commands/enterprise/jobs/view.ts
1492
+ var import_core11 = require("@wport/core");
1603
1493
  var DETAIL_FIELDS = ["enc_id", "job_title", "code", "status", "created_at", "updated_at"];
1604
1494
  function renderDetailLines(job) {
1605
1495
  const pad = Math.max(...DETAIL_FIELDS.map((f) => f.length)) + 1;
@@ -1620,11 +1510,11 @@ function registerEnterpriseJobsView(parent) {
1620
1510
  }
1621
1511
  const globals = command.optsWithGlobals();
1622
1512
  const { key } = resolveApiKey(globals.apiKey);
1623
- const { body } = await enterpriseGet(
1513
+ const { body } = await enterpriseGet2(
1624
1514
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey: key },
1625
1515
  `/jobs/${encodeURIComponent(encId.trim())}`
1626
1516
  );
1627
- const job = unwrapDataResponse(body);
1517
+ const job = (0, import_core11.unwrapDataResponse)(body);
1628
1518
  if (flags.fields) {
1629
1519
  printJson(pickPaths(job, parseFieldsList(flags.fields)));
1630
1520
  return;
@@ -1639,15 +1529,16 @@ function registerEnterpriseJobsView(parent) {
1639
1529
 
1640
1530
  // src/commands/enterprise/jobs/create.ts
1641
1531
  var import_node_crypto = require("crypto");
1532
+ var import_core12 = require("@wport/core");
1642
1533
  async function runJobsCreate(ctx, apiKey, source, idempotencyKey) {
1643
1534
  const jobBody = readJsonObject(source, { timeoutMs: ctx.timeoutMs });
1644
- const { body } = await enterprisePost(
1535
+ const { body } = await enterprisePost2(
1645
1536
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1646
1537
  "/jobs",
1647
1538
  jobBody,
1648
1539
  { idempotencyKey }
1649
1540
  );
1650
- const created = unwrapDataResponse(body);
1541
+ const created = (0, import_core12.unwrapDataResponse)(body);
1651
1542
  if (ctx.format === "json") {
1652
1543
  printJson(created);
1653
1544
  return;
@@ -1666,6 +1557,7 @@ function registerEnterpriseJobsCreate(parent) {
1666
1557
 
1667
1558
  // src/commands/enterprise/jobs/update.ts
1668
1559
  var import_node_crypto2 = require("crypto");
1560
+ var import_core13 = require("@wport/core");
1669
1561
 
1670
1562
  // src/commands/enterprise/jobs/write-shared.ts
1671
1563
  function requireEncId(encId) {
@@ -1678,13 +1570,13 @@ function requireEncId(encId) {
1678
1570
  async function runJobsUpdate(ctx, apiKey, encId, source, idempotencyKey, ifMatch) {
1679
1571
  const trimmed = requireEncId(encId);
1680
1572
  const jobBody = readJsonObject(source, { timeoutMs: ctx.timeoutMs });
1681
- const { body } = await enterprisePatch(
1573
+ const { body } = await enterprisePatch2(
1682
1574
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1683
1575
  `/jobs/${encodeURIComponent(trimmed)}`,
1684
1576
  jobBody,
1685
1577
  { idempotencyKey, ifMatch }
1686
1578
  );
1687
- const updated = unwrapDataResponse(body);
1579
+ const updated = (0, import_core13.unwrapDataResponse)(body);
1688
1580
  if (ctx.format === "json") {
1689
1581
  printJson(updated);
1690
1582
  return;
@@ -1703,15 +1595,16 @@ function registerEnterpriseJobsUpdate(parent) {
1703
1595
 
1704
1596
  // src/commands/enterprise/jobs/lifecycle.ts
1705
1597
  var import_node_crypto3 = require("crypto");
1598
+ var import_core14 = require("@wport/core");
1706
1599
  async function runJobsTransition(ctx, apiKey, encId, action, idempotencyKey) {
1707
1600
  const trimmed = requireEncId(encId);
1708
- const { body } = await enterprisePatch(
1601
+ const { body } = await enterprisePatch2(
1709
1602
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1710
1603
  `/jobs/${encodeURIComponent(trimmed)}/${action}`,
1711
1604
  {},
1712
1605
  { idempotencyKey }
1713
1606
  );
1714
- const result = unwrapDataResponse(body);
1607
+ const result = (0, import_core14.unwrapDataResponse)(body);
1715
1608
  if (ctx.format === "json") {
1716
1609
  printJson(result);
1717
1610
  return;
@@ -1725,7 +1618,7 @@ async function runJobsDelete(ctx, apiKey, encId, confirm, idempotencyKey) {
1725
1618
  throw new CliError("Refusing to delete without --confirm (destructive, irreversible)", ExitCode.InvalidArgument);
1726
1619
  }
1727
1620
  const trimmed = requireEncId(encId);
1728
- await enterpriseDelete(
1621
+ await enterpriseDelete2(
1729
1622
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1730
1623
  `/jobs/${encodeURIComponent(trimmed)}`,
1731
1624
  { idempotencyKey }
@@ -1739,13 +1632,13 @@ async function runJobsDelete(ctx, apiKey, encId, confirm, idempotencyKey) {
1739
1632
  }
1740
1633
  async function runJobsCopy(ctx, apiKey, encId, idempotencyKey) {
1741
1634
  const trimmed = requireEncId(encId);
1742
- const { body } = await enterprisePost(
1635
+ const { body } = await enterprisePost2(
1743
1636
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1744
1637
  `/jobs/${encodeURIComponent(trimmed)}/copy`,
1745
1638
  {},
1746
1639
  { idempotencyKey }
1747
1640
  );
1748
- const result = unwrapDataResponse(body);
1641
+ const result = (0, import_core14.unwrapDataResponse)(body);
1749
1642
  if (ctx.format === "json") {
1750
1643
  printJson(result);
1751
1644
  return;
@@ -1799,6 +1692,7 @@ function registerEnterpriseJobsDelete(parent) {
1799
1692
 
1800
1693
  // src/commands/enterprise/jobs/batch.ts
1801
1694
  var import_node_crypto4 = require("crypto");
1695
+ var import_core15 = require("@wport/core");
1802
1696
  var BATCH_MIN = 1;
1803
1697
  var BATCH_MAX = 10;
1804
1698
  async function runJobsBatch(ctx, apiKey, source, confirm, idempotencyKey) {
@@ -1813,13 +1707,13 @@ async function runJobsBatch(ctx, apiKey, source, confirm, idempotencyKey) {
1813
1707
  ExitCode.InvalidArgument
1814
1708
  );
1815
1709
  }
1816
- const { body } = await enterprisePost(
1710
+ const { body } = await enterprisePost2(
1817
1711
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1818
1712
  "/jobs/batch",
1819
1713
  payload,
1820
1714
  { idempotencyKey }
1821
1715
  );
1822
- const result = unwrapDataResponse(body);
1716
+ const result = (0, import_core15.unwrapDataResponse)(body);
1823
1717
  const succeeded = result.succeeded ?? [];
1824
1718
  const failed = result.failed ?? [];
1825
1719
  if (ctx.format === "json") {
@@ -1862,6 +1756,7 @@ function registerEnterpriseJobsCommand(parent) {
1862
1756
  }
1863
1757
 
1864
1758
  // src/commands/enterprise/keys/list.ts
1759
+ var import_core16 = require("@wport/core");
1865
1760
  function formatDate3(value) {
1866
1761
  return value ? String(value).slice(0, 10) : "";
1867
1762
  }
@@ -1869,11 +1764,11 @@ function formatScopes(scopes) {
1869
1764
  return Array.isArray(scopes) ? scopes.join(",") : "";
1870
1765
  }
1871
1766
  async function runKeysList(ctx, apiKey) {
1872
- const { body } = await enterpriseGet(
1767
+ const { body } = await enterpriseGet2(
1873
1768
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1874
1769
  "/keys"
1875
1770
  );
1876
- const keys = unwrapDataArray(body);
1771
+ const keys = (0, import_core16.unwrapDataArray)(body);
1877
1772
  if (ctx.format === "json") {
1878
1773
  printJson(keys);
1879
1774
  return;
@@ -1904,6 +1799,7 @@ function registerEnterpriseKeysList(parent) {
1904
1799
  }
1905
1800
 
1906
1801
  // src/commands/enterprise/keys/rotate.ts
1802
+ var import_core17 = require("@wport/core");
1907
1803
  var ENTERPRISE_KEY_EXPIRY_DAYS = [30, 60, 90];
1908
1804
  function validateExpiryDays(raw) {
1909
1805
  if (raw === void 0) return void 0;
@@ -1921,12 +1817,12 @@ async function runKeysRotate(ctx, apiKey, encId, flags) {
1921
1817
  const expiryDays = validateExpiryDays(flags.expiryDays);
1922
1818
  const requestBody = {};
1923
1819
  if (expiryDays !== void 0) requestBody.expiry_days = expiryDays;
1924
- const { body } = await enterprisePost(
1820
+ const { body } = await enterprisePost2(
1925
1821
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1926
1822
  `/keys/${encodeURIComponent(trimmed)}/rotate`,
1927
1823
  requestBody
1928
1824
  );
1929
- const issued = unwrapDataResponse(body);
1825
+ const issued = (0, import_core17.unwrapDataResponse)(body);
1930
1826
  if (ctx.format === "json") {
1931
1827
  printJson(issued);
1932
1828
  } else {
@@ -1965,6 +1861,7 @@ function registerEnterpriseKeysCommand(parent) {
1965
1861
  }
1966
1862
 
1967
1863
  // src/commands/enterprise/company/view.ts
1864
+ var import_core18 = require("@wport/core");
1968
1865
  var COMPANY_STATUS_LABELS = {
1969
1866
  0: "not_submitted",
1970
1867
  1: "pending_review",
@@ -2020,11 +1917,11 @@ function renderDetailLines2(company) {
2020
1917
  return lines;
2021
1918
  }
2022
1919
  async function runCompanyView(ctx, apiKey, flags) {
2023
- const { body } = await enterpriseGet(
1920
+ const { body } = await enterpriseGet2(
2024
1921
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2025
1922
  "/company"
2026
1923
  );
2027
- const company = unwrapDataResponse(body);
1924
+ const company = (0, import_core18.unwrapDataResponse)(body);
2028
1925
  if (flags.fields) {
2029
1926
  printJson(pickPaths(company, parseFieldsList(flags.fields)));
2030
1927
  return;
@@ -2046,6 +1943,7 @@ function registerEnterpriseCompanyView(parent) {
2046
1943
 
2047
1944
  // src/commands/enterprise/company/update.ts
2048
1945
  var import_node_crypto5 = require("crypto");
1946
+ var import_core19 = require("@wport/core");
2049
1947
 
2050
1948
  // src/commands/enterprise/company/types.ts
2051
1949
  var BASIC_FIELDS = [
@@ -2111,11 +2009,11 @@ async function buildCompanyUpdatePayloads(input, ctx, apiKey) {
2111
2009
  if (!inputHasBasicField && !inputHasDescriptionField) {
2112
2010
  throw new InvalidArgumentError("No writable company fields provided");
2113
2011
  }
2114
- const { body } = await enterpriseGet(
2012
+ const { body } = await enterpriseGet2(
2115
2013
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2116
2014
  "/company"
2117
2015
  );
2118
- const current = unwrapDataResponse(body);
2016
+ const current = (0, import_core19.unwrapDataResponse)(body);
2119
2017
  const missingContractFields = CONTRACT_REQUIRED_GET_FIELDS.filter((f) => !(f in current));
2120
2018
  if (missingContractFields.length > 0) {
2121
2019
  throw new CliError(
@@ -2182,23 +2080,23 @@ async function runCompanyUpdate(ctx, apiKey, source, idempotencyFlags, options =
2182
2080
  const requestOpts = { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey };
2183
2081
  let basicResultCompany;
2184
2082
  if (needsBasic) {
2185
- const { body } = await enterprisePatch(requestOpts, "/company/basic", payloads.basic, {
2083
+ const { body } = await enterprisePatch2(requestOpts, "/company/basic", payloads.basic, {
2186
2084
  idempotencyKey: basicKey
2187
2085
  });
2188
- basicResultCompany = unwrapDataResponse(body);
2086
+ basicResultCompany = (0, import_core19.unwrapDataResponse)(body);
2189
2087
  }
2190
2088
  if (!needsDescriptions) {
2191
2089
  printCompanyResult(basicResultCompany, ctx.format);
2192
2090
  return;
2193
2091
  }
2194
2092
  try {
2195
- const { body } = await enterprisePatch(
2093
+ const { body } = await enterprisePatch2(
2196
2094
  requestOpts,
2197
2095
  "/company/descriptions",
2198
2096
  payloads.descriptions,
2199
2097
  { idempotencyKey: descriptionsKey }
2200
2098
  );
2201
- const result = unwrapDataResponse(body);
2099
+ const result = (0, import_core19.unwrapDataResponse)(body);
2202
2100
  printCompanyResult(result.company, ctx.format);
2203
2101
  } catch (err) {
2204
2102
  if (!needsBasic) {
@@ -2257,6 +2155,7 @@ function registerEnterpriseCompanyUpdate(parent) {
2257
2155
  var import_node_crypto6 = require("crypto");
2258
2156
  var import_node_fs7 = require("fs");
2259
2157
  var import_node_path3 = require("path");
2158
+ var import_core20 = require("@wport/core");
2260
2159
  var EXTENSION_TO_CONTENT_TYPE = {
2261
2160
  ".png": "image/png",
2262
2161
  ".jpg": "image/jpeg",
@@ -2308,29 +2207,29 @@ async function runCompanyLogoUpload(ctx, apiKey, path, idempotencyFlags) {
2308
2207
  const { contentType, fileSize, bytes } = inspectLocalFile(path);
2309
2208
  const { presignKey, confirmKey } = resolveLogoIdempotencyKeys(idempotencyFlags);
2310
2209
  const requestOpts = { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey };
2311
- const { body: presignBody } = await enterprisePost(
2210
+ const { body: presignBody } = await enterprisePost2(
2312
2211
  requestOpts,
2313
2212
  "/company/logo/presign",
2314
2213
  { content_type: contentType, file_size: fileSize },
2315
2214
  { idempotencyKey: presignKey }
2316
2215
  );
2317
- const presign = unwrapDataResponse(presignBody);
2216
+ const presign = (0, import_core20.unwrapDataResponse)(presignBody);
2318
2217
  const putRequest = new Request(presign.upload_url, {
2319
2218
  method: "PUT",
2320
2219
  headers: { "Content-Type": contentType },
2321
2220
  body: bytes
2322
2221
  });
2323
- const putResponse = await fetchWithTimeout(putRequest, ctx.timeoutMs);
2222
+ const putResponse = await (0, import_core20.fetchWithTimeout)(putRequest, ctx.timeoutMs);
2324
2223
  if (!putResponse.ok) {
2325
2224
  throw new NetworkError(`Failed to upload file to S3: HTTP ${putResponse.status}`);
2326
2225
  }
2327
- const { body: confirmBody } = await enterprisePost(
2226
+ const { body: confirmBody } = await enterprisePost2(
2328
2227
  requestOpts,
2329
2228
  "/company/logo/confirm",
2330
2229
  { s3_key: presign.s3_key },
2331
2230
  { idempotencyKey: confirmKey }
2332
2231
  );
2333
- const result = unwrapDataResponse(confirmBody);
2232
+ const result = (0, import_core20.unwrapDataResponse)(confirmBody);
2334
2233
  printLogoResult(result, ctx.format);
2335
2234
  }
2336
2235
  function registerEnterpriseCompanyLogo(parent) {
@@ -2352,6 +2251,7 @@ function registerEnterpriseCompanyCommand(parent) {
2352
2251
  }
2353
2252
 
2354
2253
  // src/commands/enterprise/talents/list.ts
2254
+ var import_core21 = require("@wport/core");
2355
2255
  var DEFAULT_PAGE_SIZE = 20;
2356
2256
  var MINIMAL_LIST_FIELDS2 = ["enc_resume_id", "candidate_name", "applied_job_title", "applied_at"];
2357
2257
  function formatDate4(value) {
@@ -2361,7 +2261,7 @@ async function runEnterpriseTalentsList(ctx, apiKey, flags) {
2361
2261
  if (flags.fields && flags.minimal) {
2362
2262
  throw new CliError("Use either --fields or --minimal, not both", ExitCode.InvalidArgument);
2363
2263
  }
2364
- const { body } = await enterpriseGet(
2264
+ const { body } = await enterpriseGet2(
2365
2265
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2366
2266
  "/talents",
2367
2267
  {
@@ -2374,7 +2274,7 @@ async function runEnterpriseTalentsList(ctx, apiKey, flags) {
2374
2274
  pageSize: flags.pageSize ?? DEFAULT_PAGE_SIZE
2375
2275
  }
2376
2276
  );
2377
- const paged = asPaginatedBody(body);
2277
+ const paged = (0, import_core21.asPaginatedBody)(body);
2378
2278
  const projection = flags.minimal ? MINIMAL_LIST_FIELDS2 : flags.fields ? parseFieldsList(flags.fields) : void 0;
2379
2279
  if (projection || ctx.format === "json") {
2380
2280
  printJson(projection ? { ...paged, data: paged.data.map((row) => pickPaths(row, projection)) } : paged);
@@ -2407,16 +2307,17 @@ function registerEnterpriseTalentsList(parent) {
2407
2307
  }
2408
2308
 
2409
2309
  // src/commands/enterprise/talents/view.ts
2310
+ var import_core22 = require("@wport/core");
2410
2311
  async function runEnterpriseTalentsView(ctx, apiKey, encResumeId, flags) {
2411
2312
  const trimmed = encResumeId.trim();
2412
2313
  if (!trimmed) {
2413
2314
  throw new CliError("enc_resume_id must not be empty", ExitCode.InvalidArgument);
2414
2315
  }
2415
- const { body } = await enterpriseGet(
2316
+ const { body } = await enterpriseGet2(
2416
2317
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2417
2318
  `/talents/${encodeURIComponent(trimmed)}`
2418
2319
  );
2419
- const resume = unwrapDataResponse(body);
2320
+ const resume = (0, import_core22.unwrapDataResponse)(body);
2420
2321
  if (flags.fields) {
2421
2322
  printJson(pickPaths(resume, parseFieldsList(flags.fields)));
2422
2323
  return;
@@ -2434,6 +2335,7 @@ function registerEnterpriseTalentsView(parent) {
2434
2335
 
2435
2336
  // src/commands/enterprise/talents/respond.ts
2436
2337
  var import_node_crypto7 = require("crypto");
2338
+ var import_core23 = require("@wport/core");
2437
2339
  function resolveRespondBody(flags, options = {}) {
2438
2340
  const hasBody = flags.body !== void 0;
2439
2341
  const hasBodyFile = flags.bodyFile !== void 0;
@@ -2459,13 +2361,13 @@ async function runEnterpriseTalentsRespond(ctx, apiKey, encResumeId, flags, idem
2459
2361
  const encJobId = flags.encJobId?.trim();
2460
2362
  const payload = { subject, body };
2461
2363
  if (encJobId) payload.enc_job_id = encJobId;
2462
- const { body: respBody } = await enterprisePost(
2364
+ const { body: respBody } = await enterprisePost2(
2463
2365
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2464
2366
  `/talents/${encodeURIComponent(trimmedId)}/respond`,
2465
2367
  payload,
2466
2368
  { idempotencyKey }
2467
2369
  );
2468
- const result = unwrapDataResponse(respBody);
2370
+ const result = (0, import_core23.unwrapDataResponse)(respBody);
2469
2371
  if (ctx.format === "json") {
2470
2372
  printJson(result);
2471
2373
  return;
@@ -2494,15 +2396,16 @@ function registerEnterpriseTalentsCommand(parent) {
2494
2396
 
2495
2397
  // src/commands/enterprise/campaigns/create.ts
2496
2398
  var import_node_crypto8 = require("crypto");
2399
+ var import_core24 = require("@wport/core");
2497
2400
  async function runCampaignCreate(ctx, apiKey, source, idempotencyKey) {
2498
2401
  const campaignBody = readJsonObject(source, { timeoutMs: ctx.timeoutMs });
2499
- const { body } = await enterprisePost(
2402
+ const { body } = await enterprisePost2(
2500
2403
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2501
2404
  "/campaigns",
2502
2405
  campaignBody,
2503
2406
  { idempotencyKey }
2504
2407
  );
2505
- const created = unwrapDataResponse(body);
2408
+ const created = (0, import_core24.unwrapDataResponse)(body);
2506
2409
  if (ctx.format === "json") {
2507
2410
  printJson(created);
2508
2411
  return;
@@ -2520,6 +2423,7 @@ function registerEnterpriseCampaignsCreate(parent) {
2520
2423
  }
2521
2424
 
2522
2425
  // src/commands/enterprise/campaigns/list.ts
2426
+ var import_core25 = require("@wport/core");
2523
2427
  var STATUS_MAP2 = { open: 1, closed: 0 };
2524
2428
  var MINIMAL_LIST_FIELDS3 = ["enc_id", "name", "status", "job_count"];
2525
2429
  function mapStatusFlag2(raw) {
@@ -2539,7 +2443,7 @@ async function runEnterpriseCampaignsList(ctx, apiKey, flags) {
2539
2443
  if (flags.fields && flags.minimal) {
2540
2444
  throw new CliError("Use either --fields or --minimal, not both", ExitCode.InvalidArgument);
2541
2445
  }
2542
- const { body } = await enterpriseGet(
2446
+ const { body } = await enterpriseGet2(
2543
2447
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2544
2448
  "/campaigns",
2545
2449
  {
@@ -2549,7 +2453,7 @@ async function runEnterpriseCampaignsList(ctx, apiKey, flags) {
2549
2453
  status: mapStatusFlag2(flags.status)
2550
2454
  }
2551
2455
  );
2552
- const paged = asPaginatedBody(body);
2456
+ const paged = (0, import_core25.asPaginatedBody)(body);
2553
2457
  const projection = flags.minimal ? MINIMAL_LIST_FIELDS3 : flags.fields ? parseFieldsList(flags.fields) : void 0;
2554
2458
  if (projection || ctx.format === "json") {
2555
2459
  printJson(projection ? { ...paged, data: paged.data.map((row) => pickPaths(row, projection)) } : paged);
@@ -2582,15 +2486,16 @@ function registerEnterpriseCampaignsList(parent) {
2582
2486
 
2583
2487
  // src/commands/enterprise/campaigns/lifecycle.ts
2584
2488
  var import_node_crypto9 = require("crypto");
2489
+ var import_core26 = require("@wport/core");
2585
2490
  async function runCampaignTransition(ctx, apiKey, encId, action, idempotencyKey) {
2586
2491
  const trimmed = requireEncId(encId);
2587
- const { body } = await enterprisePatch(
2492
+ const { body } = await enterprisePatch2(
2588
2493
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2589
2494
  `/campaigns/${encodeURIComponent(trimmed)}/${action}`,
2590
2495
  {},
2591
2496
  { idempotencyKey }
2592
2497
  );
2593
- const result = unwrapDataResponse(body);
2498
+ const result = (0, import_core26.unwrapDataResponse)(body);
2594
2499
  if (ctx.format === "json") {
2595
2500
  printJson(result);
2596
2501
  return;
@@ -2616,16 +2521,17 @@ function registerEnterpriseCampaignsUnpublish(parent) {
2616
2521
 
2617
2522
  // src/commands/enterprise/campaigns/update.ts
2618
2523
  var import_node_crypto10 = require("crypto");
2524
+ var import_core27 = require("@wport/core");
2619
2525
  async function runCampaignUpdate(ctx, apiKey, encId, source, idempotencyKey) {
2620
2526
  const trimmed = requireEncId(encId);
2621
2527
  const campaignBody = readJsonObject(source, { timeoutMs: ctx.timeoutMs });
2622
- const { body } = await enterprisePatch(
2528
+ const { body } = await enterprisePatch2(
2623
2529
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2624
2530
  `/campaigns/${encodeURIComponent(trimmed)}`,
2625
2531
  campaignBody,
2626
2532
  { idempotencyKey }
2627
2533
  );
2628
- const updated = unwrapDataResponse(body);
2534
+ const updated = (0, import_core27.unwrapDataResponse)(body);
2629
2535
  if (ctx.format === "json") {
2630
2536
  printJson(updated);
2631
2537
  return;
@@ -2643,16 +2549,17 @@ function registerEnterpriseCampaignsUpdate(parent) {
2643
2549
  }
2644
2550
 
2645
2551
  // src/commands/enterprise/campaigns/view.ts
2552
+ var import_core28 = require("@wport/core");
2646
2553
  async function runEnterpriseCampaignsView(ctx, apiKey, encId, flags) {
2647
2554
  const trimmed = encId.trim();
2648
2555
  if (!trimmed) {
2649
2556
  throw new CliError("enc_id must not be empty", ExitCode.InvalidArgument);
2650
2557
  }
2651
- const { body } = await enterpriseGet(
2558
+ const { body } = await enterpriseGet2(
2652
2559
  { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2653
2560
  `/campaigns/${encodeURIComponent(trimmed)}`
2654
2561
  );
2655
- const campaign = unwrapDataResponse(body);
2562
+ const campaign = (0, import_core28.unwrapDataResponse)(body);
2656
2563
  if (flags.fields) {
2657
2564
  printJson(pickPaths(campaign, parseFieldsList(flags.fields)));
2658
2565
  return;
@@ -2744,6 +2651,8 @@ function describeIdentity(displayName, email) {
2744
2651
  return "this device";
2745
2652
  }
2746
2653
  async function performLogin2(ctx, flags, deps = {}) {
2654
+ const banner = channelBanner();
2655
+ if (banner) process.stderr.write(banner);
2747
2656
  const openBrowser = deps.openBrowser ?? openInBrowser;
2748
2657
  const hostnameFn = deps.hostname ?? import_node_os.hostname;
2749
2658
  if (!flags.force) {
@@ -2794,7 +2703,11 @@ function registerLoginCommand(program2) {
2794
2703
  });
2795
2704
  }
2796
2705
 
2706
+ // src/commands/auth/whoami.ts
2707
+ var import_core30 = require("@wport/core");
2708
+
2797
2709
  // src/lib/personal-client.ts
2710
+ var import_core29 = require("@wport/core");
2798
2711
  var EXPIRY_SKEW_MS = 3e4;
2799
2712
  var NOT_LOGGED_IN_MESSAGE = "Not logged in. Run `wport login`.";
2800
2713
  async function ensureFreshCredentials(opts) {
@@ -2823,7 +2736,7 @@ async function attemptRequest(opts, method, path, accessToken, body, query, extr
2823
2736
  const headers = {
2824
2737
  Authorization: `Bearer ${accessToken}`,
2825
2738
  "Accept-Language": opts.locale,
2826
- "User-Agent": buildUserAgent(),
2739
+ "User-Agent": (0, import_core29.buildUserAgent)("wport-cli", "0.9.1"),
2827
2740
  Accept: "application/json"
2828
2741
  };
2829
2742
  if (body !== void 0) headers["Content-Type"] = "application/json";
@@ -2833,7 +2746,7 @@ async function attemptRequest(opts, method, path, accessToken, body, query, extr
2833
2746
  headers,
2834
2747
  body: body !== void 0 ? JSON.stringify(body) : void 0
2835
2748
  });
2836
- const res = await fetchWithTimeout(request, opts.timeoutMs);
2749
+ const res = await (0, import_core29.fetchWithTimeout)(request, opts.timeoutMs);
2837
2750
  const respBody = await res.json().catch(() => null);
2838
2751
  return { status: res.status, body: respBody, headers: res.headers };
2839
2752
  }
@@ -2865,13 +2778,13 @@ function personalDelete(opts, path, extra) {
2865
2778
  }
2866
2779
  function throwPersonalHttpError(status, body) {
2867
2780
  if (status === 401) {
2868
- const base = extractErrorMessage(body) ?? `HTTP ${status}`;
2781
+ const base = (0, import_core29.extractErrorMessage)(body) ?? `HTTP ${status}`;
2869
2782
  throw new CliError(
2870
2783
  `${base} \u2014 Your session may have expired or the request was rejected. Run \`wport login\` to sign in again.`,
2871
2784
  ExitCode.ServerClientError
2872
2785
  );
2873
2786
  }
2874
- throwForHttpStatus(status, body);
2787
+ (0, import_core29.throwForHttpStatus)(status, body);
2875
2788
  }
2876
2789
  function warnIfRateLimitLow2(headers) {
2877
2790
  const remaining = Number(headers.get("x-ratelimit-remaining"));
@@ -2882,6 +2795,7 @@ function warnIfRateLimitLow2(headers) {
2882
2795
  }
2883
2796
 
2884
2797
  // src/commands/auth/whoami.ts
2798
+ var import_core31 = require("@wport/core");
2885
2799
  var PLACEHOLDER = "\u2014";
2886
2800
  function display(value) {
2887
2801
  const clean = sanitizeForTerminal(value ?? "");
@@ -2889,8 +2803,8 @@ function display(value) {
2889
2803
  }
2890
2804
  async function performWhoami(ctx) {
2891
2805
  const opts = { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs };
2892
- const { body } = await personalGet(opts, OAUTH_SESSIONS_BASE);
2893
- const sessions = unwrapDataArray(body);
2806
+ const { body } = await personalGet(opts, import_core31.OAUTH_SESSIONS_BASE);
2807
+ const sessions = (0, import_core30.unwrapDataArray)(body);
2894
2808
  const current = sessions.find((s) => s.is_current);
2895
2809
  const localLoginAt = loadPersonalCredentials()?.session_created_at ?? null;
2896
2810
  if (ctx.format === "json") {
@@ -2942,13 +2856,15 @@ function registerLogoutCommand(program2) {
2942
2856
  }
2943
2857
 
2944
2858
  // src/commands/sessions/list.ts
2859
+ var import_core32 = require("@wport/core");
2860
+ var import_core33 = require("@wport/core");
2945
2861
  function formatDate5(value) {
2946
2862
  return value ? String(value).slice(0, 10) : "";
2947
2863
  }
2948
2864
  async function runSessionsList(ctx) {
2949
2865
  const opts = { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs };
2950
- const { body } = await personalGet(opts, OAUTH_SESSIONS_BASE);
2951
- const sessions = unwrapDataArray(body);
2866
+ const { body } = await personalGet(opts, import_core33.OAUTH_SESSIONS_BASE);
2867
+ const sessions = (0, import_core32.unwrapDataArray)(body);
2952
2868
  if (ctx.format === "json") {
2953
2869
  printJson(sessions);
2954
2870
  return;
@@ -2972,6 +2888,8 @@ function registerSessionsList(parent) {
2972
2888
  }
2973
2889
 
2974
2890
  // src/commands/sessions/revoke.ts
2891
+ var import_core34 = require("@wport/core");
2892
+ var import_core35 = require("@wport/core");
2975
2893
  async function runSessionsRevoke(ctx, encId, allOthers) {
2976
2894
  const hasEncId = encId !== void 0;
2977
2895
  if (hasEncId === allOthers) {
@@ -2979,8 +2897,8 @@ async function runSessionsRevoke(ctx, encId, allOthers) {
2979
2897
  }
2980
2898
  const opts = { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs };
2981
2899
  if (allOthers) {
2982
- const { body } = await personalDelete(opts, `${OAUTH_SESSIONS_BASE}/others`);
2983
- const result = unwrapDataResponse(body);
2900
+ const { body } = await personalDelete(opts, `${import_core34.OAUTH_SESSIONS_BASE}/others`);
2901
+ const result = (0, import_core35.unwrapDataResponse)(body);
2984
2902
  if (ctx.format === "json") {
2985
2903
  printJson(result);
2986
2904
  return;
@@ -2991,7 +2909,7 @@ async function runSessionsRevoke(ctx, encId, allOthers) {
2991
2909
  }
2992
2910
  const trimmed = encId.trim();
2993
2911
  if (!trimmed) throw new CliError("enc_id must not be empty", ExitCode.InvalidArgument);
2994
- await personalDelete(opts, `${OAUTH_SESSIONS_BASE}/${encodeURIComponent(trimmed)}`);
2912
+ await personalDelete(opts, `${import_core34.OAUTH_SESSIONS_BASE}/${encodeURIComponent(trimmed)}`);
2995
2913
  if (ctx.format === "json") {
2996
2914
  printJson({ enc_id: trimmed, revoked: true });
2997
2915
  return;
@@ -3013,949 +2931,19 @@ function registerSessionsCommand(program2) {
3013
2931
  registerSessionsRevoke(sessions);
3014
2932
  }
3015
2933
 
3016
- // src/lib/resume-schema/definition.ts
3017
- var DEGREE_TYPE_VALUES = ["phd", "master", "bachelor", "associate", "senior_high", "junior_or_below"];
3018
- var EDUCATION_STATUS_VALUES = ["graduated", "dropped", "studying"];
3019
- var JOB_FEATURE_CODE_VALUES = ["dispatch", "executive", "full_time", "internship", "part_time"];
3020
- var WORKING_HOUR_TYPE_VALUES = ["day_shift", "evening_shift", "night_shift", "rotating_shift", "weekend_shift"];
3021
- var AVAILABLE_START_TYPE_VALUES = ["available_after_hired", "available_custom_date"];
3022
- var AVAILABLE_START_PERIOD_VALUES = ["week", "two_weeks", "month", "two_months", "three_months", "anytime"];
3023
- var SALARY_EXPECTATION_TYPE_VALUES = ["negotiable", "company_rules", "custom"];
3024
- var SALARY_UNIT_VALUES = ["hourly", "daily", "monthly", "yearly"];
3025
- var PROFICIENCY_LEVEL_VALUES = ["beginner", "daily_convo", "proficient", "native"];
3026
- var JOB_STATUS_VALUES = ["employed", "military", "student", "unemployed"];
3027
- var VEHICLE_OR_LICENSE_TYPE_VALUES = [
3028
- "bus",
3029
- "heavy_motorcycle",
3030
- "light_motorcycle",
3031
- "light_car",
3032
- "pro_bus",
3033
- "pro_small_car",
3034
- "pro_trailer",
3035
- "pro_truck",
3036
- "scooter",
3037
- "trailer",
3038
- "truck"
3039
- ];
3040
- var CURRENT_YEAR = (/* @__PURE__ */ new Date()).getFullYear();
3041
- var TOP_LEVEL_FIELDS = {
3042
- name: {
3043
- type: "string",
3044
- required: true,
3045
- minLength: 1,
3046
- maxLength: 100,
3047
- description: "\u5C65\u6B77\u540D\u7A31",
3048
- example: "\u6211\u7684\u5C65\u6B77\u540D\u7A31"
3049
- }
3050
- };
3051
- var educationSection = {
3052
- key: "education",
3053
- kind: "array",
3054
- description: "\u5B78\u6B77\uFF0C\u53EF\u591A\u7B46",
3055
- fields: {
3056
- school_name: {
3057
- type: "string",
3058
- required: true,
3059
- minLength: 1,
3060
- maxLength: 80,
3061
- description: "\u5B78\u6821\u540D\u7A31",
3062
- example: "National Taiwan University"
3063
- },
3064
- degree_code: {
3065
- type: "enum",
3066
- required: true,
3067
- enumValues: DEGREE_TYPE_VALUES,
3068
- description: "\u5B78\u6B77\u7B49\u7D1A",
3069
- example: "bachelor"
3070
- },
3071
- department: {
3072
- type: "string",
3073
- required: true,
3074
- minLength: 1,
3075
- maxLength: 80,
3076
- description: "\u4E3B\u4FEE\u79D1\u7CFB\u540D\u7A31",
3077
- example: "Computer Science"
3078
- },
3079
- minor_department: {
3080
- type: "string",
3081
- required: false,
3082
- minLength: 1,
3083
- maxLength: 80,
3084
- description: "\u526F\u4FEE\u79D1\u7CFB\u540D\u7A31",
3085
- example: "Mathematics"
3086
- },
3087
- department_class_code: {
3088
- type: "string",
3089
- required: false,
3090
- description: "\u4E3B\u4FEE\u79D1\u7CFB\u985E\u5225\u4EE3\u78BC",
3091
- example: "engineering"
3092
- },
3093
- minor_department_class_code: {
3094
- type: "string",
3095
- required: false,
3096
- description: "\u526F\u4FEE\u79D1\u7CFB\u985E\u5225\u4EE3\u78BC",
3097
- example: "industry_machinery"
3098
- },
3099
- edu_status_code: {
3100
- type: "enum",
3101
- required: true,
3102
- enumValues: EDUCATION_STATUS_VALUES,
3103
- description: "\u5C31\u5B78\u72C0\u614B",
3104
- example: "studying"
3105
- },
3106
- start_year: {
3107
- type: "integer",
3108
- required: true,
3109
- min: 1900,
3110
- max: CURRENT_YEAR,
3111
- description: "\u5165\u5B78\u5E74\u4EFD",
3112
- example: 2015
3113
- },
3114
- start_month: {
3115
- type: "integer",
3116
- required: true,
3117
- min: 1,
3118
- max: 12,
3119
- description: "\u5165\u5B78\u6708\u4EFD",
3120
- example: 9
3121
- },
3122
- end_year: {
3123
- type: "integer",
3124
- required: false,
3125
- min: 1900,
3126
- max: CURRENT_YEAR,
3127
- description: "\u7562\u696D\u5E74\u4EFD\uFF08edu_status_code \u70BA graduated/dropped \u6642\u5FC5\u586B\uFF09",
3128
- example: 2019
3129
- },
3130
- end_month: {
3131
- type: "integer",
3132
- required: false,
3133
- min: 1,
3134
- max: 12,
3135
- description: "\u7562\u696D\u6708\u4EFD\uFF08edu_status_code \u70BA graduated/dropped \u6642\u5FC5\u586B\uFF09",
3136
- example: 6
3137
- },
3138
- experience: {
3139
- type: "string",
3140
- required: false,
3141
- htmlText: true,
3142
- maxLength: 2e3,
3143
- description: "\u5728\u6821\u7D93\u6B77\uFF08\u5BCC\u6587\u672C\uFF09",
3144
- example: "<p>Served as the president of the student council.</p>"
3145
- }
3146
- },
3147
- crossFieldRules: [
3148
- {
3149
- name: "education-graduation-date",
3150
- message: 'When edu_status_code is "graduated" or "dropped", end_year/end_month are required and the end date must be after start_year/start_month and not later than today.',
3151
- check(item) {
3152
- const status = item.edu_status_code;
3153
- if (status !== "graduated" && status !== "dropped") return true;
3154
- const { end_year, end_month, start_year, start_month } = item;
3155
- if (typeof end_year !== "number" || typeof end_month !== "number") return false;
3156
- if (typeof start_year !== "number" || typeof start_month !== "number") return true;
3157
- const start = new Date(start_year, start_month - 1);
3158
- const end = new Date(end_year, end_month - 1);
3159
- return end > start && end <= /* @__PURE__ */ new Date();
3160
- }
3161
- }
3162
- ]
3163
- };
3164
- var workExperienceSection = {
3165
- key: "work_experience",
3166
- kind: "wrapper",
3167
- description: "\u5DE5\u4F5C\u7D93\u9A57\uFF08\u5305\u88DD\u5C64\u542B\u300C\u7121\u5DE5\u4F5C\u7D93\u9A57\u300D\u65D7\u6A19\uFF09",
3168
- wrapperFields: {
3169
- has_no_work_experience: {
3170
- type: "boolean",
3171
- required: false,
3172
- description: "\u662F\u5426\u7121\u5DE5\u4F5C\u7D93\u9A57\uFF08true \u6642 items \u5FC5\u70BA\u7A7A\u9663\u5217\uFF09",
3173
- example: false
3174
- }
3175
- },
3176
- fields: {
3177
- job_title: {
3178
- type: "string",
3179
- required: true,
3180
- maxLength: 100,
3181
- description: "\u8077\u7A31",
3182
- example: "Software Engineer"
3183
- },
3184
- job_type: {
3185
- type: "enum",
3186
- required: true,
3187
- enumValues: JOB_FEATURE_CODE_VALUES,
3188
- description: "\u8077\u52D9\u985E\u578B",
3189
- example: "full_time"
3190
- },
3191
- company_name: {
3192
- type: "string",
3193
- required: true,
3194
- maxLength: 200,
3195
- description: "\u516C\u53F8\u540D\u7A31",
3196
- example: "Tech Corp"
3197
- },
3198
- start_year: {
3199
- type: "integer",
3200
- required: true,
3201
- min: 1900,
3202
- max: CURRENT_YEAR,
3203
- description: "\u4EFB\u8077\u958B\u59CB\u5E74\u4EFD",
3204
- example: 2020
3205
- },
3206
- start_month: {
3207
- type: "integer",
3208
- required: true,
3209
- min: 1,
3210
- max: 12,
3211
- description: "\u4EFB\u8077\u958B\u59CB\u6708\u4EFD",
3212
- example: 1
3213
- },
3214
- end_year: {
3215
- type: "integer",
3216
- required: false,
3217
- min: 1900,
3218
- max: CURRENT_YEAR,
3219
- description: "\u4EFB\u8077\u7D50\u675F\u5E74\u4EFD\uFF08is_current \u70BA false \u6642\u5FC5\u586B\uFF09",
3220
- example: 2022
3221
- },
3222
- end_month: {
3223
- type: "integer",
3224
- required: false,
3225
- min: 1,
3226
- max: 12,
3227
- description: "\u4EFB\u8077\u7D50\u675F\u6708\u4EFD\uFF08is_current \u70BA false \u6642\u5FC5\u586B\uFF09",
3228
- example: 12
3229
- },
3230
- is_current: {
3231
- type: "boolean",
3232
- required: true,
3233
- description: "\u662F\u5426\u5728\u8077\u4E2D\uFF08\u8B80\u5BEB\u4E0D\u5C0D\u7A31\uFF1A\u805A\u5408\u683C\u5F0F\u53D6 read shape \u70BA boolean\uFF0Corchestrator \u5BEB\u5165\u6642\u8F49 0/1\uFF09",
3234
- example: false
3235
- },
3236
- job_description: {
3237
- type: "string",
3238
- required: false,
3239
- htmlText: true,
3240
- maxLength: 1e4,
3241
- description: "\u5DE5\u4F5C\u5167\u5BB9\u63CF\u8FF0\uFF08\u5BCC\u6587\u672C\uFF09",
3242
- example: "<p>Built and maintained internal developer tools.</p>"
3243
- }
3244
- },
3245
- crossFieldRules: [
3246
- {
3247
- name: "work-experience-end-date",
3248
- message: "When is_current is true, end_year/end_month must not be present. When is_current is false, end_year/end_month are required and the end date must be on/after the start date and not later than today.",
3249
- check(item) {
3250
- if (typeof item.is_current !== "boolean") return true;
3251
- if (item.is_current) {
3252
- return item.end_year === void 0 && item.end_month === void 0;
3253
- }
3254
- const { end_year, end_month, start_year, start_month } = item;
3255
- if (typeof end_year !== "number" || typeof end_month !== "number") return false;
3256
- if (typeof start_year !== "number" || typeof start_month !== "number") return true;
3257
- const start = new Date(start_year, start_month - 1);
3258
- const end = new Date(end_year, end_month - 1);
3259
- return end >= start && end <= /* @__PURE__ */ new Date();
3260
- }
3261
- },
3262
- {
3263
- name: "work-experience-no-experience-items-empty",
3264
- message: "When has_no_work_experience is true, items must be an empty array.",
3265
- check(wrapper) {
3266
- if (wrapper.has_no_work_experience !== true) return true;
3267
- return Array.isArray(wrapper.items) && wrapper.items.length === 0;
3268
- }
3269
- }
3270
- ]
3271
- };
3272
- var certificateSection = {
3273
- key: "certificate",
3274
- kind: "array",
3275
- description: "\u8B49\u7167\uFF0C\u53EF\u591A\u7B46",
3276
- fields: {
3277
- certificate_code: {
3278
- type: "string",
3279
- required: true,
3280
- description: "\u8B49\u7167\u4EE3\u78BC",
3281
- example: "4001001014"
3282
- },
3283
- issue_year: {
3284
- type: "integer",
3285
- required: true,
3286
- min: 1900,
3287
- max: 2100,
3288
- description: "\u8D77\u59CB\u5E74\u4EFD\uFF08\u683C\u5F0F\uFF1AYYYY\uFF09",
3289
- example: 2023
3290
- },
3291
- issue_month: {
3292
- type: "integer",
3293
- required: true,
3294
- min: 1,
3295
- max: 12,
3296
- description: "\u8D77\u59CB\u6708\u4EFD\uFF08\u683C\u5F0F\uFF1AMM\uFF09",
3297
- example: 1
3298
- },
3299
- expiry_year: {
3300
- type: "integer",
3301
- required: false,
3302
- min: 1900,
3303
- max: 2100,
3304
- description: "\u5230\u671F\u5E74\u4EFD\uFF08is_permanent \u70BA false \u6642\u5FC5\u586B\uFF09",
3305
- example: 2025
3306
- },
3307
- expiry_month: {
3308
- type: "integer",
3309
- required: false,
3310
- min: 1,
3311
- max: 12,
3312
- description: "\u5230\u671F\u6708\u4EFD\uFF08is_permanent \u70BA false \u6642\u5FC5\u586B\uFF09",
3313
- example: 1
3314
- },
3315
- is_permanent: {
3316
- type: "boolean",
3317
- required: true,
3318
- description: "\u662F\u5426\u6C38\u4E45\u6709\u6548",
3319
- example: false
3320
- }
3321
- },
3322
- crossFieldRules: [
3323
- {
3324
- name: "certificate-expiry-required",
3325
- message: "When is_permanent is false, expiry_year/expiry_month are required and must not be earlier than issue_year/issue_month.",
3326
- check(item) {
3327
- if (item.is_permanent !== false) return true;
3328
- const { expiry_year, expiry_month, issue_year, issue_month } = item;
3329
- if (typeof expiry_year !== "number" || typeof expiry_month !== "number") return false;
3330
- if (typeof issue_year !== "number" || typeof issue_month !== "number") return true;
3331
- return expiry_year * 12 + expiry_month >= issue_year * 12 + issue_month;
3332
- }
3333
- }
3334
- ]
3335
- };
3336
- var languageSection = {
3337
- key: "language",
3338
- kind: "array",
3339
- description: "\u8A9E\u8A00\u80FD\u529B\uFF0C\u53EF\u591A\u7B46",
3340
- fields: {
3341
- code: {
3342
- type: "string",
3343
- required: true,
3344
- description: "\u6280\u80FD\u8A9E\u8A00\u4EE3\u78BC\uFF08\u5C0D\u61C9 skill_languages \u8CC7\u6599\u8868\u4E2D\u7684 code\uFF09",
3345
- example: "Chinese"
3346
- },
3347
- proficiency_level_code: {
3348
- type: "enum",
3349
- required: true,
3350
- enumValues: PROFICIENCY_LEVEL_VALUES,
3351
- description: "\u719F\u7DF4\u7A0B\u5EA6",
3352
- example: "proficient"
3353
- }
3354
- }
3355
- };
3356
- var professionalSkillsSection = {
3357
- key: "professional_skills",
3358
- kind: "object",
3359
- description: "\u5C08\u696D\u6280\u80FD",
3360
- fields: {
3361
- tech_tool_codes: {
3362
- type: "string[]",
3363
- required: true,
3364
- minItems: 1,
3365
- maxItems: 10,
3366
- description: "\u5DE5\u5177\u4EE3\u78BC\u9663\u5217\uFF08\u6700\u591A 10 \u500B\uFF09",
3367
- example: ["12001001045", "12001001044"]
3368
- },
3369
- other_tool_description: {
3370
- type: "string",
3371
- required: false,
3372
- htmlText: true,
3373
- maxLength: 2e3,
3374
- description: "\u5176\u4ED6\u64C5\u9577\u5DE5\u5177\u63CF\u8FF0\uFF08\u5BCC\u6587\u672C\uFF0C\u5BE6\u969B\u6587\u5B57\u4E0A\u9650 2000 \u5B57\uFF09",
3375
- example: "\u719F\u6089 Photoshop \u548C Illustrator"
3376
- },
3377
- job_skill_codes: {
3378
- type: "string[]",
3379
- required: true,
3380
- minItems: 1,
3381
- maxItems: 10,
3382
- description: "\u6280\u80FD\u4EE3\u78BC\u9663\u5217\uFF08\u6700\u591A 10 \u500B\uFF09",
3383
- example: ["5001001006", "5001001007"]
3384
- },
3385
- other_job_skill_description: {
3386
- type: "string",
3387
- required: false,
3388
- htmlText: true,
3389
- maxLength: 2e3,
3390
- description: "\u5176\u4ED6\u5DE5\u4F5C\u6280\u80FD\u63CF\u8FF0\uFF08\u5BCC\u6587\u672C\uFF0C\u5BE6\u969B\u6587\u5B57\u4E0A\u9650 2000 \u5B57\uFF09",
3391
- example: "\u719F\u6089 Scrum \u548C\u654F\u6377\u958B\u767C\u6D41\u7A0B"
3392
- }
3393
- }
3394
- };
3395
- var autobiographySection = {
3396
- key: "autobiography",
3397
- kind: "scalar",
3398
- description: "\u81EA\u50B3\uFF08\u7D14\u5B57\u4E32\u7BC0\uFF09",
3399
- valueDef: {
3400
- type: "string",
3401
- required: true,
3402
- htmlText: true,
3403
- maxLength: 3e3,
3404
- description: "\u81EA\u50B3\u5167\u5BB9\uFF08\u5BCC\u6587\u672C\u683C\u5F0F\uFF09\uFF0C\u5BE6\u969B\u6587\u5B57\u9577\u5EA6\u4E0D\u8D85\u904E 3000 \u5B57",
3405
- example: "<p>This is my <strong>autobiography</strong>.</p>"
3406
- }
3407
- };
3408
- var jobConditionSection = {
3409
- key: "job_condition",
3410
- kind: "object",
3411
- description: "\u5E0C\u671B\u5DE5\u4F5C\u689D\u4EF6",
3412
- fields: {
3413
- feature_codes: {
3414
- type: "enum[]",
3415
- required: true,
3416
- enumValues: JOB_FEATURE_CODE_VALUES,
3417
- description: "\u5E0C\u671B\u6027\u8CEA\u4EE3\u78BC\uFF08\u591A\u9078\uFF09",
3418
- example: ["full_time", "part_time"]
3419
- },
3420
- working_hour_type_codes: {
3421
- type: "enum[]",
3422
- required: true,
3423
- enumValues: WORKING_HOUR_TYPE_VALUES,
3424
- description: "\u4E0A\u73ED\u6642\u6BB5\u4EE3\u78BC\uFF08\u591A\u9078\uFF09",
3425
- example: ["day_shift", "evening_shift"]
3426
- },
3427
- available_start_type_code: {
3428
- type: "enum",
3429
- required: true,
3430
- enumValues: AVAILABLE_START_TYPE_VALUES,
3431
- description: "\u53EF\u4E0A\u73ED\u6642\u9593\u985E\u578B\u4EE3\u78BC",
3432
- example: "available_after_hired"
3433
- },
3434
- available_start_date: {
3435
- type: "date",
3436
- required: false,
3437
- description: "\u81EA\u8A02\u53EF\u4E0A\u73ED\u65E5\u671F\uFF08available_start_type_code \u70BA available_custom_date \u6642\u5FC5\u586B\uFF09",
3438
- example: "2025-05-01"
3439
- },
3440
- available_start_period_code: {
3441
- type: "enum",
3442
- required: false,
3443
- enumValues: AVAILABLE_START_PERIOD_VALUES,
3444
- description: "\u9304\u53D6\u5F8C\u671F\u9593\u4EE3\u78BC\uFF08available_start_type_code \u70BA available_after_hired \u6642\u5FC5\u586B\uFF09",
3445
- example: "week"
3446
- },
3447
- salary_expectation_type_code: {
3448
- type: "enum",
3449
- required: true,
3450
- enumValues: SALARY_EXPECTATION_TYPE_VALUES,
3451
- description: "\u5E0C\u671B\u5F85\u9047\u985E\u578B\u4EE3\u78BC",
3452
- example: "negotiable"
3453
- },
3454
- salary_unit_code: {
3455
- type: "enum",
3456
- required: false,
3457
- enumValues: SALARY_UNIT_VALUES,
3458
- description: "\u85AA\u8CC7\u55AE\u4F4D\u4EE3\u78BC\uFF08salary_expectation_type_code \u70BA custom \u6642\u5FC5\u586B\uFF09",
3459
- example: "monthly"
3460
- },
3461
- salary_amount: {
3462
- type: "number",
3463
- required: false,
3464
- min: 0,
3465
- description: "\u81EA\u8A02\u85AA\u8CC7\u91D1\u984D\uFF08salary_expectation_type_code \u70BA custom \u6642\u5FC5\u586B\uFF09",
3466
- example: 5e4
3467
- },
3468
- expected_salary_currency_code: {
3469
- type: "string",
3470
- required: false,
3471
- minLength: 3,
3472
- maxLength: 3,
3473
- description: "\u671F\u671B\u85AA\u8CC7\u5E63\u5225\u4EE3\u78BC\uFF08ISO 4217\uFF0Csalary_expectation_type_code \u70BA custom \u6642\u5FC5\u586B\uFF09",
3474
- example: "TWD"
3475
- },
3476
- area_codes: {
3477
- type: "string[]",
3478
- required: false,
3479
- description: "\u5E0C\u671B\u5730\u9EDE\u4EE3\u78BC\uFF08\u591A\u9078\uFF09",
3480
- example: ["6001005009", "6001006008"]
3481
- },
3482
- job_classification_codes: {
3483
- type: "string[]",
3484
- required: false,
3485
- description: "\u5E0C\u671B\u8077\u985E\u4EE3\u78BC\uFF08\u591A\u9078\uFF09",
3486
- example: ["2002002006", "2003002007"]
3487
- }
3488
- },
3489
- crossFieldRules: [
3490
- {
3491
- name: "job-condition-available-start",
3492
- message: 'When available_start_type_code is "available_custom_date", available_start_date is required. When it is "available_after_hired", available_start_period_code is required.',
3493
- check(item) {
3494
- const code = item.available_start_type_code;
3495
- if (code === "available_custom_date") {
3496
- const v = item.available_start_date;
3497
- return v !== void 0 && v !== null && v !== "";
3498
- }
3499
- if (code === "available_after_hired") {
3500
- const v = item.available_start_period_code;
3501
- return v !== void 0 && v !== null && v !== "";
3502
- }
3503
- return true;
3504
- }
3505
- },
3506
- {
3507
- name: "job-condition-salary-expectation",
3508
- message: 'When salary_expectation_type_code is "custom", salary_unit_code, salary_amount, and expected_salary_currency_code are all required.',
3509
- check(item) {
3510
- if (item.salary_expectation_type_code !== "custom") return true;
3511
- const hasUnit = item.salary_unit_code !== void 0 && item.salary_unit_code !== null && item.salary_unit_code !== "";
3512
- const hasAmount = typeof item.salary_amount === "number";
3513
- const hasCurrency = typeof item.expected_salary_currency_code === "string" && item.expected_salary_currency_code.length > 0;
3514
- return hasUnit && hasAmount && hasCurrency;
3515
- }
3516
- }
3517
- ]
3518
- };
3519
- var portfolioLinksSection = {
3520
- key: "portfolio_links",
3521
- kind: "array",
3522
- maxItems: 5,
3523
- description: "\u4F5C\u54C1\u96C6\u9023\u7D50\uFF0C\u6700\u591A 5 \u7B46",
3524
- fields: {
3525
- link_title: {
3526
- type: "string",
3527
- required: true,
3528
- minLength: 1,
3529
- maxLength: 50,
3530
- description: "\u4F5C\u54C1\u96C6\u6A19\u984C",
3531
- example: "My Portfolio"
3532
- },
3533
- link_url: {
3534
- type: "string",
3535
- required: true,
3536
- minLength: 1,
3537
- maxLength: 2083,
3538
- description: "\u4F5C\u54C1\u96C6\u9023\u7D50\uFF08URL\uFF09",
3539
- example: "https://myportfolio.com"
3540
- }
3541
- }
3542
- };
3543
- var backgroundSection = {
3544
- key: "background",
3545
- kind: "object",
3546
- description: "\u80CC\u666F\u8CC7\u8A0A\uFF08\u99D5\u7167\uFF0F\u8ECA\u8F1B\uFF0F\u5175\u5F79\u72C0\u614B\uFF09",
3547
- fields: {
3548
- // identity_types(身分種類)刻意不列:屬會員個資唯讀例外(pm_27 欄位),寫聚合不收,
3549
- // validate 對此鍵給 read-only 專屬訊息而非泛用 unknown-field(見 design doc §4.2)。
3550
- driving_license_types: {
3551
- type: "enum[]",
3552
- required: false,
3553
- enumValues: VEHICLE_OR_LICENSE_TYPE_VALUES,
3554
- description: "\u99D5\u99DB\u57F7\u7167\u985E\u578B\u5217\u8868",
3555
- example: ["scooter", "light_motorcycle"]
3556
- },
3557
- vehicle_types: {
3558
- type: "enum[]",
3559
- required: false,
3560
- enumValues: VEHICLE_OR_LICENSE_TYPE_VALUES,
3561
- description: "\u8ECA\u8F1B\u985E\u578B\u5217\u8868",
3562
- example: ["scooter"]
3563
- },
3564
- job_status: {
3565
- type: "enum",
3566
- required: false,
3567
- enumValues: JOB_STATUS_VALUES,
3568
- description: "\u5DE5\u4F5C\u72C0\u614B",
3569
- example: "employed"
3570
- }
3571
- }
3572
- };
3573
- var SECTIONS = [
3574
- educationSection,
3575
- workExperienceSection,
3576
- certificateSection,
3577
- languageSection,
3578
- professionalSkillsSection,
3579
- autobiographySection,
3580
- jobConditionSection,
3581
- portfolioLinksSection,
3582
- backgroundSection
3583
- ];
3584
- function getSection(key) {
3585
- return SECTIONS.find((s) => s.key === key);
3586
- }
3587
-
3588
- // src/lib/resume-schema/to-json-schema.ts
3589
- var JSON_SCHEMA_DRAFT = "http://json-schema.org/draft-07/schema#";
3590
- function fieldDefToJsonSchema(field) {
3591
- const out = {};
3592
- switch (field.type) {
3593
- case "string":
3594
- out.type = "string";
3595
- if (field.minLength !== void 0) out.minLength = field.minLength;
3596
- if (field.maxLength !== void 0) out.maxLength = field.maxLength;
3597
- break;
3598
- case "number":
3599
- out.type = "number";
3600
- if (field.min !== void 0) out.minimum = field.min;
3601
- if (field.max !== void 0) out.maximum = field.max;
3602
- break;
3603
- case "integer":
3604
- out.type = "integer";
3605
- if (field.min !== void 0) out.minimum = field.min;
3606
- if (field.max !== void 0) out.maximum = field.max;
3607
- break;
3608
- case "boolean":
3609
- out.type = "boolean";
3610
- break;
3611
- case "enum":
3612
- out.type = "string";
3613
- out.enum = field.enumValues ? [...field.enumValues] : [];
3614
- break;
3615
- case "string[]":
3616
- out.type = "array";
3617
- out.items = { type: "string" };
3618
- if (field.minItems !== void 0) out.minItems = field.minItems;
3619
- if (field.maxItems !== void 0) out.maxItems = field.maxItems;
3620
- break;
3621
- case "enum[]":
3622
- out.type = "array";
3623
- out.items = { type: "string", enum: field.enumValues ? [...field.enumValues] : [] };
3624
- if (field.minItems !== void 0) out.minItems = field.minItems;
3625
- if (field.maxItems !== void 0) out.maxItems = field.maxItems;
3626
- break;
3627
- case "date":
3628
- out.type = "string";
3629
- out.format = "date";
3630
- break;
3631
- }
3632
- out.description = field.htmlText ? `${field.description}(\u5B57\u6578\u4E0A\u9650\u8A08\u7B97\u65B9\u5F0F:\u5148\u525D\u9664 HTML tag,\u4EE5\u7D14\u6587\u5B57\u9577\u5EA6\u8A08\u7B97)` : field.description;
3633
- if (field.example !== void 0) out.example = field.example;
3634
- return out;
3635
- }
3636
- function fieldsToPropertiesAndRequired(fields) {
3637
- const properties = {};
3638
- const required = [];
3639
- for (const [key, field] of Object.entries(fields)) {
3640
- properties[key] = fieldDefToJsonSchema(field);
3641
- if (field.required) required.push(key);
3642
- }
3643
- return { properties, required };
3644
- }
3645
- function buildSectionSchema(section) {
3646
- if (section.kind === "scalar") {
3647
- return fieldDefToJsonSchema(section.valueDef);
3648
- }
3649
- if (section.kind === "object") {
3650
- const { properties, required } = fieldsToPropertiesAndRequired(section.fields);
3651
- return {
3652
- type: "object",
3653
- description: section.description,
3654
- additionalProperties: false,
3655
- required,
3656
- properties
3657
- };
3658
- }
3659
- if (section.kind === "array") {
3660
- const { properties, required } = fieldsToPropertiesAndRequired(section.fields);
3661
- const schema = {
3662
- type: "array",
3663
- description: section.description,
3664
- items: { type: "object", additionalProperties: false, required, properties }
3665
- };
3666
- if (section.maxItems !== void 0) schema.maxItems = section.maxItems;
3667
- return schema;
3668
- }
3669
- const wrapperParts = fieldsToPropertiesAndRequired(section.wrapperFields ?? {});
3670
- const itemParts = fieldsToPropertiesAndRequired(section.fields);
3671
- return {
3672
- type: "object",
3673
- description: section.description,
3674
- additionalProperties: false,
3675
- required: [...wrapperParts.required, "items"],
3676
- properties: {
3677
- ...wrapperParts.properties,
3678
- items: {
3679
- type: "array",
3680
- items: { type: "object", additionalProperties: false, required: itemParts.required, properties: itemParts.properties }
3681
- }
3682
- }
3683
- };
3684
- }
3685
- function buildAggregateJsonSchema(sectionKey) {
3686
- if (sectionKey !== void 0) {
3687
- const section = getSection(sectionKey);
3688
- if (!section) {
3689
- const allowed = SECTIONS.map((s) => s.key).join(", ");
3690
- throw new CliError(`Unknown section "${sectionKey}". Allowed: ${allowed}`, ExitCode.InvalidArgument);
3691
- }
3692
- return buildSectionSchema(section);
3693
- }
3694
- const properties = {
3695
- name: fieldDefToJsonSchema(TOP_LEVEL_FIELDS.name)
3696
- };
3697
- for (const section of SECTIONS) {
3698
- properties[section.key] = buildSectionSchema(section);
3699
- }
3700
- return {
3701
- $schema: JSON_SCHEMA_DRAFT,
3702
- title: "wport resume aggregate",
3703
- description: "wport \u5C65\u6B77\u805A\u5408\u683C\u5F0F(\u4F9B personal resumes create/update/validate \u4F7F\u7528)\u3002view \u8F38\u51FA\u53E6\u542B\u552F\u8B80\u7684 photo_url(\u6703\u54E1\u500B\u8CC7),\u4E0D\u5728\u6B64\u5BEB\u5165\u805A\u5408 schema \u5167,\u5982\u9700\u66F4\u65B0\u8ACB\u8D70 profile \u76F8\u95DC\u6D41\u7A0B\u3002",
3704
- type: "object",
3705
- additionalProperties: false,
3706
- required: ["name"],
3707
- properties
3708
- };
3709
- }
3710
-
3711
2934
  // src/commands/personal/resumes/schema.ts
2935
+ var import_core36 = require("@wport/core");
3712
2936
  function registerPersonalResumesSchema(parent) {
3713
2937
  parent.command("schema").description("Print the resume aggregate JSON Schema (offline, no login required)").option("--section <name>", "print only the given section (e.g. education, work_experience)").action((opts) => {
3714
- printJson(buildAggregateJsonSchema(opts.section));
2938
+ printJson((0, import_core36.buildAggregateJsonSchema)(opts.section));
3715
2939
  });
3716
2940
  }
3717
2941
 
3718
- // src/lib/resume-schema/validate.ts
3719
- var TOP_LEVEL_READ_ONLY_KEYS = /* @__PURE__ */ new Set(["photo_url"]);
3720
- var BACKGROUND_READ_ONLY_KEYS = /* @__PURE__ */ new Set(["identity_types"]);
3721
- var NO_READ_ONLY_KEYS = /* @__PURE__ */ new Set();
3722
- function itemAllowedKeys(fields) {
3723
- return /* @__PURE__ */ new Set([...Object.keys(fields), "enc_id"]);
3724
- }
3725
- function isPlainObject(value) {
3726
- return typeof value === "object" && value !== null && !Array.isArray(value);
3727
- }
3728
- function describeType(value) {
3729
- if (value === null) return "null";
3730
- if (Array.isArray(value)) return "an array";
3731
- return typeof value;
3732
- }
3733
- function unknownFieldMessage(key) {
3734
- return `Unknown field "${key}" \u2014 not part of the resume schema`;
3735
- }
3736
- function readOnlyFieldMessage(key) {
3737
- return `Field "${key}" is read-only (member profile data) \u2014 not part of the resume write aggregate`;
3738
- }
3739
- function stripHtml2(s) {
3740
- return s.replace(/<[^>]*>/g, "");
3741
- }
3742
- function isValidDateString(s) {
3743
- if (!/^\d{4}-\d{2}-\d{2}$/.test(s)) return false;
3744
- const [y, m, d] = s.split("-").map(Number);
3745
- const date = new Date(Date.UTC(y, m - 1, d));
3746
- return date.getUTCFullYear() === y && date.getUTCMonth() === m - 1 && date.getUTCDate() === d;
3747
- }
3748
- function checkUnknownKeys(obj, allowedKeys, readOnlyKeys, pathFor, issues) {
3749
- for (const key of Object.keys(obj)) {
3750
- if (allowedKeys.has(key)) continue;
3751
- if (readOnlyKeys.has(key)) {
3752
- issues.push({ path: pathFor(key), message: readOnlyFieldMessage(key) });
3753
- continue;
3754
- }
3755
- issues.push({ path: pathFor(key), message: unknownFieldMessage(key) });
3756
- }
3757
- }
3758
- function checkFieldValue(field, value, path, issues) {
3759
- switch (field.type) {
3760
- case "string": {
3761
- if (typeof value !== "string") {
3762
- issues.push({ path, message: `Expected a string (got ${describeType(value)})` });
3763
- return;
3764
- }
3765
- const text = field.htmlText ? stripHtml2(value) : value;
3766
- if (field.minLength !== void 0 && text.length < field.minLength) {
3767
- issues.push({ path, message: `Must be at least ${field.minLength} characters (got ${text.length})` });
3768
- }
3769
- if (field.maxLength !== void 0 && text.length > field.maxLength) {
3770
- issues.push({ path, message: `Must be at most ${field.maxLength} characters (got ${text.length})` });
3771
- }
3772
- return;
3773
- }
3774
- case "number": {
3775
- if (typeof value !== "number" || Number.isNaN(value)) {
3776
- issues.push({ path, message: `Expected a number (got ${describeType(value)})` });
3777
- return;
3778
- }
3779
- checkRange(field, value, path, issues);
3780
- return;
3781
- }
3782
- case "integer": {
3783
- if (typeof value !== "number" || !Number.isInteger(value)) {
3784
- issues.push({ path, message: `Expected an integer (got ${describeType(value)})` });
3785
- return;
3786
- }
3787
- checkRange(field, value, path, issues);
3788
- return;
3789
- }
3790
- case "boolean": {
3791
- if (typeof value !== "boolean") issues.push({ path, message: `Expected a boolean (got ${describeType(value)})` });
3792
- return;
3793
- }
3794
- case "enum": {
3795
- const allowed = field.enumValues ?? [];
3796
- if (typeof value !== "string" || !allowed.includes(value)) {
3797
- issues.push({ path, message: `Invalid value ${JSON.stringify(value)} \u2014 allowed: ${allowed.join(", ")}` });
3798
- }
3799
- return;
3800
- }
3801
- case "string[]": {
3802
- if (!Array.isArray(value)) {
3803
- issues.push({ path, message: `Expected an array (got ${describeType(value)})` });
3804
- return;
3805
- }
3806
- value.forEach((v, i) => {
3807
- if (typeof v !== "string") issues.push({ path: `${path}[${i}]`, message: `Expected a string (got ${describeType(v)})` });
3808
- });
3809
- checkItemsCount(field, value, path, issues);
3810
- return;
3811
- }
3812
- case "enum[]": {
3813
- const allowed = field.enumValues ?? [];
3814
- if (!Array.isArray(value)) {
3815
- issues.push({ path, message: `Expected an array (got ${describeType(value)})` });
3816
- return;
3817
- }
3818
- value.forEach((v, i) => {
3819
- if (typeof v !== "string" || !allowed.includes(v)) {
3820
- issues.push({ path: `${path}[${i}]`, message: `Invalid value ${JSON.stringify(v)} \u2014 allowed: ${allowed.join(", ")}` });
3821
- }
3822
- });
3823
- checkItemsCount(field, value, path, issues);
3824
- return;
3825
- }
3826
- case "date": {
3827
- if (typeof value !== "string" || !isValidDateString(value)) {
3828
- issues.push({ path, message: `Expected a date string in YYYY-MM-DD format (got ${JSON.stringify(value)})` });
3829
- }
3830
- return;
3831
- }
3832
- }
3833
- }
3834
- function checkRange(field, value, path, issues) {
3835
- if (field.min !== void 0 && value < field.min) issues.push({ path, message: `Must be >= ${field.min} (got ${value})` });
3836
- if (field.max !== void 0 && value > field.max) issues.push({ path, message: `Must be <= ${field.max} (got ${value})` });
3837
- }
3838
- function checkItemsCount(field, value, path, issues) {
3839
- if (field.minItems !== void 0 && value.length < field.minItems) {
3840
- issues.push({ path, message: `Must have at least ${field.minItems} item(s) (got ${value.length})` });
3841
- }
3842
- if (field.maxItems !== void 0 && value.length > field.maxItems) {
3843
- issues.push({ path, message: `Must have at most ${field.maxItems} item(s) (got ${value.length})` });
3844
- }
3845
- }
3846
- function checkFields(obj, fields, pathFor, issues) {
3847
- for (const [key, field] of Object.entries(fields)) {
3848
- const value = obj[key];
3849
- const path = pathFor(key);
3850
- if (value === void 0) {
3851
- if (field.required) issues.push({ path, message: `Missing required field "${key}"` });
3852
- continue;
3853
- }
3854
- checkFieldValue(field, value, path, issues);
3855
- }
3856
- }
3857
- function checkCrossFieldRules(rules, item, path, issues) {
3858
- for (const rule of rules ?? []) {
3859
- if (!rule.check(item)) issues.push({ path, message: rule.message });
3860
- }
3861
- }
3862
- function validateArraySection(section, value, issues) {
3863
- const path = section.key;
3864
- if (!Array.isArray(value)) {
3865
- issues.push({ path, message: `Expected an array (got ${describeType(value)})` });
3866
- return;
3867
- }
3868
- if (section.maxItems !== void 0 && value.length > section.maxItems) {
3869
- issues.push({ path, message: `Must have at most ${section.maxItems} item(s) (got ${value.length})` });
3870
- }
3871
- value.forEach((item, i) => {
3872
- const itemPath = `${path}[${i}]`;
3873
- if (!isPlainObject(item)) {
3874
- issues.push({ path: itemPath, message: `Expected an object (got ${describeType(item)})` });
3875
- return;
3876
- }
3877
- checkUnknownKeys(item, itemAllowedKeys(section.fields), NO_READ_ONLY_KEYS, (k) => `${itemPath}.${k}`, issues);
3878
- checkFields(item, section.fields, (k) => `${itemPath}.${k}`, issues);
3879
- checkCrossFieldRules(section.crossFieldRules, item, itemPath, issues);
3880
- });
3881
- }
3882
- function validateObjectSection(section, value, issues) {
3883
- const path = section.key;
3884
- if (!isPlainObject(value)) {
3885
- issues.push({ path, message: `Expected an object (got ${describeType(value)})` });
3886
- return;
3887
- }
3888
- const readOnly = section.key === "background" ? BACKGROUND_READ_ONLY_KEYS : NO_READ_ONLY_KEYS;
3889
- checkUnknownKeys(value, new Set(Object.keys(section.fields)), readOnly, (k) => `${path}.${k}`, issues);
3890
- checkFields(value, section.fields, (k) => `${path}.${k}`, issues);
3891
- checkCrossFieldRules(section.crossFieldRules, value, path, issues);
3892
- }
3893
- function validateScalarSection(section, value, issues) {
3894
- checkFieldValue(section.valueDef, value, section.key, issues);
3895
- }
3896
- function validateWrapperSection(section, value, issues) {
3897
- const path = section.key;
3898
- if (!isPlainObject(value)) {
3899
- issues.push({ path, message: `Expected an object (got ${describeType(value)})` });
3900
- return;
3901
- }
3902
- const wrapperFields = section.wrapperFields ?? {};
3903
- const allowedTopKeys = /* @__PURE__ */ new Set([...Object.keys(wrapperFields), "items"]);
3904
- checkUnknownKeys(value, allowedTopKeys, NO_READ_ONLY_KEYS, (k) => `${path}.${k}`, issues);
3905
- checkFields(value, wrapperFields, (k) => `${path}.${k}`, issues);
3906
- const items = value.items;
3907
- const itemsPath = `${path}.items`;
3908
- if (items === void 0) {
3909
- issues.push({ path: itemsPath, message: 'Missing required field "items"' });
3910
- } else if (!Array.isArray(items)) {
3911
- issues.push({ path: itemsPath, message: `Expected an array (got ${describeType(items)})` });
3912
- } else {
3913
- items.forEach((item, i) => {
3914
- const itemPath = `${itemsPath}[${i}]`;
3915
- if (!isPlainObject(item)) {
3916
- issues.push({ path: itemPath, message: `Expected an object (got ${describeType(item)})` });
3917
- return;
3918
- }
3919
- checkUnknownKeys(item, itemAllowedKeys(section.fields), NO_READ_ONLY_KEYS, (k) => `${itemPath}.${k}`, issues);
3920
- checkFields(item, section.fields, (k) => `${itemPath}.${k}`, issues);
3921
- checkCrossFieldRules(section.crossFieldRules, item, itemPath, issues);
3922
- });
3923
- }
3924
- checkCrossFieldRules(section.crossFieldRules, value, path, issues);
3925
- }
3926
- function validateAggregate(input) {
3927
- if (!isPlainObject(input)) {
3928
- return [{ path: "(root)", message: `Input must be a JSON object at the top level (got ${describeType(input)})` }];
3929
- }
3930
- const issues = [];
3931
- const allowedTopKeys = /* @__PURE__ */ new Set(["name", ...SECTIONS.map((s) => s.key)]);
3932
- checkUnknownKeys(input, allowedTopKeys, TOP_LEVEL_READ_ONLY_KEYS, (k) => k, issues);
3933
- checkFields(input, TOP_LEVEL_FIELDS, (k) => k, issues);
3934
- for (const section of SECTIONS) {
3935
- const value = input[section.key];
3936
- if (value === void 0) continue;
3937
- switch (section.kind) {
3938
- case "array":
3939
- validateArraySection(section, value, issues);
3940
- break;
3941
- case "object":
3942
- validateObjectSection(section, value, issues);
3943
- break;
3944
- case "scalar":
3945
- validateScalarSection(section, value, issues);
3946
- break;
3947
- case "wrapper":
3948
- validateWrapperSection(section, value, issues);
3949
- break;
3950
- }
3951
- }
3952
- return issues;
3953
- }
3954
-
3955
2942
  // src/commands/personal/resumes/validate.ts
2943
+ var import_core37 = require("@wport/core");
3956
2944
  function runResumesValidate(ctx, filePath) {
3957
2945
  const input = readJsonInput(filePath, { timeoutMs: ctx.timeoutMs });
3958
- const issues = validateAggregate(input);
2946
+ const issues = (0, import_core37.validateAggregate)(input);
3959
2947
  if (issues.length === 0) {
3960
2948
  if (ctx.format === "json") {
3961
2949
  printJson({ valid: true });
@@ -3983,43 +2971,9 @@ function registerPersonalResumesValidate(parent) {
3983
2971
 
3984
2972
  // src/commands/personal/resumes/template.ts
3985
2973
  var import_node_fs8 = require("fs");
3986
-
3987
- // src/lib/resume-schema/template.ts
3988
- function buildFieldsExample(fields) {
3989
- const out = {};
3990
- for (const [key, field] of Object.entries(fields)) {
3991
- if (field.example !== void 0) out[key] = field.example;
3992
- }
3993
- return out;
3994
- }
3995
- function buildSectionTemplate(section) {
3996
- switch (section.kind) {
3997
- case "scalar":
3998
- return section.valueDef.example;
3999
- case "object":
4000
- return buildFieldsExample(section.fields);
4001
- case "array":
4002
- return [buildFieldsExample(section.fields)];
4003
- case "wrapper":
4004
- return {
4005
- ...buildFieldsExample(section.wrapperFields ?? {}),
4006
- items: [buildFieldsExample(section.fields)]
4007
- };
4008
- }
4009
- }
4010
- function buildTemplate() {
4011
- const template = {
4012
- name: TOP_LEVEL_FIELDS.name.example
4013
- };
4014
- for (const section of SECTIONS) {
4015
- template[section.key] = buildSectionTemplate(section);
4016
- }
4017
- return template;
4018
- }
4019
-
4020
- // src/commands/personal/resumes/template.ts
2974
+ var import_core38 = require("@wport/core");
4021
2975
  function runResumesTemplate(outPath) {
4022
- const template = buildTemplate();
2976
+ const template = (0, import_core38.buildTemplate)();
4023
2977
  if (outPath === void 0) {
4024
2978
  printJson(template);
4025
2979
  return;
@@ -4043,6 +2997,8 @@ function registerPersonalResumesTemplate(parent) {
4043
2997
  }
4044
2998
 
4045
2999
  // src/commands/personal/resumes/list.ts
3000
+ var import_core39 = require("@wport/core");
3001
+ var import_core40 = require("@wport/core");
4046
3002
  var MINIMAL_LIST_FIELDS4 = ["enc_id", "name", "updated_at", "is_complete", "is_published"];
4047
3003
  function formatDate6(value) {
4048
3004
  return value ? String(value).slice(0, 10) : "";
@@ -4052,8 +3008,8 @@ function formatQuotaLine(quota) {
4052
3008
  return `${quota.used}/${quota.max_resumes} used, ${status}.`;
4053
3009
  }
4054
3010
  async function fetchResumeList(opts) {
4055
- const { body } = await personalGet(opts, PERSONAL_RESUMES_BASE);
4056
- return unwrapDataResponse(body);
3011
+ const { body } = await personalGet(opts, import_core40.PERSONAL_RESUMES_BASE);
3012
+ return (0, import_core39.unwrapDataResponse)(body);
4057
3013
  }
4058
3014
  async function runResumesList(ctx, flags) {
4059
3015
  if (flags.fields && flags.minimal) {
@@ -4086,17 +3042,20 @@ function registerPersonalResumesList(parent) {
4086
3042
  }
4087
3043
 
4088
3044
  // src/commands/personal/resumes/view.ts
3045
+ var import_core41 = require("@wport/core");
3046
+ var import_core42 = require("@wport/core");
3047
+ var import_core43 = require("@wport/core");
4089
3048
  async function fetchResumeAggregate(opts, encId) {
4090
3049
  const trimmed = encId.trim();
4091
3050
  if (!trimmed) {
4092
3051
  throw new CliError("enc_id must not be empty", ExitCode.InvalidArgument);
4093
3052
  }
4094
- const { body } = await personalGet(opts, `${PERSONAL_RESUMES_BASE}/${encodeURIComponent(trimmed)}`);
4095
- return unwrapDataResponse(body);
3053
+ const { body } = await personalGet(opts, `${import_core42.PERSONAL_RESUMES_BASE}/${encodeURIComponent(trimmed)}`);
3054
+ return (0, import_core41.unwrapDataResponse)(body);
4096
3055
  }
4097
3056
  function summarizeSections(resume) {
4098
3057
  const rows = [{ section: "name", summary: resume.name }];
4099
- for (const def of SECTIONS) {
3058
+ for (const def of import_core43.SECTIONS) {
4100
3059
  rows.push({ section: def.key, summary: summarizeSection(def, resume) });
4101
3060
  }
4102
3061
  return rows;
@@ -4173,8 +3132,14 @@ function registerPersonalResumesExport(parent) {
4173
3132
  });
4174
3133
  }
4175
3134
 
3135
+ // src/commands/personal/resumes/create.ts
3136
+ var import_core47 = require("@wport/core");
3137
+
4176
3138
  // src/lib/resume-orchestrator.ts
4177
3139
  var import_node_crypto11 = require("crypto");
3140
+ var import_core44 = require("@wport/core");
3141
+ var import_core45 = require("@wport/core");
3142
+ var import_core46 = require("@wport/core");
4178
3143
  function freshIdempotencyKey() {
4179
3144
  return { idempotencyKey: (0, import_node_crypto11.randomUUID)() };
4180
3145
  }
@@ -4189,8 +3154,8 @@ function extractErrorCode(body) {
4189
3154
  }
4190
3155
  async function createShell(opts) {
4191
3156
  try {
4192
- const { body } = await personalPost(opts, PERSONAL_RESUMES_BASE, {}, freshIdempotencyKey());
4193
- return unwrapDataResponse(body).enc_id;
3157
+ const { body } = await personalPost(opts, import_core45.PERSONAL_RESUMES_BASE, {}, freshIdempotencyKey());
3158
+ return (0, import_core44.unwrapDataResponse)(body).enc_id;
4194
3159
  } catch (err) {
4195
3160
  if (err instanceof ServerClientHttpError) {
4196
3161
  const code = extractErrorCode(err.body);
@@ -4211,7 +3176,7 @@ async function createShell(opts) {
4211
3176
  }
4212
3177
  }
4213
3178
  async function renameResume(opts, encId, name) {
4214
- await personalPut(opts, `${PERSONAL_RESUMES_BASE}/${encodeURIComponent(encId)}/name`, { name }, freshIdempotencyKey());
3179
+ await personalPut(opts, `${import_core45.PERSONAL_RESUMES_BASE}/${encodeURIComponent(encId)}/name`, { name }, freshIdempotencyKey());
4215
3180
  }
4216
3181
  function toWorkExperienceWriteItem(item) {
4217
3182
  const { is_current, ...rest } = item;
@@ -4223,8 +3188,8 @@ function splitItemEncId(item) {
4223
3188
  return [valid, rest];
4224
3189
  }
4225
3190
  async function writeSection(opts, encId, key, value, mode) {
4226
- const plan = SECTION_WRITE_PLAN[key];
4227
- const base = `${PERSONAL_RESUMES_BASE}/${encodeURIComponent(encId)}`;
3191
+ const plan = import_core45.SECTION_WRITE_PLAN[key];
3192
+ const base = `${import_core45.PERSONAL_RESUMES_BASE}/${encodeURIComponent(encId)}`;
4228
3193
  switch (plan.kind) {
4229
3194
  case "per-item-post": {
4230
3195
  for (const item of value) {
@@ -4291,7 +3256,7 @@ async function createAggregate(opts, aggregate) {
4291
3256
  if (aggregate.name !== void 0) {
4292
3257
  reports.push(await attemptStep("name", () => renameResume(opts, encId, aggregate.name)));
4293
3258
  }
4294
- for (const section of SECTIONS) {
3259
+ for (const section of import_core46.SECTIONS) {
4295
3260
  const value = aggregate[section.key];
4296
3261
  if (value === void 0) continue;
4297
3262
  reports.push(await attemptStep(section.key, () => writeSection(opts, encId, section.key, value, "create")));
@@ -4301,9 +3266,9 @@ async function createAggregate(opts, aggregate) {
4301
3266
  async function updateAggregate(opts, encId, aggregate, onlySection) {
4302
3267
  const reports = [];
4303
3268
  if (onlySection !== void 0) {
4304
- const section = getSection(onlySection);
3269
+ const section = (0, import_core46.getSection)(onlySection);
4305
3270
  if (!section) {
4306
- const allowed = SECTIONS.map((s) => s.key).join(", ");
3271
+ const allowed = import_core46.SECTIONS.map((s) => s.key).join(", ");
4307
3272
  throw new CliError(`Unknown section "${onlySection}". Allowed: ${allowed}`, ExitCode.InvalidArgument);
4308
3273
  }
4309
3274
  const value = aggregate[onlySection];
@@ -4316,7 +3281,7 @@ async function updateAggregate(opts, encId, aggregate, onlySection) {
4316
3281
  if (aggregate.name !== void 0) {
4317
3282
  reports.push(await attemptStep("name", () => renameResume(opts, encId, aggregate.name)));
4318
3283
  }
4319
- for (const section of SECTIONS) {
3284
+ for (const section of import_core46.SECTIONS) {
4320
3285
  const value = aggregate[section.key];
4321
3286
  if (value === void 0) continue;
4322
3287
  reports.push(await attemptStep(section.key, () => writeSection(opts, encId, section.key, value, "update")));
@@ -4327,7 +3292,7 @@ async function updateAggregate(opts, encId, aggregate, onlySection) {
4327
3292
  // src/commands/personal/resumes/create.ts
4328
3293
  async function runResumesCreate(ctx, filePath) {
4329
3294
  const input = readJsonObject(filePath, { timeoutMs: ctx.timeoutMs });
4330
- const issues = validateAggregate(input);
3295
+ const issues = (0, import_core47.validateAggregate)(input);
4331
3296
  if (issues.length > 0) {
4332
3297
  if (ctx.format === "json") {
4333
3298
  printJson({ valid: false, issues });
@@ -4420,6 +3385,8 @@ function registerPersonalResumesUpdate(parent) {
4420
3385
 
4421
3386
  // src/commands/personal/resumes/copy.ts
4422
3387
  var import_node_crypto12 = require("crypto");
3388
+ var import_core48 = require("@wport/core");
3389
+ var import_core49 = require("@wport/core");
4423
3390
  function requireEncId3(encId) {
4424
3391
  const trimmed = encId.trim();
4425
3392
  if (!trimmed) {
@@ -4438,12 +3405,12 @@ function printCopyResult(ctx, sourceEncId, newEncId) {
4438
3405
  async function runResumesCopy(ctx, encId, name) {
4439
3406
  const trimmed = requireEncId3(encId);
4440
3407
  const opts = { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs };
4441
- const { body } = await personalPost(opts, `${PERSONAL_RESUMES_BASE}/${encodeURIComponent(trimmed)}/duplicate`, {}, { idempotencyKey: (0, import_node_crypto12.randomUUID)() });
4442
- const { enc_id: newEncId } = unwrapDataResponse(body);
3408
+ const { body } = await personalPost(opts, `${import_core49.PERSONAL_RESUMES_BASE}/${encodeURIComponent(trimmed)}/duplicate`, {}, { idempotencyKey: (0, import_node_crypto12.randomUUID)() });
3409
+ const { enc_id: newEncId } = (0, import_core48.unwrapDataResponse)(body);
4443
3410
  printCopyResult(ctx, trimmed, newEncId);
4444
3411
  if (name === void 0) return;
4445
3412
  try {
4446
- await personalPut(opts, `${PERSONAL_RESUMES_BASE}/${encodeURIComponent(newEncId)}/name`, { name }, { idempotencyKey: (0, import_node_crypto12.randomUUID)() });
3413
+ await personalPut(opts, `${import_core49.PERSONAL_RESUMES_BASE}/${encodeURIComponent(newEncId)}/name`, { name }, { idempotencyKey: (0, import_node_crypto12.randomUUID)() });
4447
3414
  } catch (err) {
4448
3415
  const reason = err instanceof Error ? err.message : String(err);
4449
3416
  throw new CliError(
@@ -4460,6 +3427,8 @@ function registerPersonalResumesCopy(parent) {
4460
3427
 
4461
3428
  // src/commands/personal/resumes/publish.ts
4462
3429
  var import_node_crypto13 = require("crypto");
3430
+ var import_core50 = require("@wport/core");
3431
+ var import_core51 = require("@wport/core");
4463
3432
  function requireEncId4(encId) {
4464
3433
  const trimmed = encId.trim();
4465
3434
  if (!trimmed) {
@@ -4473,11 +3442,11 @@ async function runResumesPublishTransition(ctx, encId, action) {
4473
3442
  const targetStatus = action === "publish";
4474
3443
  const { body } = await personalPatch(
4475
3444
  opts,
4476
- `${PERSONAL_RESUMES_BASE}/${encodeURIComponent(trimmed)}/published-status`,
3445
+ `${import_core51.PERSONAL_RESUMES_BASE}/${encodeURIComponent(trimmed)}/published-status`,
4477
3446
  { target_status: targetStatus },
4478
3447
  { idempotencyKey: (0, import_node_crypto13.randomUUID)() }
4479
3448
  );
4480
- const result = unwrapDataResponse(body);
3449
+ const result = (0, import_core50.unwrapDataResponse)(body);
4481
3450
  if (ctx.format === "json") {
4482
3451
  printJson({ enc_id: trimmed, is_published: result.is_published });
4483
3452
  return;
@@ -4499,6 +3468,7 @@ function registerPersonalResumesUnpublish(parent) {
4499
3468
 
4500
3469
  // src/commands/personal/resumes/delete.ts
4501
3470
  var import_node_crypto14 = require("crypto");
3471
+ var import_core52 = require("@wport/core");
4502
3472
  function requireEncId5(encId) {
4503
3473
  const trimmed = encId.trim();
4504
3474
  if (!trimmed) {
@@ -4512,7 +3482,7 @@ async function runResumesDelete(ctx, encId, confirm) {
4512
3482
  }
4513
3483
  const trimmed = requireEncId5(encId);
4514
3484
  const opts = { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs };
4515
- await personalDelete(opts, `${PERSONAL_RESUMES_BASE}/${encodeURIComponent(trimmed)}`, { idempotencyKey: (0, import_node_crypto14.randomUUID)() });
3485
+ await personalDelete(opts, `${import_core52.PERSONAL_RESUMES_BASE}/${encodeURIComponent(trimmed)}`, { idempotencyKey: (0, import_node_crypto14.randomUUID)() });
4516
3486
  if (ctx.format === "json") {
4517
3487
  printJson({ enc_id: trimmed, deleted: true });
4518
3488
  return;
@@ -4546,6 +3516,7 @@ function registerPersonalResumesCommand(parent) {
4546
3516
  // src/commands/personal/apply/index.ts
4547
3517
  var import_node_crypto15 = require("crypto");
4548
3518
  var import_node_fs11 = require("fs");
3519
+ var import_core53 = require("@wport/core");
4549
3520
 
4550
3521
  // src/commands/personal/apply/message-input.ts
4551
3522
  var import_node_fs10 = require("fs");
@@ -4627,7 +3598,7 @@ async function runPersonalApply(ctx, args) {
4627
3598
  const body = { enc_job_id: args.encJobId, enc_resume_id: args.encResumeId, application_message: args.message };
4628
3599
  let result;
4629
3600
  try {
4630
- result = await personalPost(opts, PERSONAL_APPLICATIONS_BASE, body, { idempotencyKey: (0, import_node_crypto15.randomUUID)() });
3601
+ result = await personalPost(opts, import_core53.PERSONAL_APPLICATIONS_BASE, body, { idempotencyKey: (0, import_node_crypto15.randomUUID)() });
4631
3602
  } catch (err) {
4632
3603
  rethrowApplyError(err);
4633
3604
  }
@@ -4678,7 +3649,7 @@ async function runPersonalApplyBatch(ctx, args) {
4678
3649
  const opts = { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs };
4679
3650
  let result;
4680
3651
  try {
4681
- result = await personalPost(opts, `${PERSONAL_APPLICATIONS_BASE}/batch`, { applications: items }, { idempotencyKey: (0, import_node_crypto15.randomUUID)() });
3652
+ result = await personalPost(opts, `${import_core53.PERSONAL_APPLICATIONS_BASE}/batch`, { applications: items }, { idempotencyKey: (0, import_node_crypto15.randomUUID)() });
4682
3653
  } catch (err) {
4683
3654
  rethrowApplyError(err);
4684
3655
  }
@@ -4706,7 +3677,7 @@ function registerPersonalCommand(program2) {
4706
3677
 
4707
3678
  // src/index.ts
4708
3679
  var program = new import_commander.Command();
4709
- program.name("wport").description("wport CLI \u2014 terminal interface to the W101 Talent Search Hub public API").version("0.9.0", "-v, --version", "output the CLI version").option("--lang <locale>", "Accept-Language locale: zh-TW | en-US | vi-VN | th-TH | id-ID").option("--api <url>", "override API base URL").option("--output <fmt>", "output format: table | json").option("--no-color", "disable color output").option("--timeout <ms>", "HTTP timeout in milliseconds", (v) => Number(v));
3680
+ program.name("wport").description("wport CLI \u2014 terminal interface to the W101 Talent Search Hub public API").version("0.9.1", "-v, --version", "output the CLI version").option("--lang <locale>", "Accept-Language locale: zh-TW | en-US | vi-VN | th-TH | id-ID").option("--api <url>", "override API base URL").option("--output <fmt>", "output format: table | json").option("--no-color", "disable color output").option("--timeout <ms>", "HTTP timeout in milliseconds", (v) => Number(v));
4710
3681
  registerJobsCommand(program);
4711
3682
  registerConfigCommand(program);
4712
3683
  registerDoctorCommand(program);
@@ -4732,6 +3703,10 @@ function handleTopLevelError(err) {
4732
3703
  printError(err.message, color);
4733
3704
  process.exit(err.exitCode);
4734
3705
  }
3706
+ if ((0, import_core.isWportError)(err)) {
3707
+ printError(err.message, color);
3708
+ process.exit(exitCodeForError(err));
3709
+ }
4735
3710
  const fallbackMessage = err instanceof Error ? err.message : String(err);
4736
3711
  printError(fallbackMessage, color);
4737
3712
  process.exit(ExitCode.ServerOrNetworkError);