@pkgseer/cli 0.1.3 → 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-8dn0z2ja.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
@@ -488,12 +489,12 @@ var GetDocPageDocument = gql`
488
489
  }
489
490
  `;
490
491
  var SearchPackageDocsDocument = gql`
491
- 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) {
492
493
  searchPackageDocs(
493
494
  registry: $registry
494
495
  packageName: $packageName
495
496
  keywords: $keywords
496
- query: $query
497
+ matchMode: $matchMode
497
498
  includeSnippets: $includeSnippets
498
499
  limit: $limit
499
500
  version: $version
@@ -502,7 +503,6 @@ var SearchPackageDocsDocument = gql`
502
503
  registry
503
504
  packageName
504
505
  version
505
- query
506
506
  entries {
507
507
  id
508
508
  title
@@ -523,17 +523,16 @@ var SearchPackageDocsDocument = gql`
523
523
  }
524
524
  `;
525
525
  var SearchProjectDocsDocument = gql`
526
- 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) {
527
527
  searchProjectDocs(
528
528
  project: $project
529
529
  keywords: $keywords
530
- query: $query
530
+ matchMode: $matchMode
531
531
  includeSnippets: $includeSnippets
532
532
  limit: $limit
533
533
  ) {
534
534
  schemaVersion
535
535
  project
536
- query
537
536
  entries {
538
537
  id
539
538
  title
@@ -1255,7 +1254,7 @@ class PkgseerServiceImpl {
1255
1254
  registry,
1256
1255
  packageName,
1257
1256
  keywords: options?.keywords,
1258
- query: options?.query,
1257
+ matchMode: options?.matchMode,
1259
1258
  includeSnippets: options?.includeSnippets,
1260
1259
  limit: options?.limit,
1261
1260
  version: options?.version
@@ -1266,7 +1265,7 @@ class PkgseerServiceImpl {
1266
1265
  const result = await this.client.SearchProjectDocs({
1267
1266
  project,
1268
1267
  keywords: options?.keywords,
1269
- query: options?.query,
1268
+ matchMode: options?.matchMode,
1270
1269
  includeSnippets: options?.includeSnippets,
1271
1270
  limit: options?.limit
1272
1271
  });
@@ -1292,12 +1291,13 @@ class PkgseerServiceImpl {
1292
1291
  });
1293
1292
  return { data: result.data, errors: result.errors };
1294
1293
  }
1295
- async cliPackageDeps(registry, name, version2, includeTransitive) {
1294
+ async cliPackageDeps(registry, name, version2, includeTransitive, maxDepth) {
1296
1295
  const result = await this.client.CliPackageDeps({
1297
1296
  registry,
1298
1297
  name,
1299
1298
  version: version2,
1300
- includeTransitive
1299
+ includeTransitive,
1300
+ maxDepth
1301
1301
  });
1302
1302
  return { data: result.data, errors: result.errors };
1303
1303
  }
@@ -1724,10 +1724,26 @@ function outputError(message, json) {
1724
1724
  process.exit(1);
1725
1725
  }
1726
1726
  function handleErrors(errors, json) {
1727
- if (errors && errors.length > 0) {
1728
- const message = errors.map((e) => e.message).join(", ");
1729
- outputError(message, json);
1730
- }
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);
1731
1747
  }
1732
1748
  function formatNumber(num) {
1733
1749
  if (num == null)
@@ -1805,6 +1821,19 @@ async function withCliErrorHandling(json, fn) {
1805
1821
  return;
1806
1822
  }
1807
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
+ }
1808
1837
  const colonMatch = message.match(/^([^:]+):/);
1809
1838
  const shortMessage = colonMatch?.[1] ?? message;
1810
1839
  outputError(shortMessage, json);
@@ -1855,7 +1884,7 @@ function parsePackageRef(ref) {
1855
1884
  }
1856
1885
  return { packageName, pageId, originalRef: ref };
1857
1886
  }
1858
- function formatDocPage(data, ref, verbose = false) {
1887
+ function formatDocPage(data, ref, verbose = false, previewLines) {
1859
1888
  const lines = [];
1860
1889
  const page = data.page;
1861
1890
  if (!page) {
@@ -1913,9 +1942,22 @@ function formatDocPage(data, ref, verbose = false) {
1913
1942
  lines.push(`# ${page.title}`);
1914
1943
  lines.push("");
1915
1944
  if (page.content) {
1916
- 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
+ }
1917
1959
  } else {
1918
- lines.push("(No content available)");
1960
+ lines.push("(No content available. Verify the page ID or try another version.)");
1919
1961
  }
