@pkgseer/cli 0.1.2 → 0.1.4

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/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  version
4
- } from "./shared/chunk-2wbvwsc9.js";
4
+ } from "./shared/chunk-z505vakm.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
@@ -71,12 +71,13 @@ var CliPackageQualityDocument = gql`
71
71
  }
72
72
  `;
73
73
  var CliPackageDepsDocument = gql`
74
- query CliPackageDeps($registry: Registry!, $name: String!, $version: String, $includeTransitive: Boolean) {
74
+ query CliPackageDeps($registry: Registry!, $name: String!, $version: String, $includeTransitive: Boolean, $maxDepth: Int) {
75
75
  packageDependencies(
76
76
  registry: $registry
77
77
  name: $name
78
78
  version: $version
79
79
  includeTransitive: $includeTransitive
80
+ maxDepth: $maxDepth
80
81
  ) {
81
82
  package {
82
83
  name
@@ -202,7 +203,7 @@ var CliDocsListDocument = gql`
202
203
  packageName
203
204
  version
204
205
  pages {
205
- slug
206
+ id
206
207
  title
207
208
  words
208
209
  }
@@ -465,13 +466,35 @@ var FetchPackageDocDocument = gql`
465
466
  }
466
467
  }
467
468
  `;
469
+ var GetDocPageDocument = gql`
470
+ query GetDocPage($pageId: String!) {
471
+ getDocPage(pageId: $pageId) {
472
+ schemaVersion
473
+ page {
474
+ id
475
+ title
476
+ content
477
+ contentFormat
478
+ breadcrumbs
479
+ linkName
480
+ linkTargets
481
+ lastUpdatedAt
482
+ source {
483
+ url
484
+ label
485
+ }
486
+ baseUrl
487
+ }
488
+ }
489
+ }
490
+ `;
468
491
  var SearchPackageDocsDocument = gql`
469
- query SearchPackageDocs($registry: Registry!, $packageName: String!, $keywords: [String!], $query: String, $includeSnippets: Boolean, $limit: Int, $version: String) {
492
+ query SearchPackageDocs($registry: Registry!, $packageName: String!, $keywords: [String!], $matchMode: MatchMode, $includeSnippets: Boolean, $limit: Int, $version: String) {
470
493
  searchPackageDocs(
471
494
  registry: $registry
472
495
  packageName: $packageName
473
496
  keywords: $keywords
474
- query: $query
497
+ matchMode: $matchMode
475
498
  includeSnippets: $includeSnippets
476
499
  limit: $limit
477
500
  version: $version
@@ -480,7 +503,6 @@ var SearchPackageDocsDocument = gql`
480
503
  registry
481
504
  packageName
482
505
  version
