@pkgseer/cli 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -96,8 +96,20 @@ pkgseer search "json parsing" -P hex:jason,hex:poison
96
96
  # Search modes
97
97
  pkgseer search "auth" -P lodash --mode code # Code only
98
98
  pkgseer search "auth" -P lodash --mode docs # Docs only
99
+
100
+ # Wait options (for packages that need indexing)
101
+ pkgseer search "api" -P new-package --wait 60000 # Wait up to 60s
102
+ pkgseer search "api" -P new-package --no-wait # Return immediately
103
+
104
+ # Resume long-running searches
105
+ pkgseer search "api" -P large-package --no-poll # Disable polling, get searchRef
106
+ pkgseer search --resume <searchRef> # Check status / get results
99
107
  ```
100
108
 
109
+ If packages haven't been indexed yet, the search will wait up to 30 seconds by default. Use `--wait <ms>` to customize or `--no-wait` to return immediately with progress info.
110
+
111
+ For long-running searches, use `--no-poll` to get a searchRef and later resume with `--resume <ref>`.
112
+
101
113
  ### Package Commands
102
114
 
103
115
  ```bash
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-drz16bhv.js";
4
+ } from "./shared/chunk-55x4vqp2.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
@@ -211,8 +211,70 @@ var CliDocsListDocument = gql`
211
211
  }
212
212
  `;
