@pkgseer/cli 0.3.0 → 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
@@ -100,10 +100,16 @@ pkgseer search "auth" -P lodash --mode docs # Docs only
100
100
  # Wait options (for packages that need indexing)
101
101
  pkgseer search "api" -P new-package --wait 60000 # Wait up to 60s
102
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
103
107
  ```
104
108
 
105
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.
106
110
 
111
+ For long-running searches, use `--no-poll` to get a searchRef and later resume with `--resume <ref>`.
112
+
107
113
  ### Package Commands
108
114
 
109
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-9yar14cw.js";
4
+ } from "./shared/chunk-55x4vqp2.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { Command } from "commander";
@@ -261,6 +261,52 @@ var CombinedSearchDocument = gql`
261
261
  }
262
262
  }
263
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) {
278
+ query
279
+ mode
280
+ totalResults
281
+ entries {
282
+ id
283
+ type
284
+ title
285
+ subtitle
286
+ packageName
287
+ registry
288
+ score
289
+ snippet
290
+ filePath
291
+ startLine
292
+ endLine
293
+ language
294
+ chunkType
295
+ repoUrl
296
+ gitRef
297
+ pageId
298
+ sourceUrl
299
+ }
300
+ indexingStatus {
301
+ registry
302
+ packageName
303
+ version
304
+ codeStatus
305
+ docsStatus
306
+ }
307
+ }
308
+ }
309
+ `;
264
310
  var FetchCodeContextDocument = gql`
