@pkgseer/cli 0.1.3 → 0.1.5

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-0fnprry7.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
  }
@@ -2525,22 +2586,13 @@ function getCodexConfigPaths(fs, scope) {
2525
2586
  function getClaudeCodeConfigPaths(fs, scope) {
2526
2587
  if (scope === "project") {
2527
2588
  const cwd = fs.getCwd();
2528
- const configPath2 = fs.joinPath(cwd, ".claude-code", "mcp.json");
2529
- const backupPath2 = fs.joinPath(cwd, ".claude-code", "mcp.json.bak");
2589
+ const configPath2 = fs.joinPath(cwd, ".mcp.json");
2590
+ const backupPath2 = fs.joinPath(cwd, ".mcp.json.bak");
2530
2591
  return { configPath: configPath2, backupPath: backupPath2 };
2531
2592
  }
2532
- const platform = process.platform;
2533
- let configPath;
2534
- let backupPath;
2535
- if (platform === "win32") {
2536
- const appData = process.env.APPDATA || fs.joinPath(fs.getHomeDir(), "AppData", "Roaming");
2537
- configPath = fs.joinPath(appData, "Claude Code", "mcp.json");
2538
- backupPath = fs.joinPath(appData, "Claude Code", "mcp.json.bak");
2539
- } else {
2540
- const home = fs.getHomeDir();
2541
- configPath = fs.joinPath(home, ".claude-code", "mcp.json");
2542
- backupPath = fs.joinPath(home, ".claude-code", "mcp.json.bak");
2543
- }
2593
+ const home = fs.getHomeDir();
2594
+ const configPath = fs.joinPath(home, ".claude.json");
2595
+ const backupPath = fs.joinPath(home, ".claude.json.bak");
2544
2596
  return { configPath, backupPath };
2545
2597
  }
2546
2598
  async function parseConfigFile(fs, path) {
@@ -2624,8 +2676,8 @@ args = ["-y", "@pkgseer/cli", "mcp", "start"]`;
2624
2676
  };
2625
2677
  console.log(JSON.stringify(configExample, null, 2));
2626
2678
  }
2627
- if ((tool === "cursor" || tool === "codex" || tool === "claude-code") && scope === "project") {
2628
- const dirName = tool === "cursor" ? ".cursor" : tool === "codex" ? ".codex" : ".claude-code";
2679
+ if ((tool === "cursor" || tool === "codex") && scope === "project") {
2680
+ const dirName = tool === "cursor" ? ".cursor" : ".codex";
2629
2681
  console.log(dim(`
2630
2682
  Note: Create the ${dirName} directory if it doesn't exist.`, useColors));
2631
2683
  }
@@ -2682,8 +2734,8 @@ Run ${highlight("pkgseer project init", useColors)} first, then ${highlight("pkg
2682
2734
  }
2683
2735
  let scope;
2684
2736
  if (tool === "cursor" || tool === "codex" || tool === "claude-code") {
2685
- const projectPath = tool === "cursor" ? ".cursor/mcp.json" : tool === "codex" ? ".codex/config.toml" : ".claude-code/mcp.json";
2686
- const globalPath = tool === "cursor" ? "~/.cursor/mcp.json" : tool === "codex" ? "~/.codex/config.toml" : "~/.claude-code/mcp.json";
2737
+ const projectPath = tool === "cursor" ? ".cursor/mcp.json" : tool === "codex" ? ".codex/config.toml" : ".mcp.json";
2738
+ const globalPath = tool === "cursor" ? "~/.cursor/mcp.json" : tool === "codex" ? "~/.codex/config.toml" : "~/.claude.json";
2687
2739
  if (hasProject) {
2688
2740
  scope = await promptService.select("Where should the MCP config be created?", [
2689
2741
  {
@@ -3811,9 +3863,28 @@ var schemas = {
3811
3863
  packageName: z2.string().max(255).describe("Name of the package"),
3812
3864
  version: z2.string().max(100).optional().describe("Specific version (defaults to latest)")
3813
3865
  };
3866
+ function buildHintedMessage(operation, message) {
3867
+ const lower = message.toLowerCase();
3868
+ const isTimeout = lower.includes("-32001") || lower.includes("timeout") || lower.includes("timed out");
3869
+ if (isTimeout) {
3870
+ return `Failed to ${operation}: ${message}. ` + "Hint: try lowering the limit or narrowing the scope, then retry.";
3871
+ }
3872
+ return `Failed to ${operation}: ${message}`;
3873
+ }
3814
3874
  function handleGraphQLErrors(errors) {
3815
3875
  if (errors && errors.length > 0) {
3816
- return errorResult(`Error: ${errors.map((e) => e.message).join(", ")}`);
3876
+ const messages = errors.map((e) => e.message).filter(Boolean);
3877
+ const combined = messages.join(", ");
3878
+ const lower = combined.toLowerCase();
3879
+ const hasNotFound = lower.includes("not found") || lower.includes("does not exist");
3880
+ const hasTimeout = lower.includes("-32001") || lower.includes("timeout") || lower.includes("timed out");
3881
+ if (hasNotFound) {
3882
+ return errorResult(`Error: ${combined}. Hint: verify the name/registry and that the resource exists.`);
3883
+ }
3884
+ if (hasTimeout) {
3885
+ return errorResult(`Error: ${combined}. Hint: try lowering the limit or narrowing the scope, then retry.`);
3886
+ }
3887
+ return errorResult(`Error: ${combined}`);
3817
3888
  }
3818
3889
  return null;
3819
3890
  }
@@ -3822,7 +3893,7 @@ async function withErrorHandling(operation, fn) {
3822
3893
  return await fn();
3823
3894
  } catch (error2) {
3824
3895
  const message = error2 instanceof Error ? error2.message : "Unknown error";
3825
- return errorResult(`Failed to ${operation}: ${message}`);
3896
+ return errorResult(buildHintedMessage(operation, message));
3826
3897
  }
3827
3898
  }
3828
3899
  function notFoundError(packageName, registry) {
@@ -3841,7 +3912,7 @@ var argsSchema = {
3841
3912
  function createComparePackagesTool(pkgseerService) {
3842
3913
  return {
3843
3914
  name: "compare_packages",
3844
- description: "Compares multiple packages across metadata, quality, and security dimensions",
3915
+ description: 'Compares 2-10 packages across metadata, quality, and security. Example: [{"registry":"npm","name":"react","version":"18.2.0"},{"registry":"pypi","name":"requests"}].',
3845
3916
  schema: argsSchema,
3846
3917
  handler: async ({ packages }, _extra) => {
3847
3918
  return withErrorHandling("compare packages", async () => {
@@ -3870,7 +3941,7 @@ var argsSchema2 = {
3870
3941
  function createFetchPackageDocTool(pkgseerService) {
3871
3942
  return {
3872
3943
  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.",
3944
+ 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
3945
  schema: argsSchema2,
3875
3946
  handler: async ({ page_id }, _extra) => {
3876
3947
  return withErrorHandling("fetch documentation page", async () => {
@@ -3920,10 +3991,82 @@ var argsSchema4 = {
3920
3991
  include_transitive: z5.boolean().optional().describe("Whether to include transitive dependency DAG"),
3921
3992
  max_depth: z5.number().int().min(1).max(10).optional().describe("Maximum depth for transitive traversal (1-10)")
3922
3993
  };
3994
+ function decodeDag(rawDag) {
3995
+ if (!rawDag || typeof rawDag !== "object")
3996
+ return;
3997
+ const dag = rawDag;
3998
+ const nodesSource = dag.n ?? dag.nodes;
3999
+ const nodesRaw = nodesSource && typeof nodesSource === "object" && !Array.isArray(nodesSource) ? nodesSource : undefined;
4000
+ const edgesSource = dag.e ?? dag.edges;
4001
+ const edgesRaw = Array.isArray(edgesSource) ? edgesSource : undefined;
4002
+ const nodes = nodesRaw ? Object.entries(nodesRaw).map(([id, value]) => {
4003
+ const {
4004
+ n: nameField,
4005
+ v: versionField,
4006
+ l: labelField,
4007
+ ...rest
4008
+ } = value ?? {};
4009
+ return {
4010
+ id,
4011
+ name: nameField ?? rest.name,
4012
+ version: versionField ?? rest.version,
4013
+ label: labelField ?? rest.label,
4014
+ ...rest
4015
+ };
4016
+ }) : [];
4017
+ const edges = edgesRaw?.map((edge) => {
4018
+ if (Array.isArray(edge) && edge.length >= 2) {
4019
+ return { from: String(edge[0]), to: String(edge[1]) };
4020
+ }
4021
+ const edgeObj = edge ?? {};
4022
+ return {
4023
+ from: String(edgeObj.f ?? edgeObj.from ?? edgeObj.source ?? edgeObj.s ?? ""),
4024
+ to: String(edgeObj.t ?? edgeObj.to ?? edgeObj.target ?? edgeObj.d ?? "")
4025
+ };
4026
+ }) ?? [];
4027
+ return {
4028
+ version: dag.v ?? dag.version,
4029
+ nodes,
4030
+ edges
4031
+ };
4032
+ }
4033
+ function buildEdgeDepths(decodedDag, rootId) {
4034
+ if (!decodedDag)
4035
+ return;
4036
+ const root = rootId ?? decodedDag.nodes[0]?.id;
4037
+ if (!root)
4038
+ return;
4039
+ const depthMap = new Map([[root, 0]]);
4040
+ const adjacency = new Map;
4041
+ for (const edge of decodedDag.edges) {
4042
+ if (!edge.from || !edge.to)
4043
+ continue;
4044
+ const list = adjacency.get(edge.from) ?? [];
4045
+ list.push(edge.to);
4046
+ adjacency.set(edge.from, list);
4047
+ }
4048
+ const queue = [root];
4049
+ while (queue.length > 0) {
4050
+ const current = queue.shift();
4051
+ const currentDepth = depthMap.get(current) ?? 0;
4052
+ for (const next of adjacency.get(current) ?? []) {
4053
+ if (!depthMap.has(next)) {
4054
+ depthMap.set(next, currentDepth + 1);
4055
+ queue.push(next);
4056
+ }
4057
+ }
4058
+ }
4059
+ return decodedDag.edges.map((edge) => ({
4060
+ from: edge.from,
4061
+ to: edge.to,
4062
+ depthFrom: depthMap.get(edge.from),
4063
+ depthTo: depthMap.get(edge.to)
4064
+ }));
4065
+ }
3923
4066
  function createPackageDependenciesTool(pkgseerService) {
3924
4067
  return {
3925
4068
  name: "package_dependencies",
3926
- description: "Retrieves direct and transitive dependencies for a package version",
4069
+ 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
4070
  schema: argsSchema4,
3928
4071
  handler: async ({ registry, package_name, version: version2, include_transitive, max_depth }, _extra) => {
3929
4072
  return withErrorHandling("fetch package dependencies", async () => {
@@ -3934,7 +4077,29 @@ function createPackageDependenciesTool(pkgseerService) {
3934
4077
  if (!result.data.packageDependencies) {
3935
4078
  return notFoundError(package_name, registry);
3936
4079
  }
3937
- return textResult(JSON.stringify(result.data.packageDependencies, null, 2));
4080
+ const deps = result.data.packageDependencies.dependencies;
4081
+ const decodedDag = decodeDag(deps?.transitive?.dag);
4082
+ const transitiveDag = deps?.transitive?.dag;
4083
+ const edgesWithDepth = buildEdgeDepths(decodedDag, typeof transitiveDag?.root === "string" ? transitiveDag.root : undefined);
4084
+ const output2 = {
4085
+ summary: deps?.summary,
4086
+ package: result.data.packageDependencies.package,
4087
+ direct: deps?.direct,
4088
+ transitive: deps?.transitive ? {
4089
+ totalEdges: deps.transitive.totalEdges,
4090
+ uniquePackagesCount: deps.transitive.uniquePackagesCount,
4091
+ uniqueDependencies: deps.transitive.uniqueDependencies,
4092
+ conflicts: deps.transitive.conflicts,
4093
+ circularDependencies: deps.transitive.circularDependencies,
4094
+ dag: {
4095
+ decoded: decodedDag,
4096
+ edgesWithDepth,
4097
+ raw: deps.transitive.dag
4098
+ }
4099
+ } : undefined,
4100
+ raw: result.data.packageDependencies
4101
+ };
4102
+ return textResult(JSON.stringify(output2, null, 2));
3938
4103
  });
3939
4104
  }
3940
4105
  };
@@ -4018,8 +4183,8 @@ import { z as z6 } from "zod";
4018
4183
  var argsSchema8 = {
4019
4184
  registry: schemas.registry,
4020
4185
  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)"),
4186
+ terms: z6.array(z6.string()).optional().describe("Search terms to match. Provide a few key words or phrases that should appear in results."),
4187
+ match_mode: z6.enum(["any", "or", "all", "and"]).optional().describe('How to combine terms: "any" (OR) or "all" (AND).'),
4023
4188
  include_snippets: z6.boolean().optional().describe("Include content excerpts around matches"),
4024
4189
  limit: z6.number().int().min(1).max(100).optional().describe("Maximum number of results to return"),
4025
4190
  version: schemas.version
@@ -4027,25 +4192,28 @@ var argsSchema8 = {
4027
4192
  function createSearchPackageDocsTool(pkgseerService) {
4028
4193
  return {
4029
4194
  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.",
4195
+ description: "Searches package documentation using search terms and match modes (any/all). Returns ranked results with match context. Defaults to include_snippets=true.",
4031
4196
  schema: argsSchema8,
4032
4197
  handler: async ({
4033
4198
  registry,
4034
4199
  package_name,
4035
- keywords,
4036
- query,
4200
+ terms,
4201
+ match_mode,
4037
4202
  include_snippets,
4038
4203
  limit,
4039
4204
  version: version2
4040
4205
  }, _extra) => {
4041
4206
  return withErrorHandling("search package documentation", async () => {
4042
- if (!keywords?.length && !query) {
4043
- return errorResult("Either keywords or query must be provided for search");
4207
+ const normalizedTerms = terms?.map((term) => term.trim()).filter((term) => term.length > 0) ?? [];
4208
+ if (normalizedTerms.length === 0) {
4209
+ return errorResult("Search terms are required to run documentation search.");
4044
4210
  }
4211
+ const matchMode = match_mode === "all" || match_mode === "and" ? "AND" : match_mode === "any" || match_mode === "or" ? "OR" : undefined;
4212
+ const includeSnippets = include_snippets ?? true;
4045
4213
  const result = await pkgseerService.searchPackageDocs(toGraphQLRegistry2(registry), package_name, {
4046
- keywords,
4047
- query,
4048
- includeSnippets: include_snippets,
4214
+ keywords: normalizedTerms,
4215
+ matchMode,
4216
+ includeSnippets,
4049
4217
  limit,
4050
4218
  version: version2
4051
4219
  });
@@ -4055,6 +4223,9 @@ function createSearchPackageDocsTool(pkgseerService) {
4055
4223
  if (!result.data.searchPackageDocs) {
4056
4224
  return errorResult(`No documentation found for ${package_name} in ${registry}`);
4057
4225
  }
4226
+ if ((result.data.searchPackageDocs.entries ?? []).length === 0 && normalizedTerms.length > 0) {
4227
+ return errorResult(`No documentation matched: ${normalizedTerms.join(", ")}. ` + "Try fewer or broader terms, or reduce match constraints.");
4228
+ }
4058
4229
  return textResult(JSON.stringify(result.data.searchPackageDocs, null, 2));
4059
4230
  });
4060
4231
  }
@@ -4064,8 +4235,8 @@ function createSearchPackageDocsTool(pkgseerService) {
4064
4235
  import { z as z7 } from "zod";
4065
4236
  var argsSchema9 = {
4066
4237
  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)"),
4238
+ terms: z7.array(z7.string()).optional().describe("Search terms to match. Provide a few key words or phrases that should appear in results."),
4239
+ match_mode: z7.enum(["any", "or", "all", "and"]).optional().describe('How to combine terms: "any" (OR) or "all" (AND).'),
4069
4240
  include_snippets: z7.boolean().optional().describe("Include content excerpts around matches"),
4070
4241
  limit: z7.number().int().min(1).max(100).optional().describe("Maximum number of results to return")
4071
4242
  };
@@ -4073,21 +4244,24 @@ function createSearchProjectDocsTool(deps) {
4073
4244
  const { pkgseerService, config } = deps;
4074
4245
  return {
4075
4246
  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.",
4247
+ 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
4248
  schema: argsSchema9,
4078
- handler: async ({ project, keywords, query, include_snippets, limit }, _extra) => {
4249
+ handler: async ({ project, terms, match_mode, include_snippets, limit }, _extra) => {
4079
4250
  return withErrorHandling("search project documentation", async () => {
4080
4251
  const resolvedProject = project ?? config.project;
4081
4252
  if (!resolvedProject) {
4082
4253
  return errorResult("No project provided and none configured in pkgseer.yml. " + "Either pass project parameter or add project to your config.");
4083
4254
  }
4084
- if (!keywords?.length && !query) {
4085
- return errorResult("Either keywords or query must be provided for search");
4255
+ const normalizedTerms = terms?.map((term) => term.trim()).filter((term) => term.length > 0) ?? [];
4256
+ if (normalizedTerms.length === 0) {
4257
+ return errorResult("Search terms are required to run documentation search.");
4086
4258
  }
4259
+ const matchMode = match_mode === "all" || match_mode === "and" ? "AND" : match_mode === "any" || match_mode === "or" ? "OR" : undefined;
4260
+ const includeSnippets = include_snippets ?? true;
4087
4261
  const result = await pkgseerService.searchProjectDocs(resolvedProject, {
4088
- keywords,
4089
- query,
4090
- includeSnippets: include_snippets,
4262
+ keywords: normalizedTerms,
4263
+ matchMode,
4264
+ includeSnippets,
4091
4265
  limit
4092
4266
  });
4093
4267
  const graphqlError = handleGraphQLErrors(result.errors);
@@ -4096,6 +4270,9 @@ function createSearchProjectDocsTool(deps) {
4096
4270
  if (!result.data.searchProjectDocs) {
4097
4271
  return errorResult(`Project not found: ${resolvedProject}`);
4098
4272
  }
4273
+ if ((result.data.searchProjectDocs.entries ?? []).length === 0 && normalizedTerms.length > 0) {
4274
+ return errorResult(`No documentation matched: ${normalizedTerms.join(", ")}. ` + "Try fewer or broader terms, or reduce match constraints.");
4275
+ }
4099
4276
  return textResult(JSON.stringify(result.data.searchProjectDocs, null, 2));
4100
4277
  });
4101
4278
  }
@@ -4313,7 +4490,7 @@ function registerPkgCompareCommand(program) {
4313
4490
  });
4314
4491
  }
4315
4492
  // src/commands/pkg/deps.ts
4316
- function formatPackageDependencies(data) {
4493
+ function formatPackageDependencies(data, transitiveRequested) {
4317
4494
  const lines = [];
4318
4495
  const pkg = data.package;
4319
4496
  const deps = data.dependencies;
@@ -4341,32 +4518,52 @@ function formatPackageDependencies(data) {
4341
4518
  } else {
4342
4519
  lines.push("No direct dependencies.");
4343
4520
  }
4521
+ if (!transitiveRequested) {
4522
+ lines.push("");
4523
+ lines.push("Tip: use --transitive for full dependency graph traversal.");
4524
+ }
4344
4525
  return lines.join(`
4345
4526
  `);
4346
4527
  }
4347
4528
  async function pkgDepsAction(packageName, options, deps) {
4348
4529
  const { pkgseerService } = deps;
4349
4530
  const registry = toGraphQLRegistry(options.registry);
4350
- const result = await pkgseerService.cliPackageDeps(registry, packageName, options.pkgVersion, options.transitive);
4531
+ const transitiveRequested = options.transitive ?? false;
4532
+ const result = await pkgseerService.cliPackageDeps(registry, packageName, options.pkgVersion, options.transitive, options.maxDepth ? Number.parseInt(options.maxDepth, 10) : undefined);
4351
4533
  handleErrors(result.errors, options.json ?? false);
4352
4534
  if (!result.data.packageDependencies) {
4353
4535
  outputError(`Package not found: ${packageName} in ${options.registry}`, options.json ?? false);
4354
4536
  return;
4355
4537
  }
4356
- if (options.json) {
4538
+ const format = options.json ? "json" : options.format ?? "human";
4539
+ if (format === "json") {
4357
4540
  const data = result.data.packageDependencies;
4358
- const slim = {
4541
+ output({
4359
4542
  package: `${data.package?.name}@${data.package?.version}`,
4360
4543
  directCount: data.dependencies?.summary?.directCount ?? 0,
4361
- dependencies: data.dependencies?.direct?.filter((d) => d).map((d) => ({
4544
+ uniquePackagesCount: data.dependencies?.summary?.uniquePackagesCount ?? 0,
4545
+ transitiveIncluded: options.transitive ?? false,
4546
+ dependencies: data.dependencies?.direct?.filter((d) => Boolean(d)).map((d) => ({
4362
4547
  name: d.name,
4363
4548
  version: d.versionConstraint,
4364
4549
  type: d.type
4365
4550
  }))
4366
- };
4367
- output(slim, true);
4551
+ }, true);
4552
+ } else if (format === "summary") {
4553
+ const data = result.data.packageDependencies;
4554
+ const deps2 = data.dependencies;
4555
+ const directCount = deps2?.summary?.directCount ?? 0;
4556
+ const uniquePackagesCount = deps2?.summary?.uniquePackagesCount ?? 0;
4557
+ const lines = [
4558
+ `Package: ${data.package?.name ?? ""}@${data.package?.version ?? ""}`,
4559
+ `Direct deps: ${directCount}`,
4560
+ `Unique packages: ${uniquePackagesCount || "N/A"}`,
4561
+ transitiveRequested ? "Transitive: included" : "Transitive: not included (use --transitive)"
4562
+ ];
4563
+ console.log(lines.join(`
4564
+ `));
4368
4565
  } else {
4369
- console.log(formatPackageDependencies(result.data.packageDependencies));
4566
+ console.log(formatPackageDependencies(result.data.packageDependencies, transitiveRequested));
4370
4567
  }
4371
4568
  }
4372
4569
  var DEPS_DESCRIPTION = `Get package dependencies.
@@ -4379,7 +4576,7 @@ Examples:
4379
4576
  pkgseer pkg deps lodash --transitive
4380
4577
  pkgseer pkg deps requests --registry pypi --json`;
4381
4578
  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) => {
4579
+ 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
4580
  await withCliErrorHandling(options.json ?? false, async () => {
4384
4581
  const deps = await createContainer();
4385
4582
  await pkgDepsAction(packageName, options, deps);
@@ -4480,18 +4677,15 @@ function formatPackageQuality(data) {
4480
4677
  if (!quality) {
4481
4678
  return "No quality data available.";
4482
4679
  }
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
- }
4680
+ const topCategories = (quality.categories ?? []).filter((c) => Boolean(c)).sort((a, b) => (b?.score ?? 0) - (a?.score ?? 0)).slice(0, 3);
4681
+ lines.push(`\uD83D\uDCCA Quality: Grade ${quality.grade ?? "N/A"} (${formatScore(quality.overallScore)})`);
4682
+ if (topCategories.length > 0) {
4683
+ lines.push("Top drivers:");
4684
+ for (const category of topCategories) {
4685
+ lines.push(` - ${(category.category ?? "Unknown").toLowerCase()}: ${formatScore(category.score)}`);
4492
4686
  }
4493
- lines.push("");
4494
4687
  }
4688
+ lines.push("Tip: use --json for full category breakdown.");
4495
4689
  return lines.join(`
4496
4690
  `);
4497
4691
  }
@@ -4504,7 +4698,8 @@ async function pkgQualityAction(packageName, options, deps) {
4504
4698
  outputError(`Package not found: ${packageName} in ${options.registry}`, options.json ?? false);
4505
4699
  return;
4506
4700
  }
4507
- if (options.json) {
4701
+ const format = options.json ? "json" : options.format ?? "human";
4702
+ if (format === "json") {
4508
4703
  const quality = result.data.packageQuality.quality;
4509
4704
  const slim = {
4510
4705
  package: `${result.data.packageQuality.package?.name}@${result.data.packageQuality.package?.version}`,
@@ -4533,7 +4728,7 @@ Examples:
4533
4728
  pkgseer pkg quality express -v 4.18.0
4534
4729
  pkgseer pkg quality requests --registry pypi --json`;
4535
4730
  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) => {
4731
+ 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
4732
  await withCliErrorHandling(options.json ?? false, async () => {
4538
4733
  const deps = await createContainer();
4539
4734
  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-0fnprry7.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.5";
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.5",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",