213
213
  var CombinedSearchDocument = gql`
214
- query CombinedSearch($packages: [SearchPackageInput!]!, $query: String!, $mode: SearchMode, $limit: Int) {
215
- combinedSearch(packages: $packages, query: $query, mode: $mode, limit: $limit) {
214
+ query CombinedSearch($packages: [SearchPackageInput!]!, $query: String!, $mode: SearchMode, $limit: Int, $waitTimeoutMs: Int) {
215
+ combinedSearch(
216
+ packages: $packages
217
+ query: $query
218
+ mode: $mode
219
+ limit: $limit
220
+ waitTimeoutMs: $waitTimeoutMs
221
+ ) {
222
+ completed
223
+ searchRef
224
+ result {
225
+ query
226
+ mode
227
+ totalResults
228
+ entries {
229
+ id
230
+ type
231
+ title
232
+ subtitle
233
+ packageName
234
+ registry
235
+ score
236
+ snippet
237
+ filePath
238
+ startLine
239
+ endLine
240
+ language
241
+ chunkType
242
+ repoUrl
243
+ gitRef
244
+ pageId
245
+ sourceUrl
246
+ }
247
+ indexingStatus {
248
+ registry
249
+ packageName
250
+ version
251
+ codeStatus
252
+ docsStatus
253
+ }
254
+ }
255
+ progress {
256
+ status
257
+ packagesTotal
258
+ packagesReady
259
+ elapsedMs
260
+ }
261
+ }
262
+ }
263
+ `;
264
+ var SearchProgressDocument = gql`
265
+ query SearchProgress($searchRef: String!) {
266
+ searchProgress(searchRef: $searchRef) {
267
+ searchRef
268
+ status
269
+ packagesTotal
270
+ packagesReady
271
+ elapsedMs
272
+ }
273
+ }
274
+ `;
275
+ var SearchResultsDocument = gql`
276
+ query SearchResults($searchRef: String!) {
277
+ searchResults(searchRef: $searchRef) {
216
278
  query
217
279
  mode
218
280
  totalResults
@@ -613,6 +675,8 @@ var CliDocsSearchDocumentString = print(CliDocsSearchDocument);
613
675
  var CliProjectDocsSearchDocumentString = print(CliProjectDocsSearchDocument);
614
676
  var CliDocsListDocumentString = print(CliDocsListDocument);
615
677
  var CombinedSearchDocumentString = print(CombinedSearchDocument);
678
+ var SearchProgressDocumentString = print(SearchProgressDocument);
679
+ var SearchResultsDocumentString = print(SearchResultsDocument);
616
680
  var FetchCodeContextDocumentString = print(FetchCodeContextDocument);
617
681
  var CliDocsGetDocumentString = print(CliDocsGetDocument);
618
682
  var CreateProjectDocumentString = print(CreateProjectDocument);
@@ -655,6 +719,12 @@ function getSdk(client, withWrapper = defaultWrapper) {
655
719
  CombinedSearch(variables, requestHeaders) {
656
720
  return withWrapper((wrappedRequestHeaders) => client.rawRequest(CombinedSearchDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "CombinedSearch", "query", variables);
657
721
  },
722
+ SearchProgress(variables, requestHeaders) {
723
+ return withWrapper((wrappedRequestHeaders) => client.rawRequest(SearchProgressDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "SearchProgress", "query", variables);
724
+ },
725
+ SearchResults(variables, requestHeaders) {
726
+ return withWrapper((wrappedRequestHeaders) => client.rawRequest(SearchResultsDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "SearchResults", "query", variables);
727
+ },
658
728
  FetchCodeContext(variables, requestHeaders) {
659
729
  return withWrapper((wrappedRequestHeaders) => client.rawRequest(FetchCodeContextDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "FetchCodeContext", "query", variables);
660
730
  },
@@ -1021,6 +1091,7 @@ var TOOL_NAMES = [
1021
1091
  "list_package_docs",
1022
1092
  "fetch_package_doc",
1023
1093
  "search",
1094
+ "search_status",
1024
1095
  "fetch_code_context",
1025
1096
  "search_project_docs"
1026
1097
  ];
@@ -1419,10 +1490,19 @@ class PkgseerServiceImpl {
1419
1490
  packages,
1420
1491
  query,
1421
1492
  mode: options?.mode,
1422
- limit: options?.limit
1493
+ limit: options?.limit,
1494
+ waitTimeoutMs: options?.waitTimeoutMs
1423
1495
  });
1424
1496
  return { data: result.data, errors: result.errors };
1425
1497
  }
1498
+ async getSearchProgress(searchRef) {
1499
+ const result = await this.client.SearchProgress({ searchRef });
1500
+ return { data: result.data, errors: result.errors };
1501
+ }
1502
+ async getSearchResults(searchRef) {
1503
+ const result = await this.client.SearchResults({ searchRef });
1504
+ return { data: result.data, errors: result.errors };
1505
+ }
1426
1506
  async fetchCodeContext(repoUrl, gitRef, filePath, options) {
1427
1507
  const result = await this.client.FetchCodeContext({
1428
1508
  repoUrl,
@@ -2341,7 +2421,59 @@ function registerDocsListCommand(program) {
2341
2421
  });
2342
2422
  });
2343
2423
  }
2424
+ // src/lib/polling.ts
2425
+ var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2426
+ async function pollUntilDone(config) {
2427
+ const {
2428
+ fetch: fetch2,
2429
+ isDone,
2430
+ onUpdate,
2431
+ intervalMs,
2432
+ maxWaitMs,
2433
+ sleep = defaultSleep,
2434
+ now = Date.now
2435
+ } = config;
2436
+ const startTime = now();
2437
+ let lastData;
2438
+ while (true) {
2439
+ try {
2440
+ const data = await fetch2();
2441
+ lastData = data;
2442
+ if (onUpdate) {
2443
+ onUpdate(data);
2444
+ }
2445
+ if (isDone(data)) {
2446
+ return { success: true, data };
2447
+ }
2448
+ const elapsed = now() - startTime;
2449
+ if (elapsed >= maxWaitMs) {
2450
+ return {
2451
+ success: false,
2452
+ error: `Client polling timeout after ${elapsed}ms`,
2453
+ lastData
2454
+ };
2455
+ }
2456
+ await sleep(intervalMs);
2457
+ } catch (error) {
2458
+ const message = error instanceof Error ? error.message : String(error);
2459
+ return {
2460
+ success: false,
2461
+ error: `Poll failed: ${message}`,
2462
+ lastData
2463
+ };
2464
+ }
2465
+ }
2466
+ }
2467
+
2344
2468
  // src/commands/search.ts
2469
+ var DEFAULT_WAIT_TIMEOUT_MS = 30000;
2470
+ var DEFAULT_POLL_INTERVAL_MS = 2000;
2471
+ var DEFAULT_MAX_POLL_TIME_MS = 120000;
2472
+ var TERMINAL_STATUSES = [
2473
+ "COMPLETED",
2474
+ "TIMEOUT",
2475
+ "FAILED"
2476
+ ];
2345
2477
  var colors = {
2346
2478
  reset: "\x1B[0m",
2347
2479
  bold: "\x1B[1m",
@@ -2562,6 +2694,36 @@ function truncate(text, maxLen) {
2562
2694
  return text;
2563
2695
  return `${text.slice(0, maxLen - 3)}...`;
2564
2696
  }
2697
+ function isSubstantiveLine(line) {
2698
+ const trimmed = line.trim();
2699
+ if (trimmed.length === 0)
2700
+ return false;
2701
+ if (/^[(){}[\];,]+$/.test(trimmed))
2702
+ return false;
2703
+ if (trimmed.length <= 3 && /^[^a-zA-Z0-9]*$/.test(trimmed))
2704
+ return false;
2705
+ return true;
2706
+ }
2707
+ function findBestSnippetLine(snippet) {
2708
+ const lines = snippet.split(`
2709
+ `).map((l) => l.trim());
2710
+ if (lines.length === 0)
2711
+ return null;
2712
+ const middleIndex = Math.floor((lines.length - 1) / 2);
2713
+ for (let offset = 0;offset <= lines.length; offset++) {
2714
+ const beforeIdx = middleIndex - offset;
2715
+ if (beforeIdx >= 0 && isSubstantiveLine(lines[beforeIdx])) {
2716
+ return lines[beforeIdx];
2717
+ }
2718
+ if (offset > 0) {
2719
+ const afterIdx = middleIndex + offset;
2720
+ if (afterIdx < lines.length && isSubstantiveLine(lines[afterIdx])) {
2721
+ return lines[afterIdx];
2722
+ }
2723
+ }
2724
+ }
2725
+ return null;
2726
+ }
2565
2727
  function formatEntryCompact(entry, useColors) {
2566
2728
  if (!entry)
2567
2729
  return "";
@@ -2579,12 +2741,11 @@ function formatEntryCompact(entry, useColors) {
2579
2741
  }
2580
2742
  let snippet = "";
2581
2743
  if (entry.snippet) {
2582
- const firstLine = entry.snippet.split(`
2583
- `).map((l) => l.trim()).find((l) => l.length > 0);
2584
- if (firstLine) {
2585
- snippet = ` ${truncate(firstLine, 80)}`;
2744
+ const bestLine = findBestSnippetLine(entry.snippet);
2745
+ if (bestLine) {
2746
+ snippet = ` ${truncate(bestLine, 80)}`;
2586
2747
  if (useColors) {
2587
- snippet = ` ${colors.dim}${truncate(firstLine, 80)}${colors.reset}`;
2748
+ snippet = ` ${colors.dim}${truncate(bestLine, 80)}${colors.reset}`;
2588
2749
  }
2589
2750
  }
2590
2751
  }
@@ -2662,8 +2823,166 @@ function summarizeSearchResults(results) {
2662
2823
  return lines.join(`
