@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 +12 -0
- package/dist/cli.js +451 -21
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-drz16bhv.js → chunk-55x4vqp2.js} +1 -1
- package/package.json +1 -1
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-
|
|
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(
|
|
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
|
|
2583
|
-
|
|
2584
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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