1920
1962
  return lines.join(`
1921
1963
  `);
@@ -2028,6 +2070,8 @@ async function docsGetAction(refs, options, deps) {
2028
2070
  }
2029
2071
  const { pkgseerService } = deps;
2030
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;
2031
2075
  const parsedRefs = [];
2032
2076
  for (const ref of refs) {
2033
2077
  try {
@@ -2078,7 +2122,7 @@ ${errorMessages}`, options.json ?? false);
2078
2122
  output(jsonResults, true);
2079
2123
  } else {
2080
2124
  if (successes.length > 0) {
2081
- const pages = successes.filter((r) => r.result !== null).map((r) => formatDocPage(r.result, r.ref, options.verbose));
2125
+ const pages = successes.filter((r) => r.result !== null).map((r) => formatDocPage(r.result, r.ref, options.verbose, resolvedPreview));
2082
2126
  console.log(pages.join(`
2083
2127
 
2084
2128
  ---
@@ -2116,7 +2160,7 @@ Examples:
2116
2160
  # Multiple pages
2117
2161
  pkgseer docs get 24293-shared-plugins-during-build-2 npm/express/4.18.2/readme`;
2118
2162
  function registerDocsGetCommand(program) {
2119
- 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.)").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) => {
2120
2164
  await withCliErrorHandling(options.json ?? false, async () => {
2121
2165
  const deps = await createContainer();
2122
2166
  await docsGetAction(refs, options, deps);
@@ -2190,6 +2234,22 @@ function registerDocsListCommand(program) {
2190
2234
  });
2191
2235
  }
2192
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
+ }
2193
2253
  function buildDocRef(entry, searchResult) {
2194
2254
  if (!entry?.slug)
2195
2255
  return "";
@@ -2299,7 +2359,7 @@ function formatEntry(entry, searchResult, useColors) {
2299
2359
  function formatSearchResults(results, useColors) {
2300
2360
  const entries = results.entries ?? [];
2301
2361
  if (entries.length === 0) {
2302
- return "No matching documentation found.";
2362
+ return "No matching documentation found. Try broader terms or fewer constraints.";
2303
2363
  }
2304
2364
  const formatted = entries.filter((e) => e != null).map((entry) => formatEntry(entry, results, useColors));
2305
2365
  return formatted.join(`
@@ -2343,25 +2403,24 @@ function slimSearchResults(results) {
2343
2403
  }
2344
2404
  async function docsSearchAction(queryArg, options, deps) {
2345
2405
  const { pkgseerService, config } = deps;
2346
- const searchQuery = queryArg || options.query;
2347
- const keywords = options.keywords;
2348
- if (!searchQuery && (!keywords || keywords.length === 0)) {
2349
- 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);
2350
2410
  return;
2351
2411
  }
2352
2412
  const contextBefore = options.context ? Number.parseInt(options.context, 10) : options.before ? Number.parseInt(options.before, 10) : 2;
2353
2413
  const contextAfter = options.context ? Number.parseInt(options.context, 10) : options.after ? Number.parseInt(options.after, 10) : 2;
2354
2414
  const maxMatches = options.maxMatches ? Number.parseInt(options.maxMatches, 10) : 5;
2355
2415
  const limit = options.limit ? Number.parseInt(options.limit, 10) : 25;
2356
- const matchMode = options.mode?.toUpperCase() || undefined;
2416
+ const matchMode = options.match_mode?.toUpperCase() || options.mode?.toUpperCase() || undefined;
2357
2417
  const useColors = shouldUseColors(options.noColor);
2358
2418
  const project = options.project ?? config.project;
2359
2419
  const packageName = options.package;
2360
2420
  if (packageName) {
2361
2421
  const registry = toGraphQLRegistry(options.registry ?? "npm");
2362
2422
  const result = await pkgseerService.cliDocsSearch(registry, packageName, {
2363
- query: searchQuery,
2364
- keywords,
2423
+ keywords: terms,
2365
2424
  matchMode,
2366
2425
  limit,
2367
2426
  version: options.pkgVersion,
@@ -2379,8 +2438,7 @@ async function docsSearchAction(queryArg, options, deps) {
2379
2438
  }
2380
2439
  if (project) {
2381
2440
  const result = await pkgseerService.cliProjectDocsSearch(project, {
2382
- query: searchQuery,
2383
- keywords,
2441
+ keywords: terms,
2384
2442
  matchMode,
2385
2443
  limit,
2386
2444
  contextLinesBefore: contextBefore,
@@ -2401,12 +2459,15 @@ async function docsSearchAction(queryArg, options, deps) {
2401
2459
  ` + " - Use --package <name> to search a specific package", options.json ?? false);
2402
2460
  }
2403
2461
  function outputResults(results, options, useColors) {
2404
- if (options.json) {
2462
+ const format = options.json ? "json" : options.format ?? "human";
2463
+ if (format === "json") {
2405
2464
  output(slimSearchResults(results), true);
2406
2465
  } else if (options.refsOnly) {
2407
2466
  console.log(formatRefsOnly(results));
2408
2467
  } else if (options.count) {
2409
2468
  console.log(formatCount(results));
2469
+ } else if (format === "summary") {
2470
+ console.log(summarizeSearchResults(results));
2410
2471
  } else {
2411
2472
  console.log(formatSearchResults(results, useColors));
2412
2473
  }
@@ -2426,11 +2487,11 @@ Examples:
2426
2487
  pkgseer docs search "error handling" --package express
2427
2488
  pkgseer docs search log --package express -C 3
2428
2489
 
2429
- # Multiple keywords (OR by default)
2430
- pkgseer docs search -k "middleware,routing" --package express
2490
+ # Multiple terms (OR by default)
2491
+ pkgseer docs search -t "middleware,routing" --package express
2431
2492
 
2432
2493
  # Strict matching (AND mode)
2433
- pkgseer docs search -k "error,middleware" --mode and --package express
2494
+ pkgseer docs search -t "error,middleware" --match-mode all --package express
2434
2495
 
2435
2496
  # Output for piping
2436
2497
  pkgseer docs search log --package express --refs-only | \\
@@ -2439,14 +2500,14 @@ Examples:
2439
2500
  # Count matches
2440
2501
  pkgseer docs search error --package express --count`;
2441
2502
  function addSearchOptions(cmd) {
2442
- 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");
2443
2504
  }
2444
2505
  function registerDocsSearchCommand(program) {
2445
- const cmd = program.command("search [query]").summary("Search documentation").description(SEARCH_DESCRIPTION);
2446
- 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) => {
2447
2508
  await withCliErrorHandling(options.json ?? false, async () => {
2448
2509
  const deps = await createContainer();
2449
- await docsSearchAction(query, options, deps);
2510
+ await docsSearchAction(terms, options, deps);
2450
2511
  });
2451
2512
  });
2452
2513
  }
@@ -3811,9 +3872,28 @@ var schemas = {
3811
3872
  packageName: z2.string().max(255).describe("Name of the package"),
3812
3873
  version: z2.string().max(100).optional().describe("Specific version (defaults to latest)")
3813
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
+ }
3814
3883
  function handleGraphQLErrors(errors) {
3815
3884
  if (errors && errors.length > 0) {
3816
- 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}`);
3817
3897
  }
3818
3898
  return null;
3819
3899
  }
@@ -3822,7 +3902,7 @@ async function withErrorHandling(operation, fn) {
3822
3902
  return await fn();
3823
3903
  } catch (error2) {
3824
3904
  const message = error2 instanceof Error ? error2.message : "Unknown error";
3825
- return errorResult(`Failed to ${operation}: ${message}`);
3905
+ return errorResult(buildHintedMessage(operation, message));
3826
3906
  }
3827
3907
  }
3828
3908
  function notFoundError(packageName, registry) {
@@ -3841,7 +3921,7 @@ var argsSchema = {
3841
3921
  function createComparePackagesTool(pkgseerService) {
3842
3922
  return {
3843
3923
  name: "compare_packages",
3844
- 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"}].',
3845
3925
  schema: argsSchema,
3846
3926
  handler: async ({ packages }, _extra) => {
3847
3927
  return withErrorHandling("compare packages", async () => {
@@ -3870,7 +3950,7 @@ var argsSchema2 = {
3870
3950
  function createFetchPackageDocTool(pkgseerService) {
3871
3951
  return {
3872
3952
  name: "fetch_package_doc",
3873
- description: "Fetches the full content of a specific documentation page using a globally unique page ID. Returns complete page metadata including title, content (markdown/HTML), content format, breadcrumbs, source attribution, link metadata, base URL, and last updated timestamp. 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).",
3874
3954
  schema: argsSchema2,
3875
3955
  handler: async ({ page_id }, _extra) => {
3876
3956
  return withErrorHandling("fetch documentation page", async () => {
@@ -3920,10 +4000,82 @@ var argsSchema4 = {
3920
4000
  include_transitive: z5.boolean().optional().describe("Whether to include transitive dependency DAG"),
3921
4001
  max_depth: z5.number().int().min(1).max(10).optional().describe("Maximum depth for transitive traversal (1-10)")
3922
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
+ }
3923
4075
  function createPackageDependenciesTool(pkgseerService) {
3924
4076
  return {
3925
4077
  name: "package_dependencies",
3926
- 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.",
3927
4079
  schema: argsSchema4,
3928
4080
  handler: async ({ registry, package_name, version: version2, include_transitive, max_depth }, _extra) => {
3929
4081
  return withErrorHandling("fetch package dependencies", async () => {
@@ -3934,7 +4086,29 @@ function createPackageDependenciesTool(pkgseerService) {
3934
4086
  if (!result.data.packageDependencies) {
3935
4087
  return notFoundError(package_name, registry);
3936
4088
  }
3937
- 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));
3938
4112
  });
3939
4113
  }
3940
4114
  };
@@ -4018,8 +4192,8 @@ import { z as z6 } from "zod";
4018
4192
  var argsSchema8 = {
4019
4193
  registry: schemas.registry,
4020
4194
  package_name: schemas.packageName.describe("Name of the package to search documentation for"),
4021
- keywords: z6.array(z6.string()).optional().describe("Keywords to search for; combined into a single query"),
4022
- 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).'),
4023
4197
  include_snippets: z6.boolean().optional().describe("Include content excerpts around matches"),
4024
4198
  limit: z6.number().int().min(1).max(100).optional().describe("Maximum number of results to return"),
4025
4199
  version: schemas.version
@@ -4027,25 +4201,28 @@ var argsSchema8 = {
4027
4201
  function createSearchPackageDocsTool(pkgseerService) {
4028
4202
  return {
4029
4203
  name: "search_package_docs",
4030
- 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.",
4031
4205
  schema: argsSchema8,
4032
4206
  handler: async ({
4033
4207
  registry,
4034
4208
  package_name,
4035
- keywords,
4036
- query,
4209
+ terms,
4210
+ match_mode,
4037
4211
  include_snippets,
4038
4212
  limit,
4039
4213
  version: version2
4040
4214
  }, _extra) => {
4041
4215
  return withErrorHandling("search package documentation", async () => {
4042
- if (!keywords?.length && !query) {
4043
- 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.");
4044
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;
4045
4222
  const result = await pkgseerService.searchPackageDocs(toGraphQLRegistry2(registry), package_name, {
4046
- keywords,
4047
- query,
4048
- includeSnippets: include_snippets,
4223
+ keywords: normalizedTerms,
4224
+ matchMode,
4225
+ includeSnippets,
4049
4226
  limit,
4050
4227
  version: version2
4051
4228
  });
@@ -4055,6 +4232,9 @@ function createSearchPackageDocsTool(pkgseerService) {
4055
4232
  if (!result.data.searchPackageDocs) {
4056
4233
  return errorResult(`No documentation found for ${package_name} in ${registry}`);
4057
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
+ }
4058
4238
  return textResult(JSON.stringify(result.data.searchPackageDocs, null, 2));
4059
4239
  });
4060
4240
  }
@@ -4064,8 +4244,8 @@ function createSearchPackageDocsTool(pkgseerService) {
4064
4244
  import { z as z7 } from "zod";
4065
4245
  var argsSchema9 = {
4066
4246
  project: z7.string().optional().describe("Project name to search. Optional if configured in pkgseer.yml; only needed to search a different project."),
4067
- keywords: z7.array(z7.string()).optional().describe("Keywords to search for; combined into a single query"),
4068
- 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).'),
4069
4249
  include_snippets: z7.boolean().optional().describe("Include content excerpts around matches"),
4070
4250
  limit: z7.number().int().min(1).max(100).optional().describe("Maximum number of results to return")
4071
4251
  };
@@ -4073,21 +4253,24 @@ function createSearchProjectDocsTool(deps) {
4073
4253
  const { pkgseerService, config } = deps;
4074
4254
  return {
4075
4255
  name: "search_project_docs",
4076
- 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.",
4077
4257
  schema: argsSchema9,
4078
- handler: async ({ project, keywords, query, include_snippets, limit }, _extra) => {
4258
+ handler: async ({ project, terms, match_mode, include_snippets, limit }, _extra) => {
4079
4259
  return withErrorHandling("search project documentation", async () => {
4080
4260
  const resolvedProject = project ?? config.project;
4081
4261
  if (!resolvedProject) {
4082
4262
  return errorResult("No project provided and none configured in pkgseer.yml. " + "Either pass project parameter or add project to your config.");
4083
4263
  }
4084
- if (!keywords?.length && !query) {
4085
- 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.");
4086
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;
4087
4270
  const result = await pkgseerService.searchProjectDocs(resolvedProject, {
4088
- keywords,
4089
- query,
4090
- includeSnippets: include_snippets,
4271
+ keywords: normalizedTerms,
4272
+ matchMode,
4273
+ includeSnippets,
4091
4274
  limit
4092
4275
  });
4093
4276
  const graphqlError = handleGraphQLErrors(result.errors);
@@ -4096,6 +4279,9 @@ function createSearchProjectDocsTool(deps) {
4096
4279
  if (!result.data.searchProjectDocs) {
4097
4280
  return errorResult(`Project not found: ${resolvedProject}`);
4098
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
+ }
4099
4285
  return textResult(JSON.stringify(result.data.searchProjectDocs, null, 2));
4100
4286
  });
4101
4287
  }
@@ -4313,7 +4499,7 @@ function registerPkgCompareCommand(program) {
4313
4499
  });
4314
4500
  }
4315
4501
  // src/commands/pkg/deps.ts
4316
- function formatPackageDependencies(data) {
4502
+ function formatPackageDependencies(data, transitiveRequested) {
4317
4503
  const lines = [];
4318
4504
  const pkg = data.package;
4319
4505
  const deps = data.dependencies;
@@ -4341,32 +4527,52 @@ function formatPackageDependencies(data) {
4341
4527
  } else {
4342
4528
  lines.push("No direct dependencies.");
4343
4529
  }
4530
+ if (!transitiveRequested) {
4531
+ lines.push("");
4532
+ lines.push("Tip: use --transitive for full dependency graph traversal.");
4533
+ }
4344
4534
  return lines.join(`
4345
4535
  `);
4346
4536
  }
4347
4537
  async function pkgDepsAction(packageName, options, deps) {
4348
4538
  const { pkgseerService } = deps;
4349
4539
  const registry = toGraphQLRegistry(options.registry);
4350
- 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);
4351
4542
  handleErrors(result.errors, options.json ?? false);
4352
4543
  if (!result.data.packageDependencies) {
4353
4544
  outputError(`Package not found: ${packageName} in ${options.registry}`, options.json ?? false);
4354
4545
  return;
4355
4546
  }
4356
- if (options.json) {
4547
+ const format = options.json ? "json" : options.format ?? "human";
4548
+ if (format === "json") {
4357
4549
  const data = result.data.packageDependencies;
4358
- const slim = {
4550
+ output({
4359
4551
  package: `${data.package?.name}@${data.package?.version}`,
4360
4552
  directCount: data.dependencies?.summary?.directCount ?? 0,
4361
- 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) => ({
4362
4556
  name: d.name,
4363
4557
  version: d.versionConstraint,
4364
4558
  type: d.type
4365
4559
  }))
4366
- };
4367
- 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
+ `));
4368
4574
  } else {
4369
- console.log(formatPackageDependencies(result.data.packageDependencies));
4575
+ console.log(formatPackageDependencies(result.data.packageDependencies, transitiveRequested));
4370
4576
  }
4371
4577
  }
4372
4578
  var DEPS_DESCRIPTION = `Get package dependencies.
@@ -4379,7 +4585,7 @@ Examples:
4379
4585
  pkgseer pkg deps lodash --transitive
4380
4586
  pkgseer pkg deps requests --registry pypi --json`;
4381
4587
  function registerPkgDepsCommand(program) {
4382
- 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) => {
4383
4589
  await withCliErrorHandling(options.json ?? false, async () => {
4384
4590
  const deps = await createContainer();
4385
4591
  await pkgDepsAction(packageName, options, deps);
@@ -4480,18 +4686,15 @@ function formatPackageQuality(data) {
4480
4686
  if (!quality) {
4481
4687
  return "No quality data available.";
4482
4688
  }
4483
- lines.push(`\uD83D\uDCCA Quality Score: ${formatScore(quality.overallScore)} (Grade: ${quality.grade})`);
4484
- lines.push("");
4485
- if (quality.categories && quality.categories.length > 0) {
4486
- lines.push("Category Breakdown:");
4487
- for (const category of quality.categories) {
4488
- if (category) {
4489
- const name = (category.category || "Unknown").padEnd(20);
4490
- lines.push(` ${name} ${formatScore(category.score)}`);
4491
- }
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)}`);
4492
4695
  }
4493
- lines.push("");
4494
4696
  }
4697
+ lines.push("Tip: use --json for full category breakdown.");
4495
4698
  return lines.join(`
4496
4699
  `);
4497
4700
  }
@@ -4504,7 +4707,8 @@ async function pkgQualityAction(packageName, options, deps) {
4504
4707
  outputError(`Package not found: ${packageName} in ${options.registry}`, options.json ?? false);
4505
4708
  return;
4506
4709
  }
4507
- if (options.json) {
4710
+ const format = options.json ? "json" : options.format ?? "human";
4711
+ if (format === "json") {
4508
4712
  const quality = result.data.packageQuality.quality;
4509
4713
  const slim = {
4510
4714
  package: `${result.data.packageQuality.package?.name}@${result.data.packageQuality.package?.version}`,
@@ -4533,7 +4737,7 @@ Examples:
4533
4737
  pkgseer pkg quality express -v 4.18.0
4534
4738
  pkgseer pkg quality requests --registry pypi --json`;
4535
4739
  function registerPkgQualityCommand(program) {
4536
- 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) => {
4537
4741
  await withCliErrorHandling(options.json ?? false, async () => {
4538
4742
  const deps = await createContainer();
4539
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-8dn0z2ja.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.3";
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.3",
4
+ "version": "0.1.4",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",