2663
2824
  `);
2664
2825
  }
2826
+ function formatSearchProgress(progress, searchRef, useColors) {
2827
+ const lines = [];
2828
+ if (useColors) {
2829
+ lines.push(`${colors.yellow}Search in progress...${colors.reset}`);
2830
+ } else {
2831
+ lines.push("Search in progress...");
2832
+ }
2833
+ if (progress) {
2834
+ const statusText = progress.status?.toLowerCase() ?? "unknown";
2835
+ lines.push(`Status: ${statusText}`);
2836
+ if (progress.packagesTotal != null && progress.packagesReady != null) {
2837
+ lines.push(`Packages: ${progress.packagesReady}/${progress.packagesTotal} ready`);
2838
+ }
2839
+ if (progress.elapsedMs != null) {
2840
+ lines.push(`Elapsed: ${(progress.elapsedMs / 1000).toFixed(1)}s`);
2841
+ }
2842
+ }
2843
+ lines.push("");
2844
+ lines.push("Some packages may still be indexing.");
2845
+ lines.push("Run again in a moment for complete results.");
2846
+ if (searchRef) {
2847
+ lines.push("");
2848
+ if (useColors) {
2849
+ lines.push(`${colors.dim}Search ref: ${searchRef}${colors.reset}`);
2850
+ } else {
2851
+ lines.push(`Search ref: ${searchRef}`);
2852
+ }
2853
+ }
2854
+ return lines.join(`
2855
+ `);
2856
+ }
2857
+ function getWaitTimeoutMs(options) {
2858
+ if (options.noWait) {
2859
+ return 0;
2860
+ }
2861
+ if (options.wait) {
2862
+ const parsed = Number.parseInt(options.wait, 10);
2863
+ if (!Number.isNaN(parsed) && parsed >= 0) {
2864
+ return parsed;
2865
+ }
2866
+ console.warn(`Warning: Invalid --wait value "${options.wait}". Using default (${DEFAULT_WAIT_TIMEOUT_MS}ms).`);
2867
+ }
2868
+ return DEFAULT_WAIT_TIMEOUT_MS;
2869
+ }
2870
+ function shouldPoll(options) {
2871
+ if (options.noPoll) {
2872
+ return false;
2873
+ }
2874
+ return process.stdout.isTTY ?? false;
2875
+ }
2876
+ async function pollSearchProgress(searchRef, pkgseerService, useColors) {
2877
+ const result = await pollUntilDone({
2878
+ fetch: async () => {
2879
+ const res = await pkgseerService.getSearchProgress(searchRef);
2880
+ return res.data.searchProgress ?? null;
2881
+ },
2882
+ isDone: (progress) => {
2883
+ if (!progress)
2884
+ return true;
2885
+ return TERMINAL_STATUSES.includes(progress.status);
2886
+ },
2887
+ onUpdate: (progress) => {
2888
+ if (progress) {
2889
+ const statusText = progress.status?.toLowerCase() ?? "unknown";
2890
+ const ready = progress.packagesReady ?? 0;
2891
+ const total = progress.packagesTotal ?? 0;
2892
+ const elapsed = ((progress.elapsedMs ?? 0) / 1000).toFixed(1);
2893
+ console.log(` ${statusText} (${ready}/${total} ready) - ${elapsed}s`);
2894
+ }
2895
+ },
2896
+ intervalMs: DEFAULT_POLL_INTERVAL_MS,
2897
+ maxWaitMs: DEFAULT_MAX_POLL_TIME_MS
2898
+ });
2899
+ if (!result.success) {
2900
+ return { success: false, error: result.error };
2901
+ }
2902
+ if (!result.data) {
2903
+ return { success: false, error: "Search session not found" };
2904
+ }
2905
+ return { success: true, status: result.data.status };
2906
+ }
2907
+ async function handleResume(searchRef, pkgseerService, options, useColors) {
2908
+ const progressResult = await pkgseerService.getSearchProgress(searchRef);
2909
+ if (progressResult.errors?.length) {
2910
+ handleErrors(progressResult.errors, options.json ?? false);
2911
+ return true;
2912
+ }
2913
+ const progress = progressResult.data.searchProgress;
2914
+ if (!progress) {
2915
+ outputError("Search session not found. It may have expired (sessions last 1 hour).", options.json ?? false);
2916
+ return true;
2917
+ }
2918
+ if (progress.status === "COMPLETED") {
2919
+ const resultsResponse = await pkgseerService.getSearchResults(searchRef);
2920
+ if (resultsResponse.errors?.length) {
2921
+ handleErrors(resultsResponse.errors, options.json ?? false);
2922
+ return true;
2923
+ }
2924
+ const searchResults = resultsResponse.data.searchResults;
2925
+ if (!searchResults) {
2926
+ outputError("Search completed but no results returned.", options.json ?? false);
2927
+ return true;
2928
+ }
2929
+ outputResults(searchResults, options, useColors);
2930
+ return true;
2931
+ }
2932
+ if (progress.status === "FAILED") {
2933
+ outputError(`Search failed. Ref: ${searchRef}`, options.json ?? false);
2934
+ return true;
2935
+ }
2936
+ if (progress.status === "TIMEOUT") {
2937
+ outputError(`Search timed out. Try again or use --wait with a higher value.
2938
+ Ref: ${searchRef}`, options.json ?? false);
2939
+ return true;
2940
+ }
2941
+ if (options.json) {
2942
+ output({
2943
+ completed: false,
2944
+ searchRef,
2945
+ progress: {
2946
+ status: progress.status,
2947
+ packagesTotal: progress.packagesTotal,
2948
+ packagesReady: progress.packagesReady,
2949
+ elapsedMs: progress.elapsedMs
2950
+ },
2951
+ message: "Search is still in progress."
2952
+ }, true);
2953
+ return true;
2954
+ }
2955
+ console.log("Checking search status...");
2956
+ console.log(`Status: ${progress.status?.toLowerCase() ?? "unknown"}`);
2957
+ if (shouldPoll(options)) {
2958
+ console.log("Polling for completion...");
2959
+ const pollResult = await pollSearchProgress(searchRef, pkgseerService, useColors);
2960
+ if (!pollResult.success) {
2961
+ outputError(pollResult.error, false);
2962
+ console.log(`Resume with: pkgseer search --resume ${searchRef}`);
2963
+ return true;
2964
+ }
2965
+ if (pollResult.status === "COMPLETED") {
2966
+ const resultsResponse = await pkgseerService.getSearchResults(searchRef);
2967
+ if (resultsResponse.data.searchResults) {
2968
+ outputResults(resultsResponse.data.searchResults, options, useColors);
2969
+ }
2970
+ } else {
2971
+ console.log(`Search ended with status: ${pollResult.status.toLowerCase()}`);
2972
+ }
2973
+ } else {
2974
+ console.log(`
2975
+ Resume with: pkgseer search --resume ${searchRef}`);
2976
+ }
2977
+ return true;
2978
+ }
2665
2979
  async function searchAction(queryArg, options, deps, defaultMode = "ALL") {
2666
2980
  const { pkgseerService } = deps;
2981
+ const useColors = shouldUseColors(options.noColor);
2982
+ if (options.resume?.trim()) {
2983
+ await handleResume(options.resume.trim(), pkgseerService, options, useColors);
2984
+ return;
2985
+ }
2667
2986
  const query = Array.isArray(queryArg) ? queryArg.join(" ") : queryArg ?? "";
2668
2987
  if (!query.trim()) {
2669
2988
  outputError("Search query required. Provide query as argument.", options.json ?? false);
@@ -2680,17 +2999,61 @@ async function searchAction(queryArg, options, deps, defaultMode = "ALL") {
2680
2999
  const packages = parsePackageList(options.packages);
2681
3000
  const limit = options.limit ? Number.parseInt(options.limit, 10) : 25;
2682
3001
  const mode = options.mode ? toSearchMode(options.mode) : defaultMode;
2683
- const useColors = shouldUseColors(options.noColor);
3002
+ const waitTimeoutMs = getWaitTimeoutMs(options);
2684
3003
  const result = await pkgseerService.combinedSearch(packages, query, {
2685
3004
  mode,
2686
- limit
3005
+ limit,
3006
+ waitTimeoutMs
2687
3007
  });
2688
3008
  handleErrors(result.errors, options.json ?? false);
2689
- if (!result.data.combinedSearch) {
3009
+ const asyncResult = result.data.combinedSearch;
3010
+ if (!asyncResult) {
2690
3011
  outputError("No results returned. Check package names and registries.", options.json ?? false);
2691
3012
  return;
2692
3013
  }
2693
- outputResults(result.data.combinedSearch, options, useColors);
3014
+ if (!asyncResult.completed) {
3015
+ const searchRef = asyncResult.searchRef;
3016
+ if (options.json) {
3017
+ output({
3018
+ completed: false,
3019
+ searchRef,
3020
+ progress: asyncResult.progress,
3021
+ message: "Search is still indexing. Retry for complete results."
3022
+ }, true);
3023
+ return;
3024
+ }
3025
+ if (shouldPoll(options) && searchRef) {
3026
+ console.log("Search in progress, polling for completion...");
3027
+ const pollResult = await pollSearchProgress(searchRef, pkgseerService, useColors);
3028
+ if (pollResult.success && pollResult.status === "COMPLETED") {
3029
+ const resultsResponse = await pkgseerService.getSearchResults(searchRef);
3030
+ if (resultsResponse.data.searchResults) {
3031
+ console.log("");
3032
+ outputResults(resultsResponse.data.searchResults, options, useColors);
3033
+ return;
3034
+ }
3035
+ }
3036
+ if (!pollResult.success) {
3037
+ console.log(`
3038
+ ${pollResult.error}`);
3039
+ } else if (pollResult.status !== "COMPLETED") {
3040
+ console.log(`
3041
+ Search ended with status: ${pollResult.status.toLowerCase()}`);
3042
+ }
3043
+ if (searchRef) {
3044
+ console.log(`Resume with: pkgseer search --resume ${searchRef}`);
3045
+ }
3046
+ return;
3047
+ }
3048
+ console.log(formatSearchProgress(asyncResult.progress, searchRef, useColors));
3049
+ return;
3050
+ }
3051
+ const searchResults = asyncResult.result;
3052
+ if (!searchResults) {
3053
+ outputError("Search completed but no results returned.", options.json ?? false);
3054
+ return;
3055
+ }
3056
+ outputResults(searchResults, options, useColors);
2694
3057
  }
2695
3058
  function outputResults(results, options, useColors) {
2696
3059
  const format = options.json ? "json" : options.format ?? "human";
@@ -2741,7 +3104,7 @@ Examples:
2741
3104
  # JSON output
2742
3105
  pkgseer search "error" -P express --json`;