265
311
  query FetchCodeContext($repoUrl: String!, $gitRef: String!, $filePath: String!, $startLine: Int, $endLine: Int) {
266
312
  fetchCodeContext(
@@ -629,6 +675,8 @@ var CliDocsSearchDocumentString = print(CliDocsSearchDocument);
629
675
  var CliProjectDocsSearchDocumentString = print(CliProjectDocsSearchDocument);
630
676
  var CliDocsListDocumentString = print(CliDocsListDocument);
631
677
  var CombinedSearchDocumentString = print(CombinedSearchDocument);
678
+ var SearchProgressDocumentString = print(SearchProgressDocument);
679
+ var SearchResultsDocumentString = print(SearchResultsDocument);
632
680
  var FetchCodeContextDocumentString = print(FetchCodeContextDocument);
633
681
  var CliDocsGetDocumentString = print(CliDocsGetDocument);
634
682
  var CreateProjectDocumentString = print(CreateProjectDocument);
@@ -671,6 +719,12 @@ function getSdk(client, withWrapper = defaultWrapper) {
671
719
  CombinedSearch(variables, requestHeaders) {
672
720
  return withWrapper((wrappedRequestHeaders) => client.rawRequest(CombinedSearchDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "CombinedSearch", "query", variables);
673
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
+ },
674
728
  FetchCodeContext(variables, requestHeaders) {
675
729
  return withWrapper((wrappedRequestHeaders) => client.rawRequest(FetchCodeContextDocumentString, variables, { ...requestHeaders, ...wrappedRequestHeaders }), "FetchCodeContext", "query", variables);
676
730
  },
@@ -1037,6 +1091,7 @@ var TOOL_NAMES = [
1037
1091
  "list_package_docs",
1038
1092
  "fetch_package_doc",
1039
1093
  "search",
1094
+ "search_status",
1040
1095
  "fetch_code_context",
1041
1096
  "search_project_docs"
1042
1097
  ];
@@ -1440,6 +1495,14 @@ class PkgseerServiceImpl {
1440
1495
  });
1441
1496
  return { data: result.data, errors: result.errors };
1442
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
+ }
1443
1506
  async fetchCodeContext(repoUrl, gitRef, filePath, options) {
1444
1507
  const result = await this.client.FetchCodeContext({
1445
1508
  repoUrl,
@@ -2358,8 +2421,59 @@ function registerDocsListCommand(program) {
2358
2421
  });
2359
2422
  });
2360
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
+
2361
2468
  // src/commands/search.ts
2362
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
+ ];
2363
2477
  var colors = {
2364
2478
  reset: "\x1B[0m",
2365
2479
  bold: "\x1B[1m",
@@ -2753,8 +2867,122 @@ function getWaitTimeoutMs(options) {
2753
2867
  }
2754
2868
  return DEFAULT_WAIT_TIMEOUT_MS;
2755
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
+ }
2756
2979
  async function searchAction(queryArg, options, deps, defaultMode = "ALL") {
2757
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
+ }
2758
2986
  const query = Array.isArray(queryArg) ? queryArg.join(" ") : queryArg ?? "";
2759
2987
  if (!query.trim()) {
2760
2988
  outputError("Search query required. Provide query as argument.", options.json ?? false);
@@ -2771,7 +2999,6 @@ async function searchAction(queryArg, options, deps, defaultMode = "ALL") {
2771
2999
  const packages = parsePackageList(options.packages);
2772
3000
  const limit = options.limit ? Number.parseInt(options.limit, 10) : 25;
2773
3001
  const mode = options.mode ? toSearchMode(options.mode) : defaultMode;
2774
- const useColors = shouldUseColors(options.noColor);
2775
3002
  const waitTimeoutMs = getWaitTimeoutMs(options);
2776
3003
  const result = await pkgseerService.combinedSearch(packages, query, {
2777
3004
  mode,
@@ -2785,16 +3012,40 @@ async function searchAction(queryArg, options, deps, defaultMode = "ALL") {
2785
3012
  return;
2786
3013
  }
2787
3014
  if (!asyncResult.completed) {
3015
+ const searchRef = asyncResult.searchRef;
2788
3016
  if (options.json) {
2789
3017
  output({
2790
3018
  completed: false,
2791
- searchRef: asyncResult.searchRef,
3019
+ searchRef,
2792
3020
  progress: asyncResult.progress,
2793
3021
  message: "Search is still indexing. Retry for complete results."
2794
3022
  }, true);
2795
- } else {
2796
- console.log(formatSearchProgress(asyncResult.progress, asyncResult.searchRef, useColors));
3023
+ return;
2797
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));
2798
3049
  return;
2799
3050
  }
2800
3051
  const searchResults = asyncResult.result;
@@ -2853,7 +3104,7 @@ Examples:
2853
3104
  # JSON output
2854
3105
  pkgseer search "error" -P express --json`;
2855
3106
  function addSearchOptions(cmd) {
2856
- 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");
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");
2857
3108
  }
2858
3109
  function registerSearchCommand(program) {
2859
3110
  const cmd = program.command("search [query...]").summary("Search code and documentation across packages").description(SEARCH_DESCRIPTION);
@@ -5219,6 +5470,55 @@ function createSearchProjectDocsTool(deps) {
5219
5470
  }
5220
5471
  };
5221
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
+ }
5222
5522
  // src/commands/mcp.ts
5223
5523
  var TOOL_FACTORIES = {
5224
5524
  package_summary: ({ pkgseerService }) => createPackageSummaryTool(pkgseerService),
@@ -5229,6 +5529,7 @@ var TOOL_FACTORIES = {
5229
5529
  list_package_docs: ({ pkgseerService }) => createListPackageDocsTool(pkgseerService),
5230
5530
  fetch_package_doc: ({ pkgseerService }) => createFetchPackageDocTool(pkgseerService),
5231
5531
  search: ({ pkgseerService }) => createSearchTool(pkgseerService),
5532
+ search_status: ({ pkgseerService }) => createSearchStatusTool(pkgseerService),
5232
5533
  fetch_code_context: ({ pkgseerService }) => createFetchCodeContextTool(pkgseerService),
5233
5534
  search_project_docs: ({ pkgseerService, configService, defaultProjectDir }) => createSearchProjectDocsTool({
5234
5535
  pkgseerService,
@@ -5245,6 +5546,7 @@ var PUBLIC_READ_TOOLS = [
5245
5546
  "list_package_docs",
5246
5547
  "fetch_package_doc",
5247
5548
  "search",
5549
+ "search_status",
5248
5550
  "fetch_code_context"
5249
5551
  ];
5250
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-9yar14cw.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.3.0";
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.3.0",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",