483
- query
484
506
  entries {
485
507
  id
486
508
  title
@@ -501,17 +523,16 @@ var SearchPackageDocsDocument = gql`
501
523
  }
502
524
  `;
503
525
  var SearchProjectDocsDocument = gql`
504
- query SearchProjectDocs($project: String!, $keywords: [String!], $query: String, $includeSnippets: Boolean, $limit: Int) {
526
+ query SearchProjectDocs($project: String!, $keywords: [String!], $matchMode: MatchMode, $includeSnippets: Boolean, $limit: Int) {
505
527
  searchProjectDocs(
506
528
  project: $project
507
529
  keywords: $keywords
508
- query: $query
530
+ matchMode: $matchMode
509
531
  includeSnippets: $includeSnippets
510
532
  limit: $limit
511
533
  ) {
512
534
  schemaVersion
513
535
  project
514
- query
515
536
  entries {
516
537
  id
517
538
  title
@@ -548,6 +569,7 @@ var PackageQualityDocumentString = print(PackageQualityDocument);
548
569
  var ComparePackagesDocumentString = print(ComparePackagesDocument);
549
570
  var ListPackageDocsDocumentString = print(ListPackageDocsDocument);
550
571
  var FetchPackageDocDocumentString = print(FetchPackageDocDocument);
572
+ var GetDocPageDocumentString = print(GetDocPageDocument);
551
573
  var SearchPackageDocsDocumentString = print(SearchPackageDocsDocument);
552
574
  var SearchProjectDocsDocumentString = print(SearchProjectDocsDocument);
553
575
  function getSdk(client, withWrapper = defaultWrapper) {
@@ -603,6 +625,9 @@ function getSdk(client, withWrapper = defaultWrapper) {
603
625
  FetchPackageDoc(variables, requestHeaders) {
604
626
  return withWrapper((wrappedRequestHeaders) => client.rawRequest(FetchPackageDocDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "FetchPackageDoc", "query", variables);
605
627
  },
628
+ GetDocPage(variables, requestHeaders) {
629
+ return withWrapper((wrappedRequestHeaders) => client.rawRequest(GetDocPageDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "GetDocPage", "query", variables);
630
+ },
606
631
  SearchPackageDocs(variables, requestHeaders) {
607
632
  return withWrapper((wrappedRequestHeaders) => client.rawRequest(SearchPackageDocsDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "SearchPackageDocs", "query", variables);
608
633
  },
@@ -1220,12 +1245,16 @@ class PkgseerServiceImpl {
1220
1245
  });
1221
1246
  return { data: result.data, errors: result.errors };
1222
1247
  }
1248
+ async getDocPage(pageId) {
1249
+ const result = await this.client.GetDocPage({ pageId });
1250
+ return { data: result.data, errors: result.errors };
1251
+ }
1223
1252
  async searchPackageDocs(registry, packageName, options) {
1224
1253
  const result = await this.client.SearchPackageDocs({
1225
1254
  registry,
1226
1255
  packageName,
1227
1256
  keywords: options?.keywords,
1228
- query: options?.query,
1257
+ matchMode: options?.matchMode,
1229
1258
  includeSnippets: options?.includeSnippets,
1230
1259
  limit: options?.limit,
1231
1260
  version: options?.version
@@ -1236,7 +1265,7 @@ class PkgseerServiceImpl {
1236
1265
  const result = await this.client.SearchProjectDocs({
1237
1266
  project,
1238
1267
  keywords: options?.keywords,
1239
- query: options?.query,
1268
+ matchMode: options?.matchMode,
1240
1269
  includeSnippets: options?.includeSnippets,
1241
1270
  limit: options?.limit
1242
1271
  });
@@ -1262,12 +1291,13 @@ class PkgseerServiceImpl {
1262
1291
  });
1263
1292
  return { data: result.data, errors: result.errors };
1264
1293
  }
1265
- async cliPackageDeps(registry, name, version2, includeTransitive) {
1294
+ async cliPackageDeps(registry, name, version2, includeTransitive, maxDepth) {
1266
1295
  const result = await this.client.CliPackageDeps({
1267
1296
  registry,
1268
1297
  name,
1269
1298
  version: version2,
1270
- includeTransitive
1299
+ includeTransitive,
1300
+ maxDepth
1271
1301
  });
1272
1302
  return { data: result.data, errors: result.errors };
1273
1303
  }
@@ -1694,10 +1724,26 @@ function outputError(message, json) {
1694
1724
  process.exit(1);
1695
1725
  }
1696
1726
  function handleErrors(errors, json) {
1697
- if (errors && errors.length > 0) {
1698
- const message = errors.map((e) => e.message).join(", ");
1699
- outputError(message, json);
1700
- }
1727
+ if (!errors || errors.length === 0)
1728
+ return;
1729
+ const combined = errors.map((e) => e.message).filter(Boolean).join(", ");
1730
+ const lower = combined.toLowerCase();
1731
+ const isTimeout = lower.includes("-32001") || lower.includes("timeout") || lower.includes("timed out");
1732
+ const isNotFound = lower.includes("not found") || lower.includes("does not exist") || lower.includes("unknown");
1733
+ const isAuth = lower.includes("unauthorized") || lower.includes("forbidden") || lower.includes("token") || lower.includes("authentication") || lower.includes("permission");
1734
+ const isRateLimit = lower.includes("rate limit") || lower.includes("too many requests");
1735
+ let hint = "";
1736
+ if (isTimeout) {
1737
+ hint = " Hint: lower the limit or narrow the scope, then retry.";
1738
+ } else if (isNotFound) {
1739
+ hint = " Hint: verify the name/registry and that the resource exists.";
1740
+ } else if (isAuth) {
1741
+ hint = " Hint: check login or token validity (pkgseer auth-status).";
1742
+ } else if (isRateLimit) {
1743
+ hint = " Hint: wait and retry, or reduce request frequency.";
1744
+ }
1745
+ const message = `${combined}${hint}`;
1746
+ outputError(message, json);
1701
1747
  }
1702
1748
  function formatNumber(num) {
1703
1749
  if (num == null)
@@ -1775,6 +1821,19 @@ async function withCliErrorHandling(json, fn) {
1775
1821
  return;
1776
1822
  }
1777
1823
  const message = error instanceof Error ? error.message : "Unknown error";
1824
+ const lower = message.toLowerCase();
1825
+ if (lower.includes("-32001") || lower.includes("timeout")) {
1826
+ outputError(`${message}. Hint: lower the limit or narrow the scope, then retry.`, json);
1827
+ return;
1828
+ }
1829
+ if (lower.includes("unauthorized") || lower.includes("forbidden") || lower.includes("token") || lower.includes("authentication") || lower.includes("permission")) {
1830
+ outputError(`${message}. Hint: check login or token validity.`, json);
1831
+ return;
1832
+ }
1833
+ if (lower.includes("rate limit") || lower.includes("too many requests")) {
1834
+ outputError(`${message}. Hint: wait and retry.`, json);
1835
+ return;
1836
+ }
1778
1837
  const colonMatch = message.match(/^([^:]+):/);
1779
1838
  const shortMessage = colonMatch?.[1] ?? message;
1780
1839
  outputError(shortMessage, json);
@@ -1792,10 +1851,11 @@ function formatScore(score) {
1792
1851
  // src/commands/docs/get.ts
1793
1852
  function parsePackageRef(ref) {
1794
1853
  if (!ref.includes("/")) {
1795
- throw new Error(`Invalid format: "${ref}". Expected format:
1796
- ` + ` Full form: <registry>/<package>/<version>/<document>
1797
- ` + ` Short form: <package>/<document>
1798
- ` + `Examples: npm/express/4.18.2/readme or express/readme`);
1854
+ return {
1855
+ pageId: ref,
1856
+ originalRef: ref,
1857
+ isGlobalId: true
1858
+ };
1799
1859
  }
1800
1860
  const parts = ref.split("/");
1801
1861
  if (parts.length < 2) {
@@ -1824,7 +1884,7 @@ function parsePackageRef(ref) {
1824
1884
  }
1825
1885
  return { packageName, pageId, originalRef: ref };
1826
1886
  }
1827
- function formatDocPage(data, ref) {
1887
+ function formatDocPage(data, ref, verbose = false, previewLines) {
1828
1888
  const lines = [];
1829
1889
  const page = data.page;
1830
1890
  if (!page) {
@@ -1832,6 +1892,49 @@ function formatDocPage(data, ref) {
1832
1892
  }
1833
1893
  lines.push(`[${ref}]`);
1834
1894
  lines.push("");
1895
+ if (verbose) {
1896
+ const metadata = [];
1897
+ if (data.schemaVersion) {
1898
+ metadata.push(`Schema Version: ${data.schemaVersion}`);
1899
+ }
1900
+ if (page.id) {
1901
+ metadata.push(`Page ID: ${page.id}`);
1902
+ }
1903
+ if (page.contentFormat) {
1904
+ metadata.push(`Format: ${page.contentFormat}`);
1905
+ }
1906
+ if (page.lastUpdatedAt) {
1907
+ metadata.push(`Last Updated: ${page.lastUpdatedAt}`);
1908
+ }
1909
+ if (page.source) {
1910
+ const sourceParts = [];
1911
+ if (page.source.url) {
1912
+ sourceParts.push(page.source.url);
1913
+ }
1914
+ if (page.source.label) {
1915
+ sourceParts.push(`(${page.source.label})`);
1916
+ }
1917
+ if (sourceParts.length > 0) {
1918
+ metadata.push(`Source: ${sourceParts.join(" ")}`);
1919
+ }
1920
+ }
1921
+ if (page.baseUrl) {
1922
+ metadata.push(`Base URL: ${page.baseUrl}`);
1923
+ }
1924
+ if (page.linkName) {
1925
+ metadata.push(`Link Name: ${page.linkName}`);
1926
+ }
1927
+ if (page.linkTargets && page.linkTargets.length > 0) {
1928
+ metadata.push(`Link Targets: ${page.linkTargets.join(", ")}`);
1929
+ }
1930
+ if (metadata.length > 0) {
1931
+ lines.push("Metadata:");
1932
+ for (const meta of metadata) {
1933
+ lines.push(` ${meta}`);
1934
+ }
1935
+ lines.push("");
1936
+ }
1937
+ }
1835
1938
  if (page.breadcrumbs && page.breadcrumbs.length > 0) {
1836
1939
  lines.push(page.breadcrumbs.join(" > "));
1837
1940
  lines.push("");
@@ -1839,9 +1942,22 @@ function formatDocPage(data, ref) {
1839
1942
  lines.push(`# ${page.title}`);
1840
1943
  lines.push("");
1841
1944
  if (page.content) {
1842
- lines.push(page.content);
1945
+ const previewLimit = previewLines ?? 20;
1946
+ if (previewLimit === 0) {
1947
+ lines.push(page.content);
1948
+ } else {
1949
+ const contentLines = page.content.split(`
1950
+ `);
1951
+ const preview = contentLines.slice(0, previewLimit).join(`
1952
+ `);
1953
+ lines.push(preview);
1954
+ if (contentLines.length > previewLimit) {
1955
+ lines.push("");
1956
+ lines.push("(Preview truncated; use --preview-lines 0 for full content.)");
1957
+ }
1958
+ }
1843
1959
  } else {
1844
- lines.push("(No content available)");
1960
+ lines.push("(No content available. Verify the page ID or try another version.)");
1845
1961
  }
1846
1962
  return lines.join(`
1847
1963
  `);
@@ -1861,13 +1977,66 @@ function extractErrorMessage(error) {
1861
1977
  }
1862
1978
  async function fetchPage(ref, defaultRegistry, defaultVersion, pkgseerService) {
1863
1979
  try {
1980
+ if (ref.isGlobalId) {
1981
+ const result2 = await pkgseerService.getDocPage(ref.pageId);
1982
+ if (result2.errors && result2.errors.length > 0) {
1983
+ const errorMsg = result2.errors.map((e) => {
1984
+ if (typeof e === "object" && e !== null) {
1985
+ const parts = [];
1986
+ if ("message" in e) {
1987
+ parts.push(String(e.message));
1988
+ }
1989
+ if ("extensions" in e && e.extensions) {
1990
+ const ext = e.extensions;
1991
+ if (typeof ext === "object" && "code" in ext) {
1992
+ parts.push(`Code: ${ext.code}`);
1993
+ }
1994
+ }
1995
+ return parts.length > 0 ? parts.join(" ") : JSON.stringify(e);
1996
+ }
1997
+ return String(e);
1998
+ }).join("; ");
1999
+ return { ref: ref.originalRef, result: null, error: errorMsg };
2000
+ }
2001
+ if (!result2.data.getDocPage) {
2002
+ return {
2003
+ ref: ref.originalRef,
2004
+ result: null,
2005
+ error: `Page not found: ${ref.pageId}`
2006
+ };
2007
+ }
2008
+ return {
2009
+ ref: ref.originalRef,
2010
+ result: {
2011
+ schemaVersion: result2.data.getDocPage?.schemaVersion ?? null,
2012
+ page: result2.data.getDocPage?.page ?? null
2013
+ }
2014
+ };
2015
+ }
1864
2016
  const registry = ref.registry ? toGraphQLRegistry(ref.registry) : defaultRegistry;
1865
2017
  const version2 = ref.version ?? defaultVersion;
2018
+ if (!ref.packageName) {
2019
+ return {
2020
+ ref: ref.originalRef,
2021
+ result: null,
2022
+ error: `Package name required for reference: ${ref.originalRef}`
2023
+ };
2024
+ }
1866
2025
  const result = await pkgseerService.cliDocsGet(registry, ref.packageName, ref.pageId, version2);
1867
2026
  if (result.errors && result.errors.length > 0) {
1868
2027
  const errorMsg = result.errors.map((e) => {
1869
- if (typeof e === "object" && e !== null && "message" in e) {
1870
- return String(e.message);
2028
+ if (typeof e === "object" && e !== null) {
2029
+ const parts = [];
2030
+ if ("message" in e) {
2031
+ parts.push(String(e.message));
2032
+ }
2033
+ if ("extensions" in e && e.extensions) {
2034
+ const ext = e.extensions;
2035
+ if (typeof ext === "object" && "code" in ext) {
2036
+ parts.push(`Code: ${ext.code}`);
2037
+ }
2038
+ }
2039
+ return parts.length > 0 ? parts.join(" ") : JSON.stringify(e);
1871
2040
  }
1872
2041
  return String(e);
1873
2042
  }).join("; ");
@@ -1880,7 +2049,12 @@ async function fetchPage(ref, defaultRegistry, defaultVersion, pkgseerService) {
1880
2049
  error: `Page not found: ${ref.pageId} for ${ref.packageName}`
1881
2050
  };
1882
2051
  }
1883
- return { ref: ref.originalRef, result: result.data.fetchPackageDoc };
2052
+ return {
2053
+ ref: ref.originalRef,
2054
+ result: result.data.fetchPackageDoc ? {
2055
+ page: result.data.fetchPackageDoc.page ?? null
2056
+ } : null
2057
+ };
1884
2058
  } catch (error) {
1885
2059
  return {
1886
2060
  ref: ref.originalRef,
@@ -1891,11 +2065,13 @@ async function fetchPage(ref, defaultRegistry, defaultVersion, pkgseerService) {
1891
2065
  }
1892
2066
  async function docsGetAction(refs, options, deps) {
1893
2067
  if (refs.length === 0) {
1894
- outputError("At least one page reference required. Format: <package>/<slug>", options.json ?? false);
2068
+ outputError("At least one page reference required. Format: <globally-unique-id> or <package>/<document>", options.json ?? false);
1895
2069
  return;
1896
2070
  }
1897
2071
  const { pkgseerService } = deps;
1898
2072
  const defaultRegistry = toGraphQLRegistry(options.registry);
2073
+ const previewLines = options.previewLines != null ? Number.parseInt(options.previewLines, 10) : 20;
2074
+ const resolvedPreview = Number.isNaN(previewLines) ? 20 : previewLines;
1899
2075
  const parsedRefs = [];
1900
2076
  for (const ref of refs) {
1901
2077
  try {
@@ -1929,17 +2105,24 @@ ${errorMessages}`, options.json ?? false);
1929
2105
  if (r.error) {
1930
2106
  return { ref: r.ref, error: r.error };
1931
2107
  }
2108
+ if (options.verbose) {
2109
+ return {
2110
+ ref: r.ref,
2111
+ ...r.result
2112
+ };
2113
+ }
1932
2114
  const page = r.result?.page;
1933
2115
  return {
1934
2116
  ref: r.ref,
1935
2117
  title: page?.title,
1936
- content: page?.content
2118
+ content: page?.content,
2119
+ breadcrumbs: page?.breadcrumbs
1937
2120
  };
1938
2121
  });
1939
2122
  output(jsonResults, true);
1940
2123
  } else {
1941
2124
  if (successes.length > 0) {
1942
- const pages = successes.filter((r) => r.result !== null).map((r) => formatDocPage(r.result, r.ref));
2125
+ const pages = successes.filter((r) => r.result !== null).map((r) => formatDocPage(r.result, r.ref, options.verbose, resolvedPreview));
1943
2126
  console.log(pages.join(`
1944
2127
 
1945
2128
  ---
@@ -1956,29 +2139,28 @@ var GET_DESCRIPTION = `Fetch one or more documentation pages.
1956
2139
  Retrieves the full content of documentation pages including
1957
2140
  title, breadcrumbs, content (markdown), and source URL.
1958
2141
 
1959
- Use 'pkgseer docs list' first to discover available page IDs, or
1960
- use the output format from 'pkgseer docs search --refs-only'.
2142
+ Use 'pkgseer docs list' first to discover available page IDs.
1961
2143
 
1962
- Format: <registry>/<package>/<version>/<document> (full form)
2144
+ Format: <globally-unique-id> (preferred)
2145
+ or <registry>/<package>/<version>/<document> (full form)
1963
2146
  or <package>/<document> (short form, requires --registry/--pkg-version)
1964
2147
 
1965
2148
  Examples:
1966
- # Full form (from search output)
2149
+ # Globally unique ID (from list output)
2150
+ pkgseer docs get 24293-shared-plugins-during-build-2
2151
+
2152
+ # Full form
1967
2153
  pkgseer docs get npm/express/4.18.2/readme
1968
2154
  pkgseer docs get hex/postgrex/1.15.0/readme
1969
2155
 
1970
- # Short form (backward compatibility)
2156
+ # Short form
1971
2157
  pkgseer docs get express/readme --registry npm --pkg-version 4.18.2
1972
2158
  pkgseer docs get postgrex/readme --registry hex
1973
2159
 
1974
2160
  # Multiple pages
1975
- pkgseer docs get npm/express/4.18.2/readme npm/express/4.18.2/writing-middleware
1976
-
1977
- # Pipe from search (full form works seamlessly)
1978
- pkgseer docs search log --package express --refs-only | \\
1979
- xargs pkgseer docs get`;
2161
+ pkgseer docs get 24293-shared-plugins-during-build-2 npm/express/4.18.2/readme`;
1980
2162
  function registerDocsGetCommand(program) {
1981
- program.command("get <refs...>").summary("Fetch documentation page(s)").description(GET_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-v, --pkg-version <version>", "Package version").option("--json", "Output as JSON").action(async (refs, options) => {
2163
+ program.command("get <refs...>").summary("Fetch documentation page(s)").description(GET_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-v, --pkg-version <version>", "Package version").option("--json", "Output as JSON").option("--verbose", "Include metadata (ID, format, source, links, etc.)").option("--preview-lines <n>", "Lines of content to show (0 for full content; default 20)").action(async (refs, options) => {
1982
2164
  await withCliErrorHandling(options.json ?? false, async () => {
1983
2165
  const deps = await createContainer();
1984
2166
  await docsGetAction(refs, options, deps);
@@ -1998,11 +2180,11 @@ function formatDocsList(docs) {
1998
2180
  lines.push(`Found ${docs.pages.length} pages:`);
1999
2181
  lines.push("");
2000
2182
  for (const page of docs.pages) {
2001
- if (!page)
2183
+ if (!page || !page.id)
2002
2184
  continue;
2003
2185
  const words = page.words ? ` (${formatNumber(page.words)} words)` : "";
2004
2186
  lines.push(` ${page.title}${words}`);
2005
- lines.push(` ID: ${page.slug}`);
2187
+ lines.push(` ID: ${page.id}`);
2006
2188
  lines.push("");
2007
2189
  }
2008
2190
  lines.push("Use 'pkgseer docs get <package>/<page-id>' to fetch a page.");
@@ -2020,10 +2202,14 @@ async function docsListAction(packageName, options, deps) {
2020
2202
  }
2021
2203
  if (options.json) {
2022
2204
  const pages = result.data.listPackageDocs.pages?.filter((p) => p) ?? [];
2023
- const slim = pages.map((p) => ({
2024
- slug: p.slug,
2025
- title: p.title
2026
- }));
2205
+ const slim = pages.map((p) => {
2206
+ if (!p || !p.id)
2207
+ return null;
2208
+ return {
2209
+ id: p.id,
2210
+ title: p.title
2211
+ };
2212
+ }).filter((p) => p !== null);
2027
2213
  output(slim, true);
2028
2214
  } else {
2029
2215
  console.log(formatDocsList(result.data.listPackageDocs));
@@ -2031,7 +2217,7 @@ async function docsListAction(packageName, options, deps) {
2031
2217
  }
2032
2218
  var LIST_DESCRIPTION = `List available documentation pages for a package.
2033
2219
 
2034
- Shows all documentation pages with titles, page IDs (slugs),
2220
+ Shows all documentation pages with titles, globally unique page IDs,
2035
2221
  word counts, and descriptions. Use the page ID with 'docs get'
2036
2222
  to fetch the full content of a specific page.
2037
2223
 
@@ -2048,6 +2234,22 @@ function registerDocsListCommand(program) {
2048
2234
  });
2049
2235
  }
2050
2236
  // src/commands/docs/search.ts
2237
+ function summarizeSearchResults(results) {
2238
+ const entries = results.entries ?? [];
2239
+ const count = entries.length;
2240
+ const totalMatches = entries.reduce((acc, e) => acc + (e?.matchCount ?? 0), 0);
2241
+ const lines = [];
2242
+ lines.push(`Results: ${count} page${count === 1 ? "" : "s"} | matches: ${totalMatches}`);
2243
+ lines.push("");
2244
+ for (const entry of entries.filter((e) => !!e)) {
2245
+ lines.push(`- ${entry.title ?? entry.slug ?? "untitled"} (${entry.matchCount ?? 0} matches)`);
2246
+ }
2247
+ if (count === 0) {
2248
+ lines.push("No matches. Try broader terms or fewer constraints.");
2249
+ }
2250
+ return lines.join(`
2251
+ `);
2252
+ }
2051
2253
  function buildDocRef(entry, searchResult) {
2052
2254
  if (!entry?.slug)
2053
2255
  return "";
@@ -2157,7 +2359,7 @@ function formatEntry(entry, searchResult, useColors) {
2157
2359
  function formatSearchResults(results, useColors) {
2158
2360
  const entries = results.entries ?? [];
2159
2361
  if (entries.length === 0) {
2160
- return "No matching documentation found.";
2362
+ return "No matching documentation found. Try broader terms or fewer constraints.";
2161
2363
  }
2162
2364
  const formatted = entries.filter((e) => e != null).map((entry) => formatEntry(entry, results, useColors));
2163
2365
  return formatted.join(`
@@ -2201,25 +2403,24 @@ function slimSearchResults(results) {
2201
2403
  }
2202
2404
  async function docsSearchAction(queryArg, options, deps) {
2203
2405
  const { pkgseerService, config } = deps;
2204
- const searchQuery = queryArg || options.query;
2205
- const keywords = options.keywords;
2206
- if (!searchQuery && (!keywords || keywords.length === 0)) {
2207
- outputError("Search query required. Provide a query as argument or use --query/--keywords", options.json ?? false);
2406
+ const providedTerms = Array.isArray(queryArg) ? queryArg : queryArg ? [queryArg] : [];
2407
+ const terms = providedTerms.concat(options.terms ?? []);
2408
+ if (!terms.length) {
2409
+ outputError("Search terms required. Provide terms as argument or with --terms", options.json ?? false);
2208
2410
  return;
2209
2411
  }
2210
2412
  const contextBefore = options.context ? Number.parseInt(options.context, 10) : options.before ? Number.parseInt(options.before, 10) : 2;
2211
2413
  const contextAfter = options.context ? Number.parseInt(options.context, 10) : options.after ? Number.parseInt(options.after, 10) : 2;
2212
2414
  const maxMatches = options.maxMatches ? Number.parseInt(options.maxMatches, 10) : 5;
2213
2415
  const limit = options.limit ? Number.parseInt(options.limit, 10) : 25;
2214
- const matchMode = options.mode?.toUpperCase() || undefined;
2416
+ const matchMode = options.match_mode?.toUpperCase() || options.mode?.toUpperCase() || undefined;
2215
2417
  const useColors = shouldUseColors(options.noColor);
2216
2418
  const project = options.project ?? config.project;
2217
2419
  const packageName = options.package;
2218
2420
  if (packageName) {
2219
2421
  const registry = toGraphQLRegistry(options.registry ?? "npm");
2220
2422
  const result = await pkgseerService.cliDocsSearch(registry, packageName, {
2221
- query: searchQuery,
2222
- keywords,
2423
+ keywords: terms,
2223
2424
  matchMode,
2224
2425
  limit,
2225
2426
  version: options.pkgVersion,
@@ -2237,8 +2438,7 @@ async function docsSearchAction(queryArg, options, deps) {
2237
2438
  }
2238
2439
  if (project) {
2239
2440
  const result = await pkgseerService.cliProjectDocsSearch(project, {
2240
- query: searchQuery,
2241
- keywords,
2441
+ keywords: terms,
2242
2442
  matchMode,
2243
2443
  limit,
2244
2444
  contextLinesBefore: contextBefore,
@@ -2259,12 +2459,15 @@ async function docsSearchAction(queryArg, options, deps) {
2259
2459
  ` + " - Use --package <name> to search a specific package", options.json ?? false);
2260
2460
  }
2261
2461
  function outputResults(results, options, useColors) {
2262
- if (options.json) {
2462
+ const format = options.json ? "json" : options.format ?? "human";
2463
+ if (format === "json") {
2263
2464
  output(slimSearchResults(results), true);
2264
2465
  } else if (options.refsOnly) {
2265
2466
  console.log(formatRefsOnly(results));
2266
2467
  } else if (options.count) {
2267
2468
  console.log(formatCount(results));
2469
+ } else if (format === "summary") {
2470
+ console.log(summarizeSearchResults(results));
2268
2471
  } else {
2269
2472
  console.log(formatSearchResults(results, useColors));
2270
2473
  }
@@ -2284,11 +2487,11 @@ Examples:
2284
2487
  pkgseer docs search "error handling" --package express
2285
2488
  pkgseer docs search log --package express -C 3
2286
2489
 
2287
- # Multiple keywords (OR by default)
2288
- pkgseer docs search -k "middleware,routing" --package express
2490
+ # Multiple terms (OR by default)
2491
+ pkgseer docs search -t "middleware,routing" --package express
2289
2492
 
2290
2493
  # Strict matching (AND mode)
2291
- pkgseer docs search -k "error,middleware" --mode and --package express
2494
+ pkgseer docs search -t "error,middleware" --match-mode all --package express
2292
2495
 
2293
2496
  # Output for piping
2294
2497
  pkgseer docs search log --package express --refs-only | \\
@@ -2297,14 +2500,14 @@ Examples:
2297
2500
  # Count matches
2298
2501
  pkgseer docs search error --package express --count`;
2299
2502
  function addSearchOptions(cmd) {
2300
- return cmd.option("-p, --package <name>", "Search specific package (overrides project)").option("-r, --registry <registry>", "Package registry (with --package)", "npm").option("-v, --pkg-version <version>", "Package version (with --package)").option("--project <name>", "Project name (overrides config)").option("-q, --query <query>", "Search query (alternative to argument)").option("-k, --keywords <words>", "Comma-separated keywords", (val) => val.split(",").map((s) => s.trim())).option("-l, --limit <n>", "Max results (default: 25)").option("-A, --after <n>", "Lines of context after match (default: 2)").option("-B, --before <n>", "Lines of context before match (default: 2)").option("-C, --context <n>", "Lines of context before and after match").option("--max-matches <n>", "Max matches per page (default: 5)").option("--mode <mode>", "Match mode: or (default), and").option("--refs-only", "Output only page references (for piping)").option("--count", "Output only match counts per page").option("--no-color", "Disable colored output").option("--json", "Output as JSON");
2503
+ return cmd.option("-p, --package <name>", "Search specific package (overrides project)").option("-r, --registry <registry>", "Package registry (with --package)", "npm").option("-v, --pkg-version <version>", "Package version (with --package)").option("--project <name>", "Project name (overrides config)").option("-t, --terms <words>", "Comma-separated search terms", (val) => val.split(",").map((s) => s.trim())).option("-l, --limit <n>", "Max results (default: 25)").option("-A, --after <n>", "Lines of context after match (default: 2)").option("-B, --before <n>", "Lines of context before match (default: 2)").option("-C, --context <n>", "Lines of context before and after match").option("--max-matches <n>", "Max matches per page (default: 5)").option("--match-mode <mode>", "How to combine terms: any/or (default) or all/and").option("--refs-only", "Output only page references (for piping)").option("--count", "Output only match counts per page").option("--format <format>", "Output format: human|summary|json", "human").option("--no-color", "Disable colored output").option("--json", "Output as JSON");
2301
2504
  }
2302
2505
  function registerDocsSearchCommand(program) {
2303
- const cmd = program.command("search [query]").summary("Search documentation").description(SEARCH_DESCRIPTION);
2304
- addSearchOptions(cmd).action(async (query, options) => {
2506
+ const cmd = program.command("search [terms...]").summary("Search documentation").description(SEARCH_DESCRIPTION);
2507
+ addSearchOptions(cmd).action(async (terms, options) => {
2305
2508
  await withCliErrorHandling(options.json ?? false, async () => {
2306
2509
  const deps = await createContainer();
2307
- await docsSearchAction(query, options, deps);
2510
+ await docsSearchAction(terms, options, deps);
2308
2511
  });
2309
2512
  });
2310
2513
  }
@@ -3669,9 +3872,28 @@ var schemas = {
3669
3872
  packageName: z2.string().max(255).describe("Name of the package"),
3670
3873
  version: z2.string().max(100).optional().describe("Specific version (defaults to latest)")
3671
3874
  };
3875
+ function buildHintedMessage(operation, message) {
3876
+ const lower = message.toLowerCase();
3877
+ const isTimeout = lower.includes("-32001") || lower.includes("timeout") || lower.includes("timed out");
3878
+ if (isTimeout) {
3879
+ return `Failed to ${operation}: ${message}. ` + "Hint: try lowering the limit or narrowing the scope, then retry.";
3880
+ }
3881
+ return `Failed to ${operation}: ${message}`;
3882
+ }
3672
3883
  function handleGraphQLErrors(errors) {
3673
3884
  if (errors && errors.length > 0) {
3674
- return errorResult(`Error: ${errors.map((e) => e.message).join(", ")}`);
3885
+ const messages = errors.map((e) => e.message).filter(Boolean);
3886
+ const combined = messages.join(", ");
3887
+ const lower = combined.toLowerCase();
3888
+ const hasNotFound = lower.includes("not found") || lower.includes("does not exist");
3889
+ const hasTimeout = lower.includes("-32001") || lower.includes("timeout") || lower.includes("timed out");
3890
+ if (hasNotFound) {
3891
+ return errorResult(`Error: ${combined}. Hint: verify the name/registry and that the resource exists.`);
3892
+ }
3893
+ if (hasTimeout) {
3894
+ return errorResult(`Error: ${combined}. Hint: try lowering the limit or narrowing the scope, then retry.`);
3895
+ }
3896
+ return errorResult(`Error: ${combined}`);
3675
3897
  }
3676
3898
  return null;
3677
3899
  }
@@ -3680,7 +3902,7 @@ async function withErrorHandling(operation, fn) {
3680
3902
  return await fn();
3681
3903
  } catch (error2) {
3682
3904
  const message = error2 instanceof Error ? error2.message : "Unknown error";
3683
- return errorResult(`Failed to ${operation}: ${message}`);
3905
+ return errorResult(buildHintedMessage(operation, message));
3684
3906
  }
3685
3907
  }
3686
3908
  function notFoundError(packageName, registry) {
@@ -3699,7 +3921,7 @@ var argsSchema = {
3699
3921
  function createComparePackagesTool(pkgseerService) {
3700
3922
  return {
3701
3923
  name: "compare_packages",
3702
- description: "Compares multiple packages across metadata, quality, and security dimensions",
3924
+ description: 'Compares 2-10 packages across metadata, quality, and security. Example: [{"registry":"npm","name":"react","version":"18.2.0"},{"registry":"pypi","name":"requests"}].',
3703
3925
  schema: argsSchema,
3704
3926
  handler: async ({ packages }, _extra) => {
3705
3927
  return withErrorHandling("compare packages", async () => {
@@ -3723,26 +3945,23 @@ function createComparePackagesTool(pkgseerService) {
3723
3945
  // src/tools/fetch-package-doc.ts
3724
3946
  import { z as z4 } from "zod";
3725
3947
  var argsSchema2 = {
3726
- registry: schemas.registry,
3727
- package_name: schemas.packageName.describe("Name of the package to fetch documentation for"),
3728
- page_id: z4.string().max(500).describe("Documentation page identifier (from list_package_docs)"),
3729
- version: schemas.version
3948
+ page_id: z4.string().max(500).describe("Globally unique documentation page identifier (from list_package_docs)")
3730
3949
  };
3731
3950
  function createFetchPackageDocTool(pkgseerService) {
3732
3951
  return {
3733
3952
  name: "fetch_package_doc",
3734
- description: "Fetches the full content of a specific documentation page. Returns page title, content (markdown/HTML), breadcrumbs, and source attribution. Use list_package_docs first to get available page IDs.",
3953
+ description: "Fetches the full content of a documentation page by globally unique page ID (from list_package_docs). Returns full metadata (title, content, format, breadcrumbs, source, base URL, last updated).",
3735
3954
  schema: argsSchema2,
3736
- handler: async ({ registry, package_name, page_id, version: version2 }, _extra) => {
3955
+ handler: async ({ page_id }, _extra) => {
3737
3956
  return withErrorHandling("fetch documentation page", async () => {
3738
- const result = await pkgseerService.fetchPackageDoc(toGraphQLRegistry2(registry), package_name, page_id, version2);
3957
+ const result = await pkgseerService.getDocPage(page_id);
3739
3958
  const graphqlError = handleGraphQLErrors(result.errors);
3740
3959
  if (graphqlError)
3741
3960
  return graphqlError;
3742
- if (!result.data.fetchPackageDoc) {
3743
- return errorResult(`Documentation page not found: ${page_id} for ${package_name} in ${registry}`);
3961
+ if (!result.data.getDocPage) {
3962
+ return errorResult(`Documentation page not found: ${page_id}`);
3744
3963
  }
3745
- return textResult(JSON.stringify(result.data.fetchPackageDoc, null, 2));
3964
+ return textResult(JSON.stringify(result.data.getDocPage, null, 2));
3746
3965
  });
3747
3966
  }
3748
3967
  };
@@ -3756,7 +3975,7 @@ var argsSchema3 = {
3756
3975
  function createListPackageDocsTool(pkgseerService) {
3757
3976
  return {
3758
3977
  name: "list_package_docs",
3759
- description: "Lists documentation pages for a package version. Returns page titles, slugs, and metadata. Use this to discover available documentation before fetching specific pages.",
3978
+ description: "Lists documentation pages for a package version. Returns page titles, globally unique IDs, and metadata. Use this to discover available documentation before fetching specific pages.",
3760
3979
  schema: argsSchema3,
3761
3980
  handler: async ({ registry, package_name, version: version2 }, _extra) => {
3762
3981
  return withErrorHandling("list package documentation", async () => {
@@ -3781,10 +4000,82 @@ var argsSchema4 = {
3781
4000
  include_transitive: z5.boolean().optional().describe("Whether to include transitive dependency DAG"),
3782
4001
  max_depth: z5.number().int().min(1).max(10).optional().describe("Maximum depth for transitive traversal (1-10)")
3783
4002
  };
4003
+ function decodeDag(rawDag) {
4004
+ if (!rawDag || typeof rawDag !== "object")
4005
+ return;
4006
+ const dag = rawDag;
4007
+ const nodesSource = dag.n ?? dag.nodes;
4008
+ const nodesRaw = nodesSource && typeof nodesSource === "object" && !Array.isArray(nodesSource) ? nodesSource : undefined;
4009
+ const edgesSource = dag.e ?? dag.edges;
4010
+ const edgesRaw = Array.isArray(edgesSource) ? edgesSource : undefined;
4011
+ const nodes = nodesRaw ? Object.entries(nodesRaw).map(([id, value]) => {
4012
+ const {
4013
+ n: nameField,
4014
+ v: versionField,
4015
+ l: labelField,
4016
+ ...rest
4017
+ } = value ?? {};
4018
+ return {
4019
+ id,
4020
+ name: nameField ?? rest.name,
4021
+ version: versionField ?? rest.version,
4022
+ label: labelField ?? rest.label,
4023
+ ...rest
4024
+ };
4025
+ }) : [];
4026
+ const edges = edgesRaw?.map((edge) => {
4027
+ if (Array.isArray(edge) && edge.length >= 2) {
4028
+ return { from: String(edge[0]), to: String(edge[1]) };
4029
+ }
4030
+ const edgeObj = edge ?? {};
4031
+ return {
4032
+ from: String(edgeObj.f ?? edgeObj.from ?? edgeObj.source ?? edgeObj.s ?? ""),
4033
+ to: String(edgeObj.t ?? edgeObj.to ?? edgeObj.target ?? edgeObj.d ?? "")
4034
+ };
4035
+ }) ?? [];
4036
+ return {
4037
+ version: dag.v ?? dag.version,
4038
+ nodes,
4039
+ edges
4040
+ };
4041
+ }
4042
+ function buildEdgeDepths(decodedDag, rootId) {
4043
+ if (!decodedDag)
4044
+ return;
4045
+ const root = rootId ?? decodedDag.nodes[0]?.id;
4046
+ if (!root)
4047
+ return;
4048
+ const depthMap = new Map([[root, 0]]);
4049
+ const adjacency = new Map;
4050
+ for (const edge of decodedDag.edges) {
4051
+ if (!edge.from || !edge.to)
4052
+ continue;
4053
+ const list = adjacency.get(edge.from) ?? [];
4054
+ list.push(edge.to);
4055
+ adjacency.set(edge.from, list);
4056
+ }
4057
+ const queue = [root];
4058
+ while (queue.length > 0) {
4059
+ const current = queue.shift();
4060
+ const currentDepth = depthMap.get(current) ?? 0;
4061
+ for (const next of adjacency.get(current) ?? []) {
4062
+ if (!depthMap.has(next)) {
4063
+ depthMap.set(next, currentDepth + 1);
4064
+ queue.push(next);
4065
+ }
4066
+ }
4067
+ }
4068
+ return decodedDag.edges.map((edge) => ({
4069
+ from: edge.from,
4070
+ to: edge.to,
4071
+ depthFrom: depthMap.get(edge.from),
4072
+ depthTo: depthMap.get(edge.to)
4073
+ }));
4074
+ }
3784
4075
  function createPackageDependenciesTool(pkgseerService) {
3785
4076
  return {
3786
4077
  name: "package_dependencies",
3787
- description: "Retrieves direct and transitive dependencies for a package version",
4078
+ description: "Retrieves direct and transitive dependencies for a package version. Options: include_transitive (bool) and max_depth (1-10). Transitive output includes a DAG (`n/e/v` keys) decoded into readable nodes/edges with depth and counts; raw DAG is preserved alongside decoded form.",
3788
4079
  schema: argsSchema4,
3789
4080
  handler: async ({ registry, package_name, version: version2, include_transitive, max_depth }, _extra) => {
3790
4081
  return withErrorHandling("fetch package dependencies", async () => {
@@ -3795,7 +4086,29 @@ function createPackageDependenciesTool(pkgseerService) {
3795
4086
  if (!result.data.packageDependencies) {
3796
4087
  return notFoundError(package_name, registry);
3797
4088
  }
3798
- return textResult(JSON.stringify(result.data.packageDependencies, null, 2));
4089
+ const deps = result.data.packageDependencies.dependencies;
4090
+ const decodedDag = decodeDag(deps?.transitive?.dag);
4091
+ const transitiveDag = deps?.transitive?.dag;
4092
+ const edgesWithDepth = buildEdgeDepths(decodedDag, typeof transitiveDag?.root === "string" ? transitiveDag.root : undefined);
4093
+ const output2 = {
4094
+ summary: deps?.summary,
4095
+ package: result.data.packageDependencies.package,
4096
+ direct: deps?.direct,
4097
+ transitive: deps?.transitive ? {
4098
+ totalEdges: deps.transitive.totalEdges,
4099
+ uniquePackagesCount: deps.transitive.uniquePackagesCount,
4100
+ uniqueDependencies: deps.transitive.uniqueDependencies,
4101
+ conflicts: deps.transitive.conflicts,
4102
+ circularDependencies: deps.transitive.circularDependencies,
4103
+ dag: {
4104
+ decoded: decodedDag,
4105
+ edgesWithDepth,
4106
+ raw: deps.transitive.dag
4107
+ }
4108
+ } : undefined,
4109
+ raw: result.data.packageDependencies
4110
+ };
4111
+ return textResult(JSON.stringify(output2, null, 2));
3799
4112
  });
3800
4113
  }
3801
4114
  };
@@ -3879,8 +4192,8 @@ import { z as z6 } from "zod";
3879
4192
  var argsSchema8 = {
3880
4193
  registry: schemas.registry,
3881
4194
  package_name: schemas.packageName.describe("Name of the package to search documentation for"),
3882
- keywords: z6.array(z6.string()).optional().describe("Keywords to search for; combined into a single query"),
3883
- query: z6.string().max(500).optional().describe("Freeform search query (alternative to keywords)"),
4195
+ terms: z6.array(z6.string()).optional().describe("Search terms to match. Provide a few key words or phrases that should appear in results."),
4196
+ match_mode: z6.enum(["any", "or", "all", "and"]).optional().describe('How to combine terms: "any" (OR) or "all" (AND).'),
3884
4197
  include_snippets: z6.boolean().optional().describe("Include content excerpts around matches"),
3885
4198
  limit: z6.number().int().min(1).max(100).optional().describe("Maximum number of results to return"),
3886
4199
  version: schemas.version
@@ -3888,25 +4201,28 @@ var argsSchema8 = {
3888
4201
  function createSearchPackageDocsTool(pkgseerService) {
3889
4202
  return {
3890
4203
  name: "search_package_docs",
3891
- description: "Searches package documentation with keyword or freeform query support. Returns ranked results with relevance scores. Use include_snippets=true to get content excerpts showing match context.",
4204
+ description: "Searches package documentation using search terms and match modes (any/all). Returns ranked results with match context. Defaults to include_snippets=true.",
3892
4205
  schema: argsSchema8,
3893
4206
  handler: async ({
3894
4207
  registry,
3895
4208
  package_name,
3896
- keywords,
3897
- query,
4209
+ terms,
4210
+ match_mode,
3898
4211
  include_snippets,
3899
4212
  limit,
3900
4213
  version: version2
3901
4214
  }, _extra) => {
3902
4215
  return withErrorHandling("search package documentation", async () => {
3903
- if (!keywords?.length && !query) {
3904
- return errorResult("Either keywords or query must be provided for search");
4216
+ const normalizedTerms = terms?.map((term) => term.trim()).filter((term) => term.length > 0) ?? [];
4217
+ if (normalizedTerms.length === 0) {
4218
+ return errorResult("Search terms are required to run documentation search.");
3905
4219
  }
4220
+ const matchMode = match_mode === "all" || match_mode === "and" ? "AND" : match_mode === "any" || match_mode === "or" ? "OR" : undefined;
4221
+ const includeSnippets = include_snippets ?? true;
3906
4222
  const result = await pkgseerService.searchPackageDocs(toGraphQLRegistry2(registry), package_name, {
3907
- keywords,
3908
- query,
3909
- includeSnippets: include_snippets,
4223
+ keywords: normalizedTerms,
4224
+ matchMode,
4225
+ includeSnippets,
3910
4226
  limit,
3911
4227
  version: version2
3912
4228
  });
@@ -3916,6 +4232,9 @@ function createSearchPackageDocsTool(pkgseerService) {
3916
4232
  if (!result.data.searchPackageDocs) {
3917
4233
  return errorResult(`No documentation found for ${package_name} in ${registry}`);
3918
4234
  }
4235
+ if ((result.data.searchPackageDocs.entries ?? []).length === 0 && normalizedTerms.length > 0) {
4236
+ return errorResult(`No documentation matched: ${normalizedTerms.join(", ")}. ` + "Try fewer or broader terms, or reduce match constraints.");
4237
+ }
3919
4238
  return textResult(JSON.stringify(result.data.searchPackageDocs, null, 2));
3920
4239
  });
3921
4240
  }
@@ -3925,8 +4244,8 @@ function createSearchPackageDocsTool(pkgseerService) {
3925
4244
  import { z as z7 } from "zod";
3926
4245
  var argsSchema9 = {
3927
4246
  project: z7.string().optional().describe("Project name to search. Optional if configured in pkgseer.yml; only needed to search a different project."),
3928
- keywords: z7.array(z7.string()).optional().describe("Keywords to search for; combined into a single query"),
3929
- query: z7.string().max(500).optional().describe("Freeform search query (alternative to keywords)"),
4247
+ terms: z7.array(z7.string()).optional().describe("Search terms to match. Provide a few key words or phrases that should appear in results."),
4248
+ match_mode: z7.enum(["any", "or", "all", "and"]).optional().describe('How to combine terms: "any" (OR) or "all" (AND).'),
3930
4249
  include_snippets: z7.boolean().optional().describe("Include content excerpts around matches"),
3931
4250
  limit: z7.number().int().min(1).max(100).optional().describe("Maximum number of results to return")
3932
4251
  };
@@ -3934,21 +4253,24 @@ function createSearchProjectDocsTool(deps) {
3934
4253
  const { pkgseerService, config } = deps;
3935
4254
  return {
3936
4255
  name: "search_project_docs",
3937
- description: "Searches documentation across all dependencies in a PkgSeer project. Returns ranked results from multiple packages. Uses project from pkgseer.yml config by default.",
4256
+ description: "Searches documentation across all dependencies in a PkgSeer project using search terms and match modes (any/all). Returns ranked results from multiple packages. Uses project from pkgseer.yml config by default.",
3938
4257
  schema: argsSchema9,
3939
- handler: async ({ project, keywords, query, include_snippets, limit }, _extra) => {
4258
+ handler: async ({ project, terms, match_mode, include_snippets, limit }, _extra) => {
3940
4259
  return withErrorHandling("search project documentation", async () => {
3941
4260
  const resolvedProject = project ?? config.project;
3942
4261
  if (!resolvedProject) {
3943
4262
  return errorResult("No project provided and none configured in pkgseer.yml. " + "Either pass project parameter or add project to your config.");
3944
4263
  }
3945
- if (!keywords?.length && !query) {
3946
- return errorResult("Either keywords or query must be provided for search");
4264
+ const normalizedTerms = terms?.map((term) => term.trim()).filter((term) => term.length > 0) ?? [];
4265
+ if (normalizedTerms.length === 0) {
4266
+ return errorResult("Search terms are required to run documentation search.");
3947
4267
  }
4268
+ const matchMode = match_mode === "all" || match_mode === "and" ? "AND" : match_mode === "any" || match_mode === "or" ? "OR" : undefined;
4269
+ const includeSnippets = include_snippets ?? true;
3948
4270
  const result = await pkgseerService.searchProjectDocs(resolvedProject, {
3949
- keywords,
3950
- query,
3951
- includeSnippets: include_snippets,
4271
+ keywords: normalizedTerms,
4272
+ matchMode,
4273
+ includeSnippets,
3952
4274
  limit
3953
4275
  });
3954
4276
  const graphqlError = handleGraphQLErrors(result.errors);
@@ -3957,6 +4279,9 @@ function createSearchProjectDocsTool(deps) {
3957
4279
  if (!result.data.searchProjectDocs) {
3958
4280
  return errorResult(`Project not found: ${resolvedProject}`);
3959
4281
  }
4282
+ if ((result.data.searchProjectDocs.entries ?? []).length === 0 && normalizedTerms.length > 0) {
4283
+ return errorResult(`No documentation matched: ${normalizedTerms.join(", ")}. ` + "Try fewer or broader terms, or reduce match constraints.");
4284
+ }
3960
4285
  return textResult(JSON.stringify(result.data.searchProjectDocs, null, 2));
3961
4286
  });
3962
4287
  }
@@ -4174,7 +4499,7 @@ function registerPkgCompareCommand(program) {
4174
4499
  });
4175
4500
  }
4176
4501
  // src/commands/pkg/deps.ts
4177
- function formatPackageDependencies(data) {
4502
+ function formatPackageDependencies(data, transitiveRequested) {
4178
4503
  const lines = [];
4179
4504
  const pkg = data.package;
4180
4505
  const deps = data.dependencies;
@@ -4202,32 +4527,52 @@ function formatPackageDependencies(data) {
4202
4527
  } else {
4203
4528
  lines.push("No direct dependencies.");
4204
4529
  }
4530
+ if (!transitiveRequested) {
4531
+ lines.push("");
4532
+ lines.push("Tip: use --transitive for full dependency graph traversal.");
4533
+ }
4205
4534
  return lines.join(`
4206
4535
  `);
4207
4536
  }
4208
4537
  async function pkgDepsAction(packageName, options, deps) {
4209
4538
  const { pkgseerService } = deps;
4210
4539
  const registry = toGraphQLRegistry(options.registry);
4211
- const result = await pkgseerService.cliPackageDeps(registry, packageName, options.pkgVersion, options.transitive);
4540
+ const transitiveRequested = options.transitive ?? false;
4541
+ const result = await pkgseerService.cliPackageDeps(registry, packageName, options.pkgVersion, options.transitive, options.maxDepth ? Number.parseInt(options.maxDepth, 10) : undefined);
4212
4542
  handleErrors(result.errors, options.json ?? false);
4213
4543
  if (!result.data.packageDependencies) {
4214
4544
  outputError(`Package not found: ${packageName} in ${options.registry}`, options.json ?? false);
4215
4545
  return;
4216
4546
  }
4217
- if (options.json) {
4547
+ const format = options.json ? "json" : options.format ?? "human";
4548
+ if (format === "json") {
4218
4549
  const data = result.data.packageDependencies;
4219
- const slim = {
4550
+ output({
4220
4551
  package: `${data.package?.name}@${data.package?.version}`,
4221
4552
  directCount: data.dependencies?.summary?.directCount ?? 0,
4222
- dependencies: data.dependencies?.direct?.filter((d) => d).map((d) => ({
4553
+ uniquePackagesCount: data.dependencies?.summary?.uniquePackagesCount ?? 0,
4554
+ transitiveIncluded: options.transitive ?? false,
4555
+ dependencies: data.dependencies?.direct?.filter((d) => Boolean(d)).map((d) => ({
4223
4556
  name: d.name,
4224
4557
  version: d.versionConstraint,
4225
4558
  type: d.type
4226
4559
  }))
4227
- };
4228
- output(slim, true);
4560
+ }, true);
4561
+ } else if (format === "summary") {
4562
+ const data = result.data.packageDependencies;
4563
+ const deps2 = data.dependencies;
4564
+ const directCount = deps2?.summary?.directCount ?? 0;
4565
+ const uniquePackagesCount = deps2?.summary?.uniquePackagesCount ?? 0;
4566
+ const lines = [
4567
+ `Package: ${data.package?.name ?? ""}@${data.package?.version ?? ""}`,
4568
+ `Direct deps: ${directCount}`,
4569
+ `Unique packages: ${uniquePackagesCount || "N/A"}`,
4570
+ transitiveRequested ? "Transitive: included" : "Transitive: not included (use --transitive)"
4571
+ ];
4572
+ console.log(lines.join(`
4573
+ `));
4229
4574
  } else {
4230
- console.log(formatPackageDependencies(result.data.packageDependencies));
4575
+ console.log(formatPackageDependencies(result.data.packageDependencies, transitiveRequested));
4231
4576
  }
4232
4577
  }
4233
4578
  var DEPS_DESCRIPTION = `Get package dependencies.
@@ -4240,7 +4585,7 @@ Examples:
4240
4585
  pkgseer pkg deps lodash --transitive
4241
4586
  pkgseer pkg deps requests --registry pypi --json`;
4242
4587
  function registerPkgDepsCommand(program) {
4243
- program.command("deps <package>").summary("Get package dependencies").description(DEPS_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-v, --pkg-version <version>", "Package version").option("-t, --transitive", "Include transitive dependencies").option("--json", "Output as JSON").action(async (packageName, options) => {
4588
+ program.command("deps <package>").summary("Get package dependencies").description(DEPS_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-v, --pkg-version <version>", "Package version").option("-t, --transitive", "Include transitive dependencies").option("--max-depth <n>", "Maximum transitive depth (1-10, defaults to server)").option("--format <format>", "Output format: human|summary|json", "human").option("--json", "Output as JSON").action(async (packageName, options) => {
4244
4589
  await withCliErrorHandling(options.json ?? false, async () => {
4245
4590
  const deps = await createContainer();
4246
4591
  await pkgDepsAction(packageName, options, deps);
@@ -4341,18 +4686,15 @@ function formatPackageQuality(data) {
4341
4686
  if (!quality) {
4342
4687
  return "No quality data available.";
4343
4688
  }
4344
- lines.push(`\uD83D\uDCCA Quality Score: ${formatScore(quality.overallScore)} (Grade: ${quality.grade})`);
4345
- lines.push("");
4346
- if (quality.categories && quality.categories.length > 0) {
4347
- lines.push("Category Breakdown:");
4348
- for (const category of quality.categories) {
4349
- if (category) {
4350
- const name = (category.category || "Unknown").padEnd(20);
4351
- lines.push(` ${name} ${formatScore(category.score)}`);
4352
- }
4689
+ const topCategories = (quality.categories ?? []).filter((c) => Boolean(c)).sort((a, b) => (b?.score ?? 0) - (a?.score ?? 0)).slice(0, 3);
4690
+ lines.push(`\uD83D\uDCCA Quality: Grade ${quality.grade ?? "N/A"} (${formatScore(quality.overallScore)})`);
4691
+ if (topCategories.length > 0) {
4692
+ lines.push("Top drivers:");
4693
+ for (const category of topCategories) {
4694
+ lines.push(` - ${(category.category ?? "Unknown").toLowerCase()}: ${formatScore(category.score)}`);
4353
4695
  }
4354
- lines.push("");
4355
4696
  }
4697
+ lines.push("Tip: use --json for full category breakdown.");
4356
4698
  return lines.join(`
4357
4699
  `);
4358
4700
  }
@@ -4365,7 +4707,8 @@ async function pkgQualityAction(packageName, options, deps) {
4365
4707
  outputError(`Package not found: ${packageName} in ${options.registry}`, options.json ?? false);
4366
4708
  return;
4367
4709
  }
4368
- if (options.json) {
4710
+ const format = options.json ? "json" : options.format ?? "human";
4711
+ if (format === "json") {
4369
4712
  const quality = result.data.packageQuality.quality;
4370
4713
  const slim = {
4371
4714
  package: `${result.data.packageQuality.package?.name}@${result.data.packageQuality.package?.version}`,
@@ -4394,7 +4737,7 @@ Examples:
4394
4737
  pkgseer pkg quality express -v 4.18.0
4395
4738
  pkgseer pkg quality requests --registry pypi --json`;
4396
4739
  function registerPkgQualityCommand(program) {
4397
- program.command("quality <package>").summary("Get package quality score").description(QUALITY_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-v, --pkg-version <version>", "Package version").option("--json", "Output as JSON").action(async (packageName, options) => {
4740
+ program.command("quality <package>").summary("Get package quality score").description(QUALITY_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-v, --pkg-version <version>", "Package version").option("--format <format>", "Output format: human|summary|json", "human").option("--json", "Output as JSON").action(async (packageName, options) => {
4398
4741
  await withCliErrorHandling(options.json ?? false, async () => {
4399
4742
  const deps = await createContainer();
4400
4743
  await pkgQualityAction(packageName, options, deps);
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  version
3
- } from "./shared/chunk-2wbvwsc9.js";
3
+ } from "./shared/chunk-z505vakm.js";
4
4
  export {
5
5
  version
6
6
  };
@@ -1,4 +1,4 @@
1
1
  // package.json
2
- var version = "0.1.2";
2
+ var version = "0.1.4";
3
3
 
4
4
  export { version };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pkgseer/cli",
3
3
  "description": "CLI companion for PkgSeer - package intelligence for developers and AI assistants",
4
- "version": "0.1.2",
4
+ "version": "0.1.4",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",