2743
3106
  function addSearchOptions(cmd) {
2744
- return cmd.option("-P, --packages <packages>", "Packages to search (comma-separated). Format: name or registry/name[@version]. Examples: express | express,lodash | pypi/django@4.2").option("-m, --mode <mode>", "Search mode: all (default), code, docs").option("-l, --limit <n>", "Max results (default: 25)").option("-v, --verbose", "Expanded output with more details").option("--refs-only", "Output only references (for piping)").option("--count", "Output result counts by package").option("--format <format>", "Output format: human|summary|json", "human").option("--no-color", "Disable colored output").option("--json", "Output as JSON");
3107
+ return cmd.option("-P, --packages <packages>", "Packages to search (comma-separated). Format: name or registry/name[@version]. Examples: express | express,lodash | pypi/django@4.2").option("-m, --mode <mode>", "Search mode: all (default), code, docs").option("-l, --limit <n>", "Max results (default: 25)").option("-v, --verbose", "Expanded output with more details").option("--refs-only", "Output only references (for piping)").option("--count", "Output result counts by package").option("--format <format>", "Output format: human|summary|json", "human").option("--no-color", "Disable colored output").option("--json", "Output as JSON").option("--wait <ms>", "Max milliseconds to wait for indexing (default: 30000)").option("--no-wait", "Return immediately without waiting for indexing").option("--resume <ref>", "Resume a previous search by reference").option("--no-poll", "Disable auto-polling for incomplete searches");
2745
3108
  }
