@wport/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -179,7 +179,7 @@ function isTimeoutAbort(err) {
179
179
  return false;
180
180
  }
181
181
  function buildUserAgent() {
182
- return `wport-cli/${"0.4.0"} (node ${process.version}; ${process.platform})`;
182
+ return `wport-cli/${"0.5.0"} (node ${process.version}; ${process.platform})`;
183
183
  }
184
184
  function unwrapDataResponse(body) {
185
185
  if (body && typeof body === "object" && "success" in body && "data" in body) {
@@ -663,6 +663,25 @@ function readJsonInput(source, options = {}) {
663
663
  throw new InvalidArgumentError("Invalid JSON in input (parse failed)");
664
664
  }
665
665
  }
666
+ function readTextInput(source, options = {}) {
667
+ const readStdin = options.readStdin ?? ((label) => readPipedStdin(label, { timeoutMs: options.timeoutMs }));
668
+ let raw;
669
+ if (source === "-") {
670
+ raw = readStdin("--body-file -");
671
+ } else {
672
+ try {
673
+ raw = (0, import_node_fs3.readFileSync)(source, "utf8");
674
+ } catch (err) {
675
+ throw new InvalidArgumentError(
676
+ `Cannot read --body-file "${source}": ${err.code ?? err.message}`
677
+ );
678
+ }
679
+ }
680
+ if (!raw.trim()) {
681
+ throw new InvalidArgumentError("Input is empty \u2014 expected message body text");
682
+ }
683
+ return raw;
684
+ }
666
685
  function readJsonObject(source, options = {}) {
667
686
  const parsed = readJsonInput(source, options);
668
687
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -959,7 +978,7 @@ function registerDoctorCommand(program2) {
959
978
  ).action(async (_opts, command) => {
960
979
  const ctx = resolveContext(command);
961
980
  const line = (s = "") => process.stdout.write(s + "\n");
962
- line(`wport-cli ${"0.4.0"}`);
981
+ line(`wport-cli ${"0.5.0"}`);
963
982
  line(` bundled schema fingerprint: ${"839e8a891dfb"}`);
964
983
  line("");
965
984
  line("Resolved configuration:");
@@ -1251,6 +1270,41 @@ function registerEnterpriseWhoami(parent) {
1251
1270
  });
1252
1271
  }
1253
1272
 
1273
+ // src/commands/enterprise/usage.ts
1274
+ function num(value) {
1275
+ return typeof value === "number" && Number.isFinite(value) ? String(value) : "\u2014";
1276
+ }
1277
+ async function runUsage(ctx, apiKey) {
1278
+ const { body } = await enterpriseGet(
1279
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1280
+ "/usage"
1281
+ );
1282
+ const usage = unwrapDataResponse(body);
1283
+ if (ctx.format === "json") {
1284
+ printJson(usage);
1285
+ return;
1286
+ }
1287
+ const quota = usage.quota ?? {};
1288
+ const rate = usage.rate_limit ?? {};
1289
+ const lines = [
1290
+ `period: ${usage.period ?? "\u2014"}`,
1291
+ `monthly quota: ${num(quota.used)} / ${num(quota.limit)} used (${num(quota.remaining)} remaining)`,
1292
+ `rate limit: ${num(rate.limit)} requests / ${num(rate.window_seconds)}s per key`
1293
+ ];
1294
+ process.stdout.write(lines.join("\n") + "\n");
1295
+ process.stdout.write(
1296
+ dim("Live per-window rate-limit headroom is reported via response headers on write requests.", ctx.color) + "\n"
1297
+ );
1298
+ }
1299
+ function registerEnterpriseUsage(parent) {
1300
+ parent.command("usage").description("Show this month API quota usage and rate-limit ceiling").action(async (_flags, command) => {
1301
+ const ctx = resolveContext(command);
1302
+ const globals = command.optsWithGlobals();
1303
+ const { key } = resolveApiKey(globals.apiKey);
1304
+ await runUsage(ctx, key);
1305
+ });
1306
+ }
1307
+
1254
1308
  // src/commands/enterprise/jobs/list.ts
1255
1309
  var STATUS_MAP = { published: 1, unpublished: 0 };
1256
1310
  var MINIMAL_LIST_FIELDS = ["enc_id", "job_title", "status", "updated_at"];
@@ -1270,44 +1324,51 @@ function formatStatus(status) {
1270
1324
  function formatDate2(value) {
1271
1325
  return value ? String(value).slice(0, 10) : "";
1272
1326
  }
1327
+ function formatCount(value) {
1328
+ return typeof value === "number" && Number.isFinite(value) ? String(value) : "\u2014";
1329
+ }
1330
+ async function runEnterpriseJobsList(ctx, apiKey, flags) {
1331
+ if (flags.fields && flags.minimal) {
1332
+ throw new CliError("Use either --fields or --minimal, not both", ExitCode.InvalidArgument);
1333
+ }
1334
+ const { body } = await enterpriseGet(
1335
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1336
+ "/jobs",
1337
+ {
1338
+ currentPage: flags.page,
1339
+ pageSize: flags.pageSize,
1340
+ keyword: flags.keyword,
1341
+ status: mapStatusFlag(flags.status)
1342
+ }
1343
+ );
1344
+ const paged = asPaginatedBody(body);
1345
+ const projection = flags.minimal ? MINIMAL_LIST_FIELDS : flags.fields ? parseFieldsList(flags.fields) : void 0;
1346
+ if (projection || ctx.format === "json") {
1347
+ printJson(projection ? { ...paged, data: paged.data.map((row) => pickPaths(row, projection)) } : paged);
1348
+ return;
1349
+ }
1350
+ printTable(
1351
+ paged.data,
1352
+ [
1353
+ { header: "ENC_ID", value: (r) => (r.enc_id ?? "").slice(0, 14) },
1354
+ { header: "TITLE", value: (r) => r.job_title ?? "", maxWidth: 36 },
1355
+ { header: "STATUS", value: (r) => formatStatus(r.status) },
1356
+ { header: "CLICKS_7D", value: (r) => formatCount(r.clicks_7d) },
1357
+ { header: "PUBLISHED", value: (r) => formatDate2(r.published_at), maxWidth: 12 },
1358
+ { header: "UPDATED", value: (r) => formatDate2(r.updated_at), maxWidth: 12 }
1359
+ ],
1360
+ ctx.color
1361
+ );
1362
+ const head = `Showing page ${paged.currentPage}/${paged.totalPages} (${paged.data.length} of ${paged.totalCount} jobs).`;
1363
+ const hint = paged.totalPages > paged.currentPage ? ` Next: wport enterprise jobs list --page ${paged.currentPage + 1}` : "";
1364
+ process.stdout.write(dim(head + hint, ctx.color) + "\n");
1365
+ }
1273
1366
  function registerEnterpriseJobsList(parent) {
1274
1367
  parent.command("list").description("List your company job postings").option("--page <n>", "page number (server: currentPage, default 1)", (v) => Number(v)).option("--page-size <n>", "items per page (server: pageSize, default 10, max 100)", (v) => Number(v)).option("--keyword <kw>", "filter by job title keyword").option("--status <state>", "filter by status: published | unpublished").option("--fields <list>", "output selected fields as JSON (comma-separated dotted paths)").option("--minimal", `output only ${MINIMAL_LIST_FIELDS.join(",")} as JSON`).action(async (flags, command) => {
1275
1368
  const ctx = resolveContext(command);
1276
- if (flags.fields && flags.minimal) {
1277
- throw new CliError("Use either --fields or --minimal, not both", ExitCode.InvalidArgument);
1278
- }
1279
1369
  const globals = command.optsWithGlobals();
1280
1370
  const { key } = resolveApiKey(globals.apiKey);
1281
- const { body } = await enterpriseGet(
1282
- { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey: key },
1283
- "/jobs",
1284
- {
1285
- currentPage: flags.page,
1286
- pageSize: flags.pageSize,
1287
- keyword: flags.keyword,
1288
- status: mapStatusFlag(flags.status)
1289
- }
1290
- );
1291
- const paged = asPaginatedBody(body);
1292
- const projection = flags.minimal ? MINIMAL_LIST_FIELDS : flags.fields ? parseFieldsList(flags.fields) : void 0;
1293
- if (projection || ctx.format === "json") {
1294
- printJson(projection ? { ...paged, data: paged.data.map((row) => pickPaths(row, projection)) } : paged);
1295
- return;
1296
- }
1297
- printTable(
1298
- paged.data,
1299
- [
1300
- { header: "ENC_ID", value: (r) => (r.enc_id ?? "").slice(0, 14) },
1301
- { header: "TITLE", value: (r) => r.job_title ?? "", maxWidth: 36 },
1302
- { header: "STATUS", value: (r) => formatStatus(r.status) },
1303
- { header: "CODE", value: (r) => r.code ?? "", maxWidth: 16 },
1304
- { header: "UPDATED", value: (r) => formatDate2(r.updated_at), maxWidth: 12 }
1305
- ],
1306
- ctx.color
1307
- );
1308
- const head = `Showing page ${paged.currentPage}/${paged.totalPages} (${paged.data.length} of ${paged.totalCount} jobs).`;
1309
- const hint = paged.totalPages > paged.currentPage ? ` Next: wport enterprise jobs list --page ${paged.currentPage + 1}` : "";
1310
- process.stdout.write(dim(head + hint, ctx.color) + "\n");
1371
+ await runEnterpriseJobsList(ctx, key, flags);
1311
1372
  });
1312
1373
  }
1313
1374
 
@@ -1449,6 +1510,58 @@ async function runJobsDelete(ctx, apiKey, encId, confirm, idempotencyKey) {
1449
1510
  process.stdout.write(`Deleted job: ${trimmed}
1450
1511
  `);
1451
1512
  }
1513
+ async function runJobsCopy(ctx, apiKey, encId, idempotencyKey) {
1514
+ const trimmed = requireEncId(encId);
1515
+ const { body } = await enterprisePost(
1516
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1517
+ `/jobs/${encodeURIComponent(trimmed)}/copy`,
1518
+ {},
1519
+ { idempotencyKey }
1520
+ );
1521
+ const result = unwrapDataResponse(body);
1522
+ if (ctx.format === "json") {
1523
+ printJson(result);
1524
+ return;
1525
+ }
1526
+ process.stdout.write(`Copied job ${trimmed} \u2192 new draft: ${result.enc_id ?? "(unknown)"}
1527
+ `);
1528
+ }
1529
+ function registerEnterpriseJobsCopy(parent) {
1530
+ parent.command("copy <enc_id>").description("Copy a job posting into a new unpublished draft").option("--idempotency-key <key>", "reuse across retries (default: a fresh UUID)").action(async (encId, flags, command) => {
1531
+ const ctx = resolveContext(command);
1532
+ const { key } = resolveApiKey(command.optsWithGlobals().apiKey);
1533
+ await runJobsCopy(ctx, key, encId, flags.idempotencyKey ?? (0, import_node_crypto3.randomUUID)());
1534
+ });
1535
+ }
1536
+ async function runJobsClose(ctx, apiKey, encId, confirm, idempotencyKey) {
1537
+ if (!confirm) {
1538
+ throw new CliError(
1539
+ "Refusing to close without --confirm (irreversible; a closed job cannot be reopened \u2014 copy it to relist)",
1540
+ ExitCode.InvalidArgument
1541
+ );
1542
+ }
1543
+ const trimmed = requireEncId(encId);
1544
+ const { body } = await enterprisePatch(
1545
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1546
+ `/jobs/${encodeURIComponent(trimmed)}/close`,
1547
+ {},
1548
+ { idempotencyKey }
1549
+ );
1550
+ const result = unwrapDataResponse(body);
1551
+ if (ctx.format === "json") {
1552
+ printJson(result);
1553
+ return;
1554
+ }
1555
+ process.stdout.write(`Closed job: ${result.enc_id ?? trimmed}
1556
+ `);
1557
+ }
1558
+ function registerEnterpriseJobsClose(parent) {
1559
+ parent.command("close <enc_id>").description("Close (finalize) a job posting (destructive, irreversible; requires --confirm)").option("--confirm", "confirm this irreversible close (a closed job cannot be reopened)").option("--idempotency-key <key>", "reuse across retries (default: a fresh UUID)").action(async (encId, flags, command) => {
1560
+ const ctx = resolveContext(command);
1561
+ const { key } = resolveApiKey(command.optsWithGlobals().apiKey);
1562
+ await runJobsClose(ctx, key, encId, flags.confirm === true, flags.idempotencyKey ?? (0, import_node_crypto3.randomUUID)());
1563
+ });
1564
+ }
1452
1565
  function registerEnterpriseJobsPublish(parent) {
1453
1566
  parent.command("publish <enc_id>").description("Publish a job posting").option("--idempotency-key <key>", "reuse across retries (default: a fresh UUID)").action(async (encId, flags, command) => {
1454
1567
  const ctx = resolveContext(command);
@@ -1530,6 +1643,8 @@ function registerEnterpriseJobsCommand(parent) {
1530
1643
  registerEnterpriseJobsPublish(jobs);
1531
1644
  registerEnterpriseJobsUnpublish(jobs);
1532
1645
  registerEnterpriseJobsDelete(jobs);
1646
+ registerEnterpriseJobsCopy(jobs);
1647
+ registerEnterpriseJobsClose(jobs);
1533
1648
  registerEnterpriseJobsBatch(jobs);
1534
1649
  }
1535
1650
 
@@ -1636,19 +1751,733 @@ function registerEnterpriseKeysCommand(parent) {
1636
1751
  registerEnterpriseKeysRotate(keys);
1637
1752
  }
1638
1753
 
1754
+ // src/commands/enterprise/company/view.ts
1755
+ var COMPANY_STATUS_LABELS = {
1756
+ 0: "not_submitted",
1757
+ 1: "pending_review",
1758
+ 2: "approved",
1759
+ 3: "rejected"
1760
+ };
1761
+ function formatCompanyStatus(status) {
1762
+ if (status === void 0) return "";
1763
+ return COMPANY_STATUS_LABELS[status] ?? String(status);
1764
+ }
1765
+ function formatPhone(company) {
1766
+ const code = company.phone_code ?? void 0;
1767
+ const number = company.phone_number ?? void 0;
1768
+ if (!code && !number) return void 0;
1769
+ return [code, number].filter(Boolean).join(" ");
1770
+ }
1771
+ function formatCapital(company) {
1772
+ if (company.capital_amount === null || company.capital_amount === void 0) return void 0;
1773
+ if (company.capital_show_status === 0) return "not displayed";
1774
+ return String(company.capital_amount);
1775
+ }
1776
+ function formatAddress(company) {
1777
+ return company.address ?? void 0;
1778
+ }
1779
+ var DETAIL_FIELDS2 = [
1780
+ "name",
1781
+ "uniform_number",
1782
+ "status",
1783
+ "website",
1784
+ "phone",
1785
+ "address",
1786
+ "capital",
1787
+ "logo_url"
1788
+ ];
1789
+ function renderDetailLines2(company) {
1790
+ const pad = Math.max(...DETAIL_FIELDS2.map((f) => f.length)) + 1;
1791
+ const derived = {
1792
+ name: company.name,
1793
+ uniform_number: company.uniform_number ?? void 0,
1794
+ status: formatCompanyStatus(company.status),
1795
+ website: company.website ?? void 0,
1796
+ phone: formatPhone(company),
1797
+ address: formatAddress(company),
1798
+ capital: formatCapital(company),
1799
+ logo_url: company.logo_url ?? void 0
1800
+ };
1801
+ const lines = [];
1802
+ for (const field of DETAIL_FIELDS2) {
1803
+ const value = derived[field];
1804
+ if (value === null || value === void 0 || value === "") continue;
1805
+ lines.push(`${(field + ":").padEnd(pad + 1)}${sanitizeForTerminal(value)}`);
1806
+ }
1807
+ return lines;
1808
+ }
1809
+ async function runCompanyView(ctx, apiKey, flags) {
1810
+ const { body } = await enterpriseGet(
1811
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1812
+ "/company"
1813
+ );
1814
+ const company = unwrapDataResponse(body);
1815
+ if (flags.fields) {
1816
+ printJson(pickPaths(company, parseFieldsList(flags.fields)));
1817
+ return;
1818
+ }
1819
+ if (ctx.format === "json") {
1820
+ printJson(company);
1821
+ return;
1822
+ }
1823
+ process.stdout.write(renderDetailLines2(company).join("\n") + "\n");
1824
+ }
1825
+ function registerEnterpriseCompanyView(parent) {
1826
+ parent.command("view").description("View your company information").option("--fields <list>", "output selected fields as JSON (comma-separated dotted paths)").action(async (flags, command) => {
1827
+ const ctx = resolveContext(command);
1828
+ const globals = command.optsWithGlobals();
1829
+ const { key } = resolveApiKey(globals.apiKey);
1830
+ await runCompanyView(ctx, key, flags);
1831
+ });
1832
+ }
1833
+
1834
+ // src/commands/enterprise/company/update.ts
1835
+ var import_node_crypto5 = require("crypto");
1836
+
1837
+ // src/commands/enterprise/company/types.ts
1838
+ var BASIC_FIELDS = [
1839
+ "name",
1840
+ "industry_category_code",
1841
+ "phone_code",
1842
+ "phone_number",
1843
+ "employee_count_range_code",
1844
+ "capital_amount",
1845
+ "capital_show_status",
1846
+ "website",
1847
+ "area_code",
1848
+ "address"
1849
+ ];
1850
+ var DESCRIPTION_FIELDS = ["description", "products_services", "latest_news"];
1851
+ var FORBIDDEN_FIELDS = [
1852
+ "uniform_number",
1853
+ "banner_url",
1854
+ "photo_1",
1855
+ "photo_2",
1856
+ "photo_3",
1857
+ "video_1",
1858
+ "video_2",
1859
+ "video_3",
1860
+ "foundation_date",
1861
+ "representative",
1862
+ "directors",
1863
+ "milestones",
1864
+ "awards",
1865
+ "qa"
1866
+ ];
1867
+
1868
+ // src/commands/enterprise/company/update.ts
1869
+ var REQUIRED_BASIC_FIELDS = [
1870
+ "name",
1871
+ "industry_category_code",
1872
+ "phone_code",
1873
+ "phone_number",
1874
+ "area_code",
1875
+ "address"
1876
+ ];
1877
+ var CONTRACT_REQUIRED_GET_FIELDS = [
1878
+ "industry_category_code",
1879
+ "area_code",
1880
+ "employee_count_range_code",
1881
+ "capital_amount",
1882
+ "capital_show_status",
1883
+ "latest_news",
1884
+ "uniform_number"
1885
+ ];
1886
+ async function buildCompanyUpdatePayloads(input, ctx, apiKey) {
1887
+ const forbiddenFound = Object.keys(input).filter((k) => FORBIDDEN_FIELDS.includes(k));
1888
+ if (forbiddenFound.length > 0) {
1889
+ throw new InvalidArgumentError(`Field(s) not writable via CLI: ${forbiddenFound.join(", ")}`);
1890
+ }
1891
+ const writableFields = /* @__PURE__ */ new Set([...BASIC_FIELDS, ...DESCRIPTION_FIELDS]);
1892
+ const unknownFound = Object.keys(input).filter((k) => !writableFields.has(k));
1893
+ if (unknownFound.length > 0) {
1894
+ throw new InvalidArgumentError(`Unknown field(s): ${unknownFound.join(", ")}`);
1895
+ }
1896
+ const inputHasBasicField = BASIC_FIELDS.some((f) => f in input);
1897
+ const inputHasDescriptionField = DESCRIPTION_FIELDS.some((f) => f in input);
1898
+ if (!inputHasBasicField && !inputHasDescriptionField) {
1899
+ throw new InvalidArgumentError("No writable company fields provided");
1900
+ }
1901
+ const { body } = await enterpriseGet(
1902
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
1903
+ "/company"
1904
+ );
1905
+ const current = unwrapDataResponse(body);
1906
+ const missingContractFields = CONTRACT_REQUIRED_GET_FIELDS.filter((f) => !(f in current));
1907
+ if (missingContractFields.length > 0) {
1908
+ throw new CliError(
1909
+ `Backend contract appears broken: GET /company response is missing field(s): ${missingContractFields.join(", ")}`,
1910
+ ExitCode.ServerOrNetworkError
1911
+ );
1912
+ }
1913
+ let basic = null;
1914
+ if (inputHasBasicField) {
1915
+ const merged = {};
1916
+ for (const field of BASIC_FIELDS) {
1917
+ merged[field] = field in input ? input[field] : current[field];
1918
+ }
1919
+ const missingRequired = REQUIRED_BASIC_FIELDS.filter((f) => {
1920
+ const v = merged[f];
1921
+ return v === null || v === void 0 || v === "";
1922
+ });
1923
+ if (missingRequired.length > 0) {
1924
+ const [first] = missingRequired;
1925
+ throw new InvalidArgumentError(
1926
+ `Missing required field '${first}' \u2014 not provided in input and not present on your current company profile` + (missingRequired.length > 1 ? ` (also missing: ${missingRequired.slice(1).join(", ")})` : "")
1927
+ );
1928
+ }
1929
+ basic = merged;
1930
+ }
1931
+ let descriptions = null;
1932
+ if (inputHasDescriptionField) {
1933
+ descriptions = {};
1934
+ for (const field of DESCRIPTION_FIELDS) {
1935
+ if (field in input) descriptions[field] = input[field];
1936
+ }
1937
+ }
1938
+ return { basic, descriptions };
1939
+ }
1940
+ function isDescriptionsPartialFailureBody(body) {
1941
+ return !!body && typeof body === "object" && Array.isArray(body.updated_sections) && typeof body.failed_section === "string";
1942
+ }
1943
+ function printCompanyResult(company, format) {
1944
+ if (format === "json") {
1945
+ printJson(company);
1946
+ return;
1947
+ }
1948
+ process.stdout.write(`Updated company: ${company.name ?? ""}
1949
+ `);
1950
+ }
1951
+ function resolveUpdateIdempotencyKeys(needsBasic, needsDescriptions, flags) {
1952
+ const bothNeeded = needsBasic && needsDescriptions;
1953
+ if (bothNeeded && flags.idempotencyKey !== void 0) {
1954
+ throw new InvalidArgumentError(
1955
+ "Both basic and descriptions sections changed: use --basic-idempotency-key and --descriptions-idempotency-key instead of --idempotency-key, to avoid reusing the same key for two different payloads"
1956
+ );
1957
+ }
1958
+ return {
1959
+ basicKey: needsBasic ? flags.basicIdempotencyKey ?? flags.idempotencyKey ?? (0, import_node_crypto5.randomUUID)() : void 0,
1960
+ descriptionsKey: needsDescriptions ? flags.descriptionsIdempotencyKey ?? flags.idempotencyKey ?? (0, import_node_crypto5.randomUUID)() : void 0
1961
+ };
1962
+ }
1963
+ async function runCompanyUpdate(ctx, apiKey, source, idempotencyFlags, options = {}) {
1964
+ const input = readJsonObject(source, options);
1965
+ const needsBasic = BASIC_FIELDS.some((f) => f in input);
1966
+ const needsDescriptions = DESCRIPTION_FIELDS.some((f) => f in input);
1967
+ const { basicKey, descriptionsKey } = resolveUpdateIdempotencyKeys(needsBasic, needsDescriptions, idempotencyFlags);
1968
+ const payloads = await buildCompanyUpdatePayloads(input, ctx, apiKey);
1969
+ const requestOpts = { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey };
1970
+ let basicResultCompany;
1971
+ if (needsBasic) {
1972
+ const { body } = await enterprisePatch(requestOpts, "/company/basic", payloads.basic, {
1973
+ idempotencyKey: basicKey
1974
+ });
1975
+ basicResultCompany = unwrapDataResponse(body);
1976
+ }
1977
+ if (!needsDescriptions) {
1978
+ printCompanyResult(basicResultCompany, ctx.format);
1979
+ return;
1980
+ }
1981
+ try {
1982
+ const { body } = await enterprisePatch(
1983
+ requestOpts,
1984
+ "/company/descriptions",
1985
+ payloads.descriptions,
1986
+ { idempotencyKey: descriptionsKey }
1987
+ );
1988
+ const result = unwrapDataResponse(body);
1989
+ printCompanyResult(result.company, ctx.format);
1990
+ } catch (err) {
1991
+ if (!needsBasic) {
1992
+ throw err;
1993
+ }
1994
+ const descriptionsBody = err instanceof ServerClientHttpError ? err.body : void 0;
1995
+ const partial = isDescriptionsPartialFailureBody(descriptionsBody) ? descriptionsBody : void 0;
1996
+ if (ctx.format === "json") {
1997
+ printJson({
1998
+ basic_updated: true,
1999
+ basic_company: basicResultCompany,
2000
+ descriptions_error: partial ? { updated_sections: partial.updated_sections, failed_section: partial.failed_section } : { message: err instanceof Error ? err.message : String(err) }
2001
+ });
2002
+ } else {
2003
+ process.stdout.write("Basic company info was updated successfully.\n");
2004
+ if (partial) {
2005
+ process.stdout.write(
2006
+ `Updated sections before failure: ${(partial.updated_sections ?? []).join(", ")}; failed section: ${partial.failed_section}
2007
+ `
2008
+ );
2009
+ } else {
2010
+ process.stdout.write(
2011
+ `Descriptions update failed entirely: ${err instanceof Error ? err.message : String(err)}
2012
+ `
2013
+ );
2014
+ }
2015
+ }
2016
+ throw err;
2017
+ }
2018
+ }
2019
+ function registerEnterpriseCompanyUpdate(parent) {
2020
+ parent.command("update").description(
2021
+ "Update your company basic info and/or descriptions from a JSON file (uniform_number is read-only and cannot be changed via this command)"
2022
+ ).requiredOption("--file <path>", 'path to a partial JSON company body, or "-" for stdin').option("--idempotency-key <key>", "single-section update only: reuse across retries (default: a fresh UUID)").option("--basic-idempotency-key <key>", "two-section update: idempotency key for PATCH /company/basic").option(
2023
+ "--descriptions-idempotency-key <key>",
2024
+ "two-section update: idempotency key for PATCH /company/descriptions"
2025
+ ).action(async (flags, command) => {
2026
+ const ctx = resolveContext(command);
2027
+ const globals = command.optsWithGlobals();
2028
+ const { key } = resolveApiKey(globals.apiKey);
2029
+ await runCompanyUpdate(
2030
+ ctx,
2031
+ key,
2032
+ flags.file,
2033
+ {
2034
+ idempotencyKey: flags.idempotencyKey,
2035
+ basicIdempotencyKey: flags.basicIdempotencyKey,
2036
+ descriptionsIdempotencyKey: flags.descriptionsIdempotencyKey
2037
+ },
2038
+ { timeoutMs: ctx.timeoutMs }
2039
+ );
2040
+ });
2041
+ }
2042
+
2043
+ // src/commands/enterprise/company/logo.ts
2044
+ var import_node_crypto6 = require("crypto");
2045
+ var import_node_fs7 = require("fs");
2046
+ var import_node_path3 = require("path");
2047
+ var EXTENSION_TO_CONTENT_TYPE = {
2048
+ ".png": "image/png",
2049
+ ".jpg": "image/jpeg",
2050
+ ".jpeg": "image/jpeg"
2051
+ };
2052
+ var COMPANY_LOGO_MAX_SIZE = 2 * 1024 * 1024;
2053
+ function inspectLocalFile(path) {
2054
+ if (!(0, import_node_fs7.existsSync)(path)) {
2055
+ throw new InvalidArgumentError(`File not found: ${path}`);
2056
+ }
2057
+ const stat = (0, import_node_fs7.statSync)(path);
2058
+ if (!stat.isFile()) {
2059
+ throw new InvalidArgumentError(`Not a regular file: ${path}`);
2060
+ }
2061
+ const ext = (0, import_node_path3.extname)(path).toLowerCase();
2062
+ const contentType = EXTENSION_TO_CONTENT_TYPE[ext];
2063
+ if (!contentType) {
2064
+ throw new InvalidArgumentError(
2065
+ `Unsupported file extension "${ext || "(none)"}" \u2014 allowed: ${Object.keys(EXTENSION_TO_CONTENT_TYPE).join(", ")}`
2066
+ );
2067
+ }
2068
+ if (stat.size <= 0) {
2069
+ throw new InvalidArgumentError(`File is empty: ${path}`);
2070
+ }
2071
+ if (stat.size > COMPANY_LOGO_MAX_SIZE) {
2072
+ throw new InvalidArgumentError(`File too large: ${stat.size} bytes (max ${COMPANY_LOGO_MAX_SIZE} bytes / 2MB)`);
2073
+ }
2074
+ const bytes = (0, import_node_fs7.readFileSync)(path);
2075
+ return { contentType, fileSize: stat.size, bytes };
2076
+ }
2077
+ function resolveLogoIdempotencyKeys(flags) {
2078
+ if (flags.idempotencyKey) {
2079
+ return {
2080
+ presignKey: `${flags.idempotencyKey}-presign`,
2081
+ confirmKey: `${flags.idempotencyKey}-confirm`
2082
+ };
2083
+ }
2084
+ return { presignKey: (0, import_node_crypto6.randomUUID)(), confirmKey: (0, import_node_crypto6.randomUUID)() };
2085
+ }
2086
+ function printLogoResult(result, format) {
2087
+ if (format === "json") {
2088
+ printJson(result);
2089
+ return;
2090
+ }
2091
+ process.stdout.write(`Updated logo: ${result.logo_url}
2092
+ `);
2093
+ }
2094
+ async function runCompanyLogoUpload(ctx, apiKey, path, idempotencyFlags) {
2095
+ const { contentType, fileSize, bytes } = inspectLocalFile(path);
2096
+ const { presignKey, confirmKey } = resolveLogoIdempotencyKeys(idempotencyFlags);
2097
+ const requestOpts = { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey };
2098
+ const { body: presignBody } = await enterprisePost(
2099
+ requestOpts,
2100
+ "/company/logo/presign",
2101
+ { content_type: contentType, file_size: fileSize },
2102
+ { idempotencyKey: presignKey }
2103
+ );
2104
+ const presign = unwrapDataResponse(presignBody);
2105
+ const putRequest = new Request(presign.upload_url, {
2106
+ method: "PUT",
2107
+ headers: { "Content-Type": contentType },
2108
+ body: bytes
2109
+ });
2110
+ const putResponse = await fetchWithTimeout(putRequest, ctx.timeoutMs);
2111
+ if (!putResponse.ok) {
2112
+ throw new NetworkError(`Failed to upload file to S3: HTTP ${putResponse.status}`);
2113
+ }
2114
+ const { body: confirmBody } = await enterprisePost(
2115
+ requestOpts,
2116
+ "/company/logo/confirm",
2117
+ { s3_key: presign.s3_key },
2118
+ { idempotencyKey: confirmKey }
2119
+ );
2120
+ const result = unwrapDataResponse(confirmBody);
2121
+ printLogoResult(result, ctx.format);
2122
+ }
2123
+ function registerEnterpriseCompanyLogo(parent) {
2124
+ const logo = parent.command("logo").description("Manage your company logo");
2125
+ logo.command("upload <path>").description("Upload and set your company logo (presign + upload + confirm)").option("--idempotency-key <key>", "base key for presign/confirm (each derives its own; default: fresh UUIDs)").action(async (path, flags, command) => {
2126
+ const ctx = resolveContext(command);
2127
+ const globals = command.optsWithGlobals();
2128
+ const { key } = resolveApiKey(globals.apiKey);
2129
+ await runCompanyLogoUpload(ctx, key, path, { idempotencyKey: flags.idempotencyKey });
2130
+ });
2131
+ }
2132
+
2133
+ // src/commands/enterprise/company/index.ts
2134
+ function registerEnterpriseCompanyCommand(parent) {
2135
+ const company = parent.command("company").description("View and manage your company information");
2136
+ registerEnterpriseCompanyView(company);
2137
+ registerEnterpriseCompanyUpdate(company);
2138
+ registerEnterpriseCompanyLogo(company);
2139
+ }
2140
+
2141
+ // src/commands/enterprise/talents/list.ts
2142
+ var DEFAULT_PAGE_SIZE = 20;
2143
+ var MINIMAL_LIST_FIELDS2 = ["enc_resume_id", "candidate_name", "applied_job_title", "applied_at"];
2144
+ function formatDate4(value) {
2145
+ return value ? String(value).slice(0, 10) : "";
2146
+ }
2147
+ async function runEnterpriseTalentsList(ctx, apiKey, flags) {
2148
+ if (flags.fields && flags.minimal) {
2149
+ throw new CliError("Use either --fields or --minimal, not both", ExitCode.InvalidArgument);
2150
+ }
2151
+ const { body } = await enterpriseGet(
2152
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2153
+ "/talents",
2154
+ {
2155
+ tab: flags.tab,
2156
+ enc_job_id: flags.job,
2157
+ keyword: flags.keyword,
2158
+ start_date: flags.from,
2159
+ end_date: flags.to,
2160
+ currentPage: flags.page,
2161
+ pageSize: flags.pageSize ?? DEFAULT_PAGE_SIZE
2162
+ }
2163
+ );
2164
+ const paged = asPaginatedBody(body);
2165
+ const projection = flags.minimal ? MINIMAL_LIST_FIELDS2 : flags.fields ? parseFieldsList(flags.fields) : void 0;
2166
+ if (projection || ctx.format === "json") {
2167
+ printJson(projection ? { ...paged, data: paged.data.map((row) => pickPaths(row, projection)) } : paged);
2168
+ return;
2169
+ }
2170
+ printTable(
2171
+ paged.data,
2172
+ [
2173
+ { header: "RESUME_ID", value: (r) => (r.enc_resume_id ?? "").slice(0, 14) },
2174
+ { header: "CANDIDATE", value: (r) => r.candidate_name ?? "", maxWidth: 20 },
2175
+ { header: "EDUCATION", value: (r) => r.highest_education ?? "", maxWidth: 18 },
2176
+ { header: "EXPERIENCE", value: (r) => r.latest_experience ?? "", maxWidth: 28 },
2177
+ { header: "APPLIED_JOB", value: (r) => r.applied_job_title ?? "", maxWidth: 24 },
2178
+ { header: "APPLIED_AT", value: (r) => formatDate4(r.applied_at), maxWidth: 12 },
2179
+ { header: "VIEWED", value: (r) => r.is_viewed ? "yes" : "no" }
2180
+ ],
2181
+ ctx.color
2182
+ );
2183
+ const head = `Showing page ${paged.currentPage}/${paged.totalPages} (${paged.data.length} of ${paged.totalCount} applicants).`;
2184
+ const hint = paged.totalPages > paged.currentPage ? ` Next: wport enterprise talents list --page ${paged.currentPage + 1}` : "";
2185
+ process.stdout.write(dim(head + hint, ctx.color) + "\n");
2186
+ }
2187
+ function registerEnterpriseTalentsList(parent) {
2188
+ parent.command("list").description("List applicants in your talent pool").option("--tab <tab>", "applied | visit (default applied; visit is not yet supported by the server)").option("--job <enc_job_id>", "filter by job posting enc_id").option("-k, --keyword <kw>", "filter by candidate name keyword").option("--from <date>", "applied on/after this date (YYYY-MM-DD)").option("--to <date>", "applied on/before this date (YYYY-MM-DD)").option("-p, --page <n>", "page number (server: currentPage, default 1)", (v) => Number(v)).option("-s, --page-size <n>", `items per page (default ${DEFAULT_PAGE_SIZE}, max 100)`, (v) => Number(v)).option("--fields <list>", "output selected fields as JSON (comma-separated dotted paths)").option("--minimal", `output only ${MINIMAL_LIST_FIELDS2.join(",")} as JSON`).action(async (flags, command) => {
2189
+ const ctx = resolveContext(command);
2190
+ const globals = command.optsWithGlobals();
2191
+ const { key } = resolveApiKey(globals.apiKey);
2192
+ await runEnterpriseTalentsList(ctx, key, flags);
2193
+ });
2194
+ }
2195
+
2196
+ // src/commands/enterprise/talents/view.ts
2197
+ async function runEnterpriseTalentsView(ctx, apiKey, encResumeId, flags) {
2198
+ const trimmed = encResumeId.trim();
2199
+ if (!trimmed) {
2200
+ throw new CliError("enc_resume_id must not be empty", ExitCode.InvalidArgument);
2201
+ }
2202
+ const { body } = await enterpriseGet(
2203
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2204
+ `/talents/${encodeURIComponent(trimmed)}`
2205
+ );
2206
+ const resume = unwrapDataResponse(body);
2207
+ if (flags.fields) {
2208
+ printJson(pickPaths(resume, parseFieldsList(flags.fields)));
2209
+ return;
2210
+ }
2211
+ printJson(resume);
2212
+ }
2213
+ function registerEnterpriseTalentsView(parent) {
2214
+ parent.command("view <enc_resume_id>").description("View one applicant resume preview (company-scoped; PII visibility per access level)").option("--fields <list>", "output selected fields as JSON (comma-separated dotted paths)").action(async (encResumeId, flags, command) => {
2215
+ const ctx = resolveContext(command);
2216
+ const globals = command.optsWithGlobals();
2217
+ const { key } = resolveApiKey(globals.apiKey);
2218
+ await runEnterpriseTalentsView(ctx, key, encResumeId, flags);
2219
+ });
2220
+ }
2221
+
2222
+ // src/commands/enterprise/talents/respond.ts
2223
+ var import_node_crypto7 = require("crypto");
2224
+ function resolveRespondBody(flags, options = {}) {
2225
+ const hasBody = flags.body !== void 0;
2226
+ const hasBodyFile = flags.bodyFile !== void 0;
2227
+ if (hasBody === hasBodyFile) {
2228
+ throw new CliError("Provide exactly one of --body or --body-file", ExitCode.InvalidArgument);
2229
+ }
2230
+ if (hasBody) {
2231
+ if (!flags.body.trim()) throw new CliError("--body must not be empty", ExitCode.InvalidArgument);
2232
+ return flags.body;
2233
+ }
2234
+ return readTextInput(flags.bodyFile, { timeoutMs: options.timeoutMs });
2235
+ }
2236
+ async function runEnterpriseTalentsRespond(ctx, apiKey, encResumeId, flags, idempotencyKey) {
2237
+ const trimmedId = encResumeId.trim();
2238
+ if (!trimmedId) {
2239
+ throw new CliError("enc_resume_id must not be empty", ExitCode.InvalidArgument);
2240
+ }
2241
+ const subject = (flags.subject ?? "").trim();
2242
+ if (!subject) {
2243
+ throw new CliError("--subject is required and must not be empty", ExitCode.InvalidArgument);
2244
+ }
2245
+ const body = resolveRespondBody(flags, { timeoutMs: ctx.timeoutMs });
2246
+ const { body: respBody } = await enterprisePost(
2247
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2248
+ `/talents/${encodeURIComponent(trimmedId)}/respond`,
2249
+ { subject, body },
2250
+ { idempotencyKey }
2251
+ );
2252
+ const result = unwrapDataResponse(respBody);
2253
+ if (ctx.format === "json") {
2254
+ printJson(result);
2255
+ return;
2256
+ }
2257
+ process.stdout.write(`Replied to ${result.enc_resume_id ?? trimmedId}; sent at ${result.sent_at ?? "(unknown)"}.
2258
+ `);
2259
+ }
2260
+ function registerEnterpriseTalentsRespond(parent) {
2261
+ parent.command("respond <enc_resume_id>").description("Reply to an applicant (in-app message; inherits the company daily reply quota)").requiredOption("--subject <subject>", "message subject (max 200)").option("--body <text>", "message body text (max 5000)").option("--body-file <path>", 'read message body from a file, or "-" for stdin').option("--idempotency-key <key>", "reuse across retries to avoid duplicate sends (default: a fresh UUID)").action(async (encResumeId, flags, command) => {
2262
+ const ctx = resolveContext(command);
2263
+ const globals = command.optsWithGlobals();
2264
+ const { key } = resolveApiKey(globals.apiKey);
2265
+ await runEnterpriseTalentsRespond(ctx, key, encResumeId, flags, flags.idempotencyKey ?? (0, import_node_crypto7.randomUUID)());
2266
+ });
2267
+ }
2268
+
2269
+ // src/commands/enterprise/talents/index.ts
2270
+ function registerEnterpriseTalentsCommand(parent) {
2271
+ const talents = parent.command("talents").description("Browse and respond to applicants in your talent pool");
2272
+ registerEnterpriseTalentsList(talents);
2273
+ registerEnterpriseTalentsView(talents);
2274
+ registerEnterpriseTalentsRespond(talents);
2275
+ }
2276
+
2277
+ // src/commands/enterprise/campaigns/create.ts
2278
+ var import_node_crypto8 = require("crypto");
2279
+ async function runCampaignCreate(ctx, apiKey, source, idempotencyKey) {
2280
+ const campaignBody = readJsonObject(source, { timeoutMs: ctx.timeoutMs });
2281
+ const { body } = await enterprisePost(
2282
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2283
+ "/campaigns",
2284
+ campaignBody,
2285
+ { idempotencyKey }
2286
+ );
2287
+ const created = unwrapDataResponse(body);
2288
+ if (ctx.format === "json") {
2289
+ printJson(created);
2290
+ return;
2291
+ }
2292
+ process.stdout.write(`Created campaign: ${created.enc_id ?? ""}
2293
+ `);
2294
+ }
2295
+ function registerEnterpriseCampaignsCreate(parent) {
2296
+ parent.command("create").description('Create a recruitment campaign from a JSON file (use "-" to read stdin)').requiredOption("--file <path>", 'path to a JSON campaign body, or "-" for stdin').option("--idempotency-key <key>", "reuse across retries to avoid duplicate creates (default: a fresh UUID)").action(async (flags, command) => {
2297
+ const ctx = resolveContext(command);
2298
+ const globals = command.optsWithGlobals();
2299
+ const { key } = resolveApiKey(globals.apiKey);
2300
+ await runCampaignCreate(ctx, key, flags.file, flags.idempotencyKey ?? (0, import_node_crypto8.randomUUID)());
2301
+ });
2302
+ }
2303
+
2304
+ // src/commands/enterprise/campaigns/list.ts
2305
+ var STATUS_MAP2 = { open: 1, closed: 0 };
2306
+ var MINIMAL_LIST_FIELDS3 = ["enc_id", "name", "status", "job_count"];
2307
+ function mapStatusFlag2(raw) {
2308
+ if (raw === void 0) return void 0;
2309
+ if (Object.prototype.hasOwnProperty.call(STATUS_MAP2, raw)) return STATUS_MAP2[raw];
2310
+ throw new CliError(`Invalid --status "${raw}". Allowed: ${Object.keys(STATUS_MAP2).join(", ")}`, ExitCode.InvalidArgument);
2311
+ }
2312
+ function formatStatus2(status) {
2313
+ if (status === 1) return "open";
2314
+ if (status === 0) return "closed";
2315
+ return status === void 0 ? "" : String(status);
2316
+ }
2317
+ function formatCount2(value) {
2318
+ return typeof value === "number" && Number.isFinite(value) ? String(value) : "\u2014";
2319
+ }
2320
+ async function runEnterpriseCampaignsList(ctx, apiKey, flags) {
2321
+ if (flags.fields && flags.minimal) {
2322
+ throw new CliError("Use either --fields or --minimal, not both", ExitCode.InvalidArgument);
2323
+ }
2324
+ const { body } = await enterpriseGet(
2325
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2326
+ "/campaigns",
2327
+ {
2328
+ currentPage: flags.page,
2329
+ pageSize: flags.pageSize,
2330
+ keyword: flags.keyword,
2331
+ status: mapStatusFlag2(flags.status)
2332
+ }
2333
+ );
2334
+ const paged = asPaginatedBody(body);
2335
+ const projection = flags.minimal ? MINIMAL_LIST_FIELDS3 : flags.fields ? parseFieldsList(flags.fields) : void 0;
2336
+ if (projection || ctx.format === "json") {
2337
+ printJson(projection ? { ...paged, data: paged.data.map((row) => pickPaths(row, projection)) } : paged);
2338
+ return;
2339
+ }
2340
+ printTable(
2341
+ paged.data,
2342
+ [
2343
+ { header: "ENC_ID", value: (r) => (r.enc_id ?? "").slice(0, 14) },
2344
+ { header: "NAME", value: (r) => r.name ?? "", maxWidth: 32 },
2345
+ { header: "STATUS", value: (r) => formatStatus2(r.status) },
2346
+ { header: "JOBS", value: (r) => formatCount2(r.job_count) },
2347
+ { header: "PV", value: (r) => formatCount2(r.pv) },
2348
+ { header: "VISITORS", value: (r) => formatCount2(r.visitors_count) }
2349
+ ],
2350
+ ctx.color
2351
+ );
2352
+ const head = `Showing page ${paged.currentPage}/${paged.totalPages} (${paged.data.length} of ${paged.totalCount} campaigns).`;
2353
+ const hint = paged.totalPages > paged.currentPage ? ` Next: wport enterprise campaigns list --page ${paged.currentPage + 1}` : "";
2354
+ process.stdout.write(dim(head + hint, ctx.color) + "\n");
2355
+ }
2356
+ function registerEnterpriseCampaignsList(parent) {
2357
+ parent.command("list").description("List your recruitment campaigns").option("-p, --page <n>", "page number (server: currentPage, default 1)", (v) => Number(v)).option("-s, --page-size <n>", "items per page (default 10, max 100)", (v) => Number(v)).option("-k, --keyword <kw>", "filter by campaign name keyword").option("--status <state>", "filter by status: open | closed").option("--fields <list>", "output selected fields as JSON (comma-separated dotted paths)").option("--minimal", `output only ${MINIMAL_LIST_FIELDS3.join(",")} as JSON`).action(async (flags, command) => {
2358
+ const ctx = resolveContext(command);
2359
+ const globals = command.optsWithGlobals();
2360
+ const { key } = resolveApiKey(globals.apiKey);
2361
+ await runEnterpriseCampaignsList(ctx, key, flags);
2362
+ });
2363
+ }
2364
+
2365
+ // src/commands/enterprise/campaigns/lifecycle.ts
2366
+ var import_node_crypto9 = require("crypto");
2367
+ async function runCampaignTransition(ctx, apiKey, encId, action, idempotencyKey) {
2368
+ const trimmed = requireEncId(encId);
2369
+ const { body } = await enterprisePatch(
2370
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2371
+ `/campaigns/${encodeURIComponent(trimmed)}/${action}`,
2372
+ {},
2373
+ { idempotencyKey }
2374
+ );
2375
+ const result = unwrapDataResponse(body);
2376
+ if (ctx.format === "json") {
2377
+ printJson(result);
2378
+ return;
2379
+ }
2380
+ const verb = action === "publish" ? "Published" : "Unpublished";
2381
+ process.stdout.write(`${verb} campaign: ${result.enc_id ?? trimmed}
2382
+ `);
2383
+ }
2384
+ function registerEnterpriseCampaignsPublish(parent) {
2385
+ parent.command("publish <enc_id>").description("Publish (activate) a recruitment campaign").option("--idempotency-key <key>", "reuse across retries (default: a fresh UUID)").action(async (encId, flags, command) => {
2386
+ const ctx = resolveContext(command);
2387
+ const { key } = resolveApiKey(command.optsWithGlobals().apiKey);
2388
+ await runCampaignTransition(ctx, key, encId, "publish", flags.idempotencyKey ?? (0, import_node_crypto9.randomUUID)());
2389
+ });
2390
+ }
2391
+ function registerEnterpriseCampaignsUnpublish(parent) {
2392
+ parent.command("unpublish <enc_id>").description("Unpublish (deactivate) a recruitment campaign").option("--idempotency-key <key>", "reuse across retries (default: a fresh UUID)").action(async (encId, flags, command) => {
2393
+ const ctx = resolveContext(command);
2394
+ const { key } = resolveApiKey(command.optsWithGlobals().apiKey);
2395
+ await runCampaignTransition(ctx, key, encId, "unpublish", flags.idempotencyKey ?? (0, import_node_crypto9.randomUUID)());
2396
+ });
2397
+ }
2398
+
2399
+ // src/commands/enterprise/campaigns/update.ts
2400
+ var import_node_crypto10 = require("crypto");
2401
+ async function runCampaignUpdate(ctx, apiKey, encId, source, idempotencyKey) {
2402
+ const trimmed = requireEncId(encId);
2403
+ const campaignBody = readJsonObject(source, { timeoutMs: ctx.timeoutMs });
2404
+ const { body } = await enterprisePatch(
2405
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2406
+ `/campaigns/${encodeURIComponent(trimmed)}`,
2407
+ campaignBody,
2408
+ { idempotencyKey }
2409
+ );
2410
+ const updated = unwrapDataResponse(body);
2411
+ if (ctx.format === "json") {
2412
+ printJson(updated);
2413
+ return;
2414
+ }
2415
+ process.stdout.write(`Updated campaign: ${updated.enc_id ?? trimmed}
2416
+ `);
2417
+ }
2418
+ function registerEnterpriseCampaignsUpdate(parent) {
2419
+ parent.command("update <enc_id>").description('Update a recruitment campaign from a JSON file (use "-" to read stdin)').requiredOption("--file <path>", 'path to a partial JSON campaign body, or "-" for stdin').option("--idempotency-key <key>", "reuse across retries (default: a fresh UUID)").action(async (encId, flags, command) => {
2420
+ const ctx = resolveContext(command);
2421
+ const globals = command.optsWithGlobals();
2422
+ const { key } = resolveApiKey(globals.apiKey);
2423
+ await runCampaignUpdate(ctx, key, encId, flags.file, flags.idempotencyKey ?? (0, import_node_crypto10.randomUUID)());
2424
+ });
2425
+ }
2426
+
2427
+ // src/commands/enterprise/campaigns/view.ts
2428
+ async function runEnterpriseCampaignsView(ctx, apiKey, encId, flags) {
2429
+ const trimmed = encId.trim();
2430
+ if (!trimmed) {
2431
+ throw new CliError("enc_id must not be empty", ExitCode.InvalidArgument);
2432
+ }
2433
+ const { body } = await enterpriseGet(
2434
+ { baseUrl: ctx.baseUrl, locale: ctx.locale, timeoutMs: ctx.timeoutMs, apiKey },
2435
+ `/campaigns/${encodeURIComponent(trimmed)}`
2436
+ );
2437
+ const campaign = unwrapDataResponse(body);
2438
+ if (flags.fields) {
2439
+ printJson(pickPaths(campaign, parseFieldsList(flags.fields)));
2440
+ return;
2441
+ }
2442
+ printJson(campaign);
2443
+ }
2444
+ function registerEnterpriseCampaignsView(parent) {
2445
+ parent.command("view <enc_id>").description("View one recruitment campaign (with its jobs)").option("--fields <list>", "output selected fields as JSON (comma-separated dotted paths)").action(async (encId, flags, command) => {
2446
+ const ctx = resolveContext(command);
2447
+ const globals = command.optsWithGlobals();
2448
+ const { key } = resolveApiKey(globals.apiKey);
2449
+ await runEnterpriseCampaignsView(ctx, key, encId, flags);
2450
+ });
2451
+ }
2452
+
2453
+ // src/commands/enterprise/campaigns/index.ts
2454
+ function registerEnterpriseCampaignsCommand(parent) {
2455
+ const campaigns = parent.command("campaigns").description("Manage your recruitment campaigns");
2456
+ registerEnterpriseCampaignsList(campaigns);
2457
+ registerEnterpriseCampaignsView(campaigns);
2458
+ registerEnterpriseCampaignsCreate(campaigns);
2459
+ registerEnterpriseCampaignsUpdate(campaigns);
2460
+ registerEnterpriseCampaignsPublish(campaigns);
2461
+ registerEnterpriseCampaignsUnpublish(campaigns);
2462
+ }
2463
+
1639
2464
  // src/commands/enterprise/index.ts
1640
2465
  function registerEnterpriseCommand(program2) {
1641
2466
  const enterprise = program2.command("enterprise").description("Manage your company job postings with an enterprise API key").option("--api-key <key>", 'one-off API key (prefer "wport enterprise login" or the WPORT_API_KEY env var)');
1642
2467
  registerEnterpriseLogin(enterprise);
1643
2468
  registerEnterpriseLogout(enterprise);
1644
2469
  registerEnterpriseWhoami(enterprise);
2470
+ registerEnterpriseUsage(enterprise);
1645
2471
  registerEnterpriseJobsCommand(enterprise);
1646
2472
  registerEnterpriseKeysCommand(enterprise);
2473
+ registerEnterpriseCompanyCommand(enterprise);
2474
+ registerEnterpriseTalentsCommand(enterprise);
2475
+ registerEnterpriseCampaignsCommand(enterprise);
1647
2476
  }
1648
2477
 
1649
2478
  // src/index.ts
1650
2479
  var program = new import_commander.Command();
1651
- program.name("wport").description("wport CLI \u2014 terminal interface to the W101 Talent Search Hub public API").version("0.4.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));
2480
+ program.name("wport").description("wport CLI \u2014 terminal interface to the W101 Talent Search Hub public API").version("0.5.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));
1652
2481
  registerJobsCommand(program);
1653
2482
  registerConfigCommand(program);
1654
2483
  registerDoctorCommand(program);