2746
3109
  function registerSearchCommand(program) {
2747
3110
  const cmd = program.command("search [query...]").summary("Search code and documentation across packages").description(SEARCH_DESCRIPTION);
@@ -2782,7 +3145,7 @@ Examples:
2782
3145
 
2783
3146
  Note: For code search, use 'pkgseer search --mode code' instead.`;
2784
3147
  function addDocsSearchOptions(cmd) {
2785
- return cmd.option("-P, --packages <packages>", "Packages to search (comma-separated). Format: [registry:]name[@version]. Examples: express | express,lodash | pypi:django@4.2").option("-l, --limit <n>", "Max results (default: 25)").option("-v, --verbose", "Expanded output with more details").option("--refs-only", "Output only references (for piping)").option("--count", "Output result counts by package").option("--format <format>", "Output format: human|summary|json", "human").option("--no-color", "Disable colored output").option("--json", "Output as JSON");
3148
+ return cmd.option("-P, --packages <packages>", "Packages to search (comma-separated). Format: [registry:]name[@version]. Examples: express | express,lodash | pypi:django@4.2").option("-l, --limit <n>", "Max results (default: 25)").option("-v, --verbose", "Expanded output with more details").option("--refs-only", "Output only references (for piping)").option("--count", "Output result counts by package").option("--format <format>", "Output format: human|summary|json", "human").option("--no-color", "Disable colored output").option("--json", "Output as JSON").option("--wait <ms>", "Max milliseconds to wait for indexing (default: 30000)").option("--no-wait", "Return immediately without waiting for indexing");
2786
3149
  }
2787
3150
  function registerDocsSearchCommand(program) {
2788
3151
  const cmd = program.command("search [query...]").summary("Search documentation").description(DOCS_SEARCH_DESCRIPTION);
@@ -4951,11 +5314,13 @@ var packageInputSchema2 = z7.object({
4951
5314
  name: z7.string().min(1).describe("Package name"),
4952
5315
  version: z7.string().optional().describe("Specific version (defaults to latest)")
4953
5316
  });
5317
+ var DEFAULT_AGENT_WAIT_TIMEOUT_MS = 1e4;
4954
5318
  var argsSchema9 = {
4955
5319
  packages: z7.array(packageInputSchema2).min(1).max(20).describe("Packages to search (1-20). Each package needs registry and name."),
4956
5320
  query: z7.string().min(1).describe("Search query - natural language or keywords"),
4957
5321
  mode: z7.enum(["all", "code", "docs"]).optional().describe('Search mode: "all" (default), "code" only, or "docs" only'),
4958
- limit: z7.number().int().min(1).max(100).optional().describe("Maximum results (default: 20)")
5322
+ limit: z7.number().int().min(1).max(100).optional().describe("Maximum results (default: 20)"),
5323
+ waitTimeoutMs: z7.number().int().min(0).max(60000).optional().describe("Max milliseconds to wait for indexing (default: 10000, 0=immediate)")
4959
5324
  };
4960
5325
  function toSearchMode2(mode) {
4961
5326
  if (!mode)
@@ -4993,9 +5358,9 @@ Results may be incomplete. Retry later for more complete results.`;
4993
5358
  function createSearchTool(pkgseerService) {
4994
5359
  return {
4995
5360
  name: "search",
4996
- description: "Search code and documentation across packages. Returns functions, classes, and documentation pages " + "matching your query. Use mode='code' for code only, mode='docs' for documentation only, or " + "mode='all' (default) for both. Provide 1-20 packages to search. Results include relevance scores " + "and snippets showing matches.",
5361
+ description: "Search code and documentation across packages. Returns functions, classes, and documentation pages " + "matching your query. Use mode='code' for code only, mode='docs' for documentation only, or " + "mode='all' (default) for both. Provide 1-20 packages to search. Results include relevance scores " + "and snippets showing matches. If packages need indexing, the search will wait up to waitTimeoutMs " + "(default 10s). If not complete, returns progress info with searchRef for follow-up.",
4997
5362
  schema: argsSchema9,
4998
- handler: async ({ packages, query, mode, limit }, _extra) => {
5363
+ handler: async ({ packages, query, mode, limit, waitTimeoutMs }, _extra) => {
4999
5364
  return withErrorHandling("search packages", async () => {
5000
5365
  const normalizedQuery = query.trim();
5001
5366
  if (normalizedQuery.length === 0) {
@@ -5008,15 +5373,29 @@ function createSearchTool(pkgseerService) {
5008
5373
  }));
5009
5374
  const result = await pkgseerService.combinedSearch(graphqlPackages, normalizedQuery, {
5010
5375
  mode: toSearchMode2(mode),
5011
- limit
5376
+ limit,
5377
+ waitTimeoutMs: waitTimeoutMs ?? DEFAULT_AGENT_WAIT_TIMEOUT_MS
5012
5378
  });
5013
5379
  const graphqlError = handleGraphQLErrors(result.errors);
5014
5380
  if (graphqlError)
5015
5381
  return graphqlError;
5016
- if (!result.data.combinedSearch) {
5382
+ const asyncResult = result.data.combinedSearch;
5383
+ if (!asyncResult) {
5017
5384
  return errorResult("No search results returned. Check package names and registries.");
5018
5385
  }
5019
- const searchResult = result.data.combinedSearch;
5386
+ if (!asyncResult.completed) {
5387
+ const progressInfo = {
5388
+ completed: false,
5389
+ searchRef: asyncResult.searchRef,
5390
+ progress: asyncResult.progress,
5391
+ message: "Search is still indexing. Retry with same query for results, " + "or increase waitTimeoutMs to wait longer."
5392
+ };
5393
+ return textResult(JSON.stringify(progressInfo, null, 2));
5394
+ }
5395
+ const searchResult = asyncResult.result;
5396
+ if (!searchResult) {
5397
+ return errorResult("Search completed but no results returned.");
5398
+ }
5020
5399
  const entries = searchResult.entries ?? [];
5021
5400
  if (entries.length === 0) {
5022
5401
  return errorResult(`No results found for "${normalizedQuery}". ` + "Try broader terms or different packages.");
@@ -5091,6 +5470,55 @@ function createSearchProjectDocsTool(deps) {
5091
5470
  }
5092
5471
  };
5093
5472
  }
5473
+ // src/tools/search-status.ts
5474
+ import { z as z9 } from "zod";
5475
+ var argsSchema11 = {
5476
+ searchRef: z9.string().min(1).describe("Search reference from a previous incomplete search")
5477
+ };
5478
+ function createSearchStatusTool(pkgseerService) {
5479
+ return {
5480
+ name: "search_status",
5481
+ description: "Check the status of an async search and get results if complete. " + "Use this after a search returns incomplete (completed=false) to poll for completion. " + "Returns status (PENDING, INDEXING, SEARCHING, COMPLETED, TIMEOUT, FAILED), " + "progress info (packagesReady/packagesTotal), and results when status is COMPLETED.",
5482
+ schema: argsSchema11,
5483
+ handler: async ({ searchRef }, _extra) => {
5484
+ return withErrorHandling("check search status", async () => {
5485
+ const progressResult = await pkgseerService.getSearchProgress(searchRef);
5486
+ const graphqlError = handleGraphQLErrors(progressResult.errors);
5487
+ if (graphqlError)
5488
+ return graphqlError;
5489
+ const progress = progressResult.data.searchProgress;
5490
+ if (!progress) {
5491
+ return errorResult("Search session not found. It may have expired (sessions last 1 hour).");
5492
+ }
5493
+ if (progress.status === "COMPLETED") {
5494
+ const resultsResponse = await pkgseerService.getSearchResults(searchRef);
5495
+ const resultsError = handleGraphQLErrors(resultsResponse.errors);
5496
+ if (resultsError)
5497
+ return resultsError;
5498
+ const searchResults = resultsResponse.data.searchResults;
5499
+ if (!searchResults) {
5500
+ return errorResult("Search completed but results are no longer available. " + "The session may have expired (sessions last 1 hour).");
5501
+ }
5502
+ return textResult(JSON.stringify({
5503
+ status: progress.status,
5504
+ searchRef: progress.searchRef,
5505
+ result: searchResults
5506
+ }, null, 2));
5507
+ }
5508
+ return textResult(JSON.stringify({
5509
+ status: progress.status,
5510
+ searchRef: progress.searchRef,
5511
+ progress: {
5512
+ packagesTotal: progress.packagesTotal,
5513
+ packagesReady: progress.packagesReady,
5514
+ elapsedMs: progress.elapsedMs
5515
+ },
5516
+ message: progress.status === "FAILED" ? "Search failed." : progress.status === "TIMEOUT" ? "Search timed out before completion." : "Search is still in progress. Poll again to check status."
5517
+ }, null, 2));
5518
+ });
5519
+ }
5520
+ };
5521
+ }
5094
5522
  // src/commands/mcp.ts
5095
5523
  var TOOL_FACTORIES = {
5096
5524
  package_summary: ({ pkgseerService }) => createPackageSummaryTool(pkgseerService),
@@ -5101,6 +5529,7 @@ var TOOL_FACTORIES = {
5101
5529
  list_package_docs: ({ pkgseerService }) => createListPackageDocsTool(pkgseerService),
5102
5530
  fetch_package_doc: ({ pkgseerService }) => createFetchPackageDocTool(pkgseerService),
5103
5531
  search: ({ pkgseerService }) => createSearchTool(pkgseerService),
5532
+ search_status: ({ pkgseerService }) => createSearchStatusTool(pkgseerService),
5104
5533
  fetch_code_context: ({ pkgseerService }) => createFetchCodeContextTool(pkgseerService),
5105
5534
  search_project_docs: ({ pkgseerService, configService, defaultProjectDir }) => createSearchProjectDocsTool({
5106
5535
  pkgseerService,
@@ -5117,6 +5546,7 @@ var PUBLIC_READ_TOOLS = [
5117
5546
  "list_package_docs",
5118
5547
  "fetch_package_doc",
5119
5548
  "search",
5549
+ "search_status",
5120
5550
  "fetch_code_context"
5121
5551
  ];
5122
5552
  var PROJECT_READ_TOOLS = ["search_project_docs"];
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  version
3
- } from "./shared/chunk-drz16bhv.js";
3
+ } from "./shared/chunk-55x4vqp2.js";
4
4
  export {
5
5
  version
6
6
  };
@@ -1,4 +1,4 @@
1
1
  // package.json
2
- var version = "0.2.5";
2
+ var version = "0.3.1";
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.2.5",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",