@pkgseer/cli 0.2.3 → 0.2.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/README.md +141 -52
- package/dist/cli.js +231 -163
- package/dist/index.js +1 -1
- package/dist/shared/{chunk-zaq9c2d8.js → chunk-drz16bhv.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,120 +2,209 @@
|
|
|
2
2
|
|
|
3
3
|
CLI and MCP server for [PkgSeer](https://pkgseer.dev) — package intelligence for developers and AI assistants.
|
|
4
4
|
|
|
5
|
-
Get insights about packages across npm, PyPI, and
|
|
5
|
+
Get insights about packages across npm, PyPI, Hex, and crates.io registries: search code and documentation, check security vulnerabilities, analyze dependencies, and compare packages. Works standalone or as an MCP server for AI assistants like Claude and Cursor.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Use npx without installation:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npx @pkgseer/cli --help
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Or install globally:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
18
|
npm install -g @pkgseer/cli
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
##
|
|
21
|
+
## Quick Start
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
```bash
|
|
24
|
+
# Interactive setup (recommended for first-time users)
|
|
25
|
+
pkgseer init
|
|
26
|
+
|
|
27
|
+
# Or set up manually:
|
|
28
|
+
pkgseer login # Authenticate with your PkgSeer account
|
|
29
|
+
pkgseer skill init # Install as AI assistant skill
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## AI Assistant Integration
|
|
33
|
+
|
|
34
|
+
PkgSeer works with AI assistants in two ways:
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
### Skills
|
|
37
|
+
|
|
38
|
+
Skills teach your AI assistant to use PkgSeer CLI commands through natural language:
|
|
26
39
|
|
|
27
40
|
```bash
|
|
28
|
-
pkgseer
|
|
41
|
+
pkgseer skill init
|
|
29
42
|
```
|
|
30
43
|
|
|
31
|
-
This
|
|
44
|
+
This installs a skill definition for Claude Code or Codex CLI. The AI runs CLI commands and reads the output.
|
|
45
|
+
|
|
46
|
+
### MCP Server
|
|
32
47
|
|
|
33
|
-
|
|
48
|
+
MCP provides structured tools that AI assistants can call programmatically:
|
|
34
49
|
|
|
35
50
|
```bash
|
|
36
|
-
pkgseer
|
|
51
|
+
pkgseer mcp init
|
|
37
52
|
```
|
|
38
53
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
The main use case for this CLI is as an MCP (Model Context Protocol) server that gives AI assistants access to package intelligence.
|
|
54
|
+
This provides structured tools that AI assistants can call programmatically. Configuration varies by assistant:
|
|
42
55
|
|
|
43
|
-
|
|
56
|
+
**Claude Code / Codex CLI**: The `mcp init` command configures these automatically.
|
|
44
57
|
|
|
45
|
-
Add
|
|
58
|
+
**Cursor IDE**: Add to `.cursor/mcp.json`:
|
|
46
59
|
|
|
47
60
|
```json
|
|
48
61
|
{
|
|
49
62
|
"mcpServers": {
|
|
50
63
|
"pkgseer": {
|
|
51
64
|
"command": "npx",
|
|
52
|
-
"args": ["-y", "@pkgseer/cli", "mcp"]
|
|
65
|
+
"args": ["-y", "@pkgseer/cli", "mcp", "start"]
|
|
53
66
|
}
|
|
54
67
|
}
|
|
55
68
|
}
|
|
56
69
|
```
|
|
57
70
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Add this to your Claude Desktop configuration file:
|
|
61
|
-
|
|
62
|
-
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
63
|
-
**Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
71
|
+
**Claude Desktop**: Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
|
|
64
72
|
|
|
65
73
|
```json
|
|
66
74
|
{
|
|
67
75
|
"mcpServers": {
|
|
68
76
|
"pkgseer": {
|
|
69
77
|
"command": "npx",
|
|
70
|
-
"args": ["-y", "@pkgseer/cli", "mcp"]
|
|
78
|
+
"args": ["-y", "@pkgseer/cli", "mcp", "start"]
|
|
71
79
|
}
|
|
72
80
|
}
|
|
73
81
|
}
|
|
74
82
|
```
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
## CLI Commands
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
- "Compare react vs preact vs solid-js"
|
|
80
|
-
- "What are the dependencies of express?"
|
|
86
|
+
### Search
|
|
81
87
|
|
|
82
|
-
|
|
88
|
+
Search code and documentation across packages:
|
|
83
89
|
|
|
84
|
-
|
|
90
|
+
```bash
|
|
91
|
+
# Search in specific packages
|
|
92
|
+
pkgseer search "authentication" -P express,passport
|
|
93
|
+
pkgseer search "http client" -P pypi:requests,pypi:httpx
|
|
94
|
+
pkgseer search "json parsing" -P hex:jason,hex:poison
|
|
95
|
+
|
|
96
|
+
# Search modes
|
|
97
|
+
pkgseer search "auth" -P lodash --mode code # Code only
|
|
98
|
+
pkgseer search "auth" -P lodash --mode docs # Docs only
|
|
99
|
+
```
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
When working with dependencies:
|
|
101
|
+
### Package Commands
|
|
88
102
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
```bash
|
|
104
|
+
pkgseer pkg info lodash # Package summary and metadata
|
|
105
|
+
pkgseer pkg vulns lodash@4.17.21 # Security vulnerabilities
|
|
106
|
+
pkgseer pkg quality express # Quality score (0-100)
|
|
107
|
+
pkgseer pkg deps express # Direct dependencies
|
|
108
|
+
pkgseer pkg deps express --transitive # Include transitive deps
|
|
109
|
+
pkgseer pkg compare axios got fetch-h2 # Compare packages
|
|
92
110
|
```
|
|
93
111
|
|
|
94
|
-
|
|
112
|
+
Package format: `[registry:]name[@version]`
|
|
113
|
+
- `lodash` — npm (default registry)
|
|
114
|
+
- `pypi:requests` — PyPI
|
|
115
|
+
- `hex:phoenix` — Hex
|
|
116
|
+
- `crates:serde` — crates.io
|
|
117
|
+
- `lodash@4.17.21` — specific version
|
|
118
|
+
|
|
119
|
+
### Documentation Commands
|
|
95
120
|
|
|
96
|
-
|
|
121
|
+
```bash
|
|
122
|
+
pkgseer docs list pypi:requests # List available doc pages
|
|
123
|
+
pkgseer docs get lodash/chunk # Fetch specific doc page
|
|
124
|
+
pkgseer docs search "routing" -P express # Search docs only
|
|
125
|
+
```
|
|
97
126
|
|
|
98
|
-
|
|
99
|
-
| ------------------------- | ----------------------------------------------------------------------------------- |
|
|
100
|
-
| `package_summary` | Get package metadata, latest versions, security advisories, and quickstart examples |
|
|
101
|
-
| `package_vulnerabilities` | Find known security vulnerabilities affecting a package |
|
|
102
|
-
| `package_dependencies` | Explore the dependency tree (direct and transitive) |
|
|
103
|
-
| `package_quality` | View quality metrics and maintenance scores |
|
|
104
|
-
| `compare_packages` | Compare multiple packages side-by-side |
|
|
127
|
+
### Project Commands
|
|
105
128
|
|
|
106
|
-
|
|
129
|
+
```bash
|
|
130
|
+
pkgseer project init # Create pkgseer.yml config
|
|
131
|
+
pkgseer project detect # Detect package manifests
|
|
132
|
+
pkgseer project upload # Upload project to PkgSeer
|
|
133
|
+
```
|
|
107
134
|
|
|
108
|
-
|
|
135
|
+
### Authentication
|
|
109
136
|
|
|
110
137
|
```bash
|
|
111
|
-
pkgseer
|
|
112
|
-
pkgseer
|
|
138
|
+
pkgseer login # Authenticate via browser
|
|
139
|
+
pkgseer logout # Sign out
|
|
140
|
+
pkgseer auth status # Check authentication state
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Configuration
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pkgseer config show # Display current configuration
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## MCP Tools
|
|
150
|
+
|
|
151
|
+
When running as an MCP server, these tools are available:
|
|
152
|
+
|
|
153
|
+
| Tool | Description |
|
|
154
|
+
|------|-------------|
|
|
155
|
+
| `package_summary` | Package metadata, versions, quickstart examples |
|
|
156
|
+
| `package_vulnerabilities` | Security advisories and CVEs |
|
|
157
|
+
| `package_dependencies` | Dependency tree (direct and transitive) |
|
|
158
|
+
| `package_quality` | Quality score with category breakdown |
|
|
159
|
+
| `compare_packages` | Side-by-side comparison of packages |
|
|
160
|
+
| `list_package_docs` | Available documentation pages |
|
|
161
|
+
| `fetch_package_doc` | Full content of a documentation page |
|
|
162
|
+
| `search` | Search code and docs across packages |
|
|
163
|
+
| `fetch_code_context` | Fetch code from search results |
|
|
164
|
+
| `search_project_docs` | Search docs for packages in current project |
|
|
165
|
+
|
|
166
|
+
## Configuration
|
|
167
|
+
|
|
168
|
+
### Project Configuration
|
|
113
169
|
|
|
114
|
-
pkgseer
|
|
115
|
-
pkgseer logout # Sign out and clear stored credentials
|
|
116
|
-
pkgseer auth status # Check if you're logged in and token validity
|
|
170
|
+
Create `pkgseer.yml` in your project root:
|
|
117
171
|
|
|
118
|
-
|
|
172
|
+
```yaml
|
|
173
|
+
project: my-project-name
|
|
174
|
+
|
|
175
|
+
# Optional: limit which tools are available
|
|
176
|
+
enabled_tools:
|
|
177
|
+
- package_summary
|
|
178
|
+
- package_vulnerabilities
|
|
179
|
+
- search_project_docs
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Environment Variables
|
|
183
|
+
|
|
184
|
+
| Variable | Description |
|
|
185
|
+
|----------|-------------|
|
|
186
|
+
| `PKGSEER_API_TOKEN` | API token (alternative to `pkgseer login`) |
|
|
187
|
+
| `PKGSEER_URL` | Base URL for PkgSeer (for development/testing) |
|
|
188
|
+
|
|
189
|
+
## Documentation
|
|
190
|
+
|
|
191
|
+
- [Architecture Overview](docs/implementation/architecture.md)
|
|
192
|
+
- [MCP Installation Guide](docs/implementation/mcp-installation.md)
|
|
193
|
+
- [Creating MCP Tools](docs/implementation/tools.md)
|
|
194
|
+
- [Authentication Flow](docs/implementation/auth.md)
|
|
195
|
+
- [Skills System](docs/implementation/skills.md)
|
|
196
|
+
- [Configuration Reference](docs/implementation/configuration.md)
|
|
197
|
+
|
|
198
|
+
## Development
|
|
199
|
+
|
|
200
|
+
See [CLAUDE.md](CLAUDE.md) for development guidelines.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
bun install # Install dependencies
|
|
204
|
+
bun run dev # Development mode
|
|
205
|
+
bun test # Run tests
|
|
206
|
+
bun run build # Build for production
|
|
207
|
+
bun run codegen # Regenerate GraphQL types
|
|
119
208
|
```
|
|
120
209
|
|
|
121
210
|
## Need Help?
|
|
@@ -125,4 +214,4 @@ pkgseer mcp # Start the MCP server (for AI assistant integration)
|
|
|
125
214
|
|
|
126
215
|
## License
|
|
127
216
|
|
|
128
|
-
|
|
217
|
+
(c) 2025-2026 Juha Litola
|
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-drz16bhv.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -23,6 +23,7 @@ var CliPackageInfoDocument = gql`
|
|
|
23
23
|
license
|
|
24
24
|
homepage
|
|
25
25
|
repositoryUrl
|
|
26
|
+
downloadsLastMonth
|
|
26
27
|
}
|
|
27
28
|
security {
|
|
28
29
|
vulnerabilityCount
|
|
@@ -1782,11 +1783,33 @@ function registerConfigShowCommand(program) {
|
|
|
1782
1783
|
}
|
|
1783
1784
|
|
|
1784
1785
|
// src/commands/shared.ts
|
|
1786
|
+
function parsePackageSpec(spec) {
|
|
1787
|
+
let registry = "npm";
|
|
1788
|
+
let rest = spec;
|
|
1789
|
+
if (spec.includes(":")) {
|
|
1790
|
+
const colonIndex = spec.indexOf(":");
|
|
1791
|
+
const potentialRegistry = spec.slice(0, colonIndex).toLowerCase();
|
|
1792
|
+
if (["npm", "pypi", "hex", "crates"].includes(potentialRegistry)) {
|
|
1793
|
+
registry = potentialRegistry;
|
|
1794
|
+
rest = spec.slice(colonIndex + 1);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
const atIndex = rest.lastIndexOf("@");
|
|
1798
|
+
if (atIndex > 0) {
|
|
1799
|
+
return {
|
|
1800
|
+
registry,
|
|
1801
|
+
name: rest.slice(0, atIndex),
|
|
1802
|
+
version: rest.slice(atIndex + 1)
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
return { registry, name: rest };
|
|
1806
|
+
}
|
|
1785
1807
|
function toGraphQLRegistry(registry) {
|
|
1786
1808
|
const map = {
|
|
1787
1809
|
npm: "NPM",
|
|
1788
1810
|
pypi: "PYPI",
|
|
1789
|
-
hex: "HEX"
|
|
1811
|
+
hex: "HEX",
|
|
1812
|
+
crates: "CRATES"
|
|
1790
1813
|
};
|
|
1791
1814
|
return map[registry.toLowerCase()] || "NPM";
|
|
1792
1815
|
}
|
|
@@ -2242,7 +2265,7 @@ Examples:
|
|
|
2242
2265
|
# Multiple pages
|
|
2243
2266
|
pkgseer docs get 24293-shared-plugins-during-build-2 npm/express/4.18.2/readme`;
|
|
2244
2267
|
function registerDocsGetCommand(program) {
|
|
2245
|
-
program.command("get <refs...>").summary("Fetch documentation page(s)").description(GET_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-
|
|
2268
|
+
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) => {
|
|
2246
2269
|
await withCliErrorHandling(options.json ?? false, async () => {
|
|
2247
2270
|
const deps = await createContainer();
|
|
2248
2271
|
await docsGetAction(refs, options, deps);
|
|
@@ -2272,13 +2295,15 @@ function formatDocsList(docs) {
|
|
|
2272
2295
|
return lines.join(`
|
|
2273
2296
|
`);
|
|
2274
2297
|
}
|
|
2275
|
-
async function docsListAction(
|
|
2298
|
+
async function docsListAction(packageArg, options, deps) {
|
|
2276
2299
|
const { pkgseerService } = deps;
|
|
2277
|
-
const
|
|
2278
|
-
const
|
|
2300
|
+
const parsed = parsePackageSpec(packageArg);
|
|
2301
|
+
const registry = toGraphQLRegistry(parsed.registry !== "npm" ? parsed.registry : options.registry);
|
|
2302
|
+
const version2 = parsed.version ?? options.pkgVersion;
|
|
2303
|
+
const result = await pkgseerService.cliDocsList(registry, parsed.name, version2);
|
|
2279
2304
|
handleErrors(result.errors, options.json ?? false);
|
|
2280
2305
|
if (!result.data.listPackageDocs) {
|
|
2281
|
-
outputError(`Package not found: ${
|
|
2306
|
+
outputError(`Package not found: ${parsed.name} in ${parsed.registry}`, options.json ?? false);
|
|
2282
2307
|
return;
|
|
2283
2308
|
}
|
|
2284
2309
|
if (options.json) {
|
|
@@ -2302,19 +2327,21 @@ Shows all documentation pages with titles, globally unique page IDs,
|
|
|
2302
2327
|
word counts, and descriptions. Use the page ID with 'docs get'
|
|
2303
2328
|
to fetch the full content of a specific page.
|
|
2304
2329
|
|
|
2330
|
+
Package format: [registry:]name[@version]
|
|
2331
|
+
|
|
2305
2332
|
Examples:
|
|
2306
2333
|
pkgseer docs list lodash
|
|
2307
|
-
pkgseer docs list requests
|
|
2308
|
-
pkgseer docs list phoenix
|
|
2334
|
+
pkgseer docs list pypi:requests
|
|
2335
|
+
pkgseer docs list hex:phoenix@1.7.0 --json`;
|
|
2309
2336
|
function registerDocsListCommand(program) {
|
|
2310
|
-
program.command("list <package>").summary("List documentation pages").description(LIST_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-
|
|
2337
|
+
program.command("list <package>").summary("List documentation pages").description(LIST_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-V, --pkg-version <version>", "Package version").option("--json", "Output as JSON").action(async (packageName, options) => {
|
|
2311
2338
|
await withCliErrorHandling(options.json ?? false, async () => {
|
|
2312
2339
|
const deps = await createContainer();
|
|
2313
2340
|
await docsListAction(packageName, options, deps);
|
|
2314
2341
|
});
|
|
2315
2342
|
});
|
|
2316
2343
|
}
|
|
2317
|
-
// src/commands/
|
|
2344
|
+
// src/commands/search.ts
|
|
2318
2345
|
var colors = {
|
|
2319
2346
|
reset: "\x1B[0m",
|
|
2320
2347
|
bold: "\x1B[1m",
|
|
@@ -2344,37 +2371,11 @@ function toSearchMode(mode) {
|
|
|
2344
2371
|
}
|
|
2345
2372
|
function parsePackageList(input2) {
|
|
2346
2373
|
return input2.split(",").map((spec) => {
|
|
2347
|
-
const
|
|
2348
|
-
const atIndex = trimmed.lastIndexOf("@");
|
|
2349
|
-
let regPkg;
|
|
2350
|
-
let version2;
|
|
2351
|
-
if (atIndex > 0) {
|
|
2352
|
-
regPkg = trimmed.slice(0, atIndex);
|
|
2353
|
-
version2 = trimmed.slice(atIndex + 1);
|
|
2354
|
-
} else {
|
|
2355
|
-
regPkg = trimmed;
|
|
2356
|
-
}
|
|
2357
|
-
const slashIndex = regPkg.indexOf("/");
|
|
2358
|
-
if (slashIndex === -1) {
|
|
2359
|
-
return {
|
|
2360
|
-
registry: "NPM",
|
|
2361
|
-
name: regPkg,
|
|
2362
|
-
version: version2
|
|
2363
|
-
};
|
|
2364
|
-
}
|
|
2365
|
-
const beforeSlash = regPkg.slice(0, slashIndex);
|
|
2366
|
-
const afterSlash = regPkg.slice(slashIndex + 1);
|
|
2367
|
-
if (beforeSlash.startsWith("@")) {
|
|
2368
|
-
return {
|
|
2369
|
-
registry: "NPM",
|
|
2370
|
-
name: regPkg,
|
|
2371
|
-
version: version2
|
|
2372
|
-
};
|
|
2373
|
-
}
|
|
2374
|
+
const parsed = parsePackageSpec(spec.trim());
|
|
2374
2375
|
return {
|
|
2375
|
-
registry: toGraphQLRegistry(
|
|
2376
|
-
name:
|
|
2377
|
-
version:
|
|
2376
|
+
registry: toGraphQLRegistry(parsed.registry),
|
|
2377
|
+
name: parsed.name,
|
|
2378
|
+
version: parsed.version
|
|
2378
2379
|
};
|
|
2379
2380
|
});
|
|
2380
2381
|
}
|
|
@@ -2394,7 +2395,7 @@ function formatEntry(entry, useColors) {
|
|
|
2394
2395
|
if (entry.type === "CODE") {
|
|
2395
2396
|
const title = entry.title ?? "Unknown";
|
|
2396
2397
|
const location = entry.filePath ? `${entry.filePath}:${entry.startLine ?? "?"}-${entry.endLine ?? "?"}` : "";
|
|
2397
|
-
const lang = entry.language ? `
|
|
2398
|
+
const lang = entry.language ? ` * ${entry.language}` : "";
|
|
2398
2399
|
if (useColors) {
|
|
2399
2400
|
lines.push(`${badge} ${colors.bold}${title}${colors.reset}`);
|
|
2400
2401
|
if (location) {
|
|
@@ -2446,7 +2447,7 @@ function formatPackageHeader(entry, prevEntry, useColors) {
|
|
|
2446
2447
|
return "";
|
|
2447
2448
|
}
|
|
2448
2449
|
const header = `${pkg} (${registry})`;
|
|
2449
|
-
const separator = "
|
|
2450
|
+
const separator = "=".repeat(Math.min(50, header.length + 10));
|
|
2450
2451
|
if (useColors) {
|
|
2451
2452
|
return `
|
|
2452
2453
|
${colors.yellow}${header}${colors.reset}
|
|
@@ -2478,10 +2479,10 @@ function formatSearchResults(results, useColors) {
|
|
|
2478
2479
|
const codeCount = entries.filter((e) => e?.type === "CODE").length;
|
|
2479
2480
|
const docCount = entries.filter((e) => e?.type === "DOC" || e?.type === "REPO_DOC").length;
|
|
2480
2481
|
if (useColors) {
|
|
2481
|
-
lines.push(`${colors.dim}
|
|
2482
|
+
lines.push(`${colors.dim}---------------------------------------------------${colors.reset}`);
|
|
2482
2483
|
lines.push(`${colors.dim}Results: ${entries.length} total (${codeCount} code, ${docCount} docs)${colors.reset}`);
|
|
2483
2484
|
} else {
|
|
2484
|
-
lines.push("
|
|
2485
|
+
lines.push("---------------------------------------------------");
|
|
2485
2486
|
lines.push(`Results: ${entries.length} total (${codeCount} code, ${docCount} docs)`);
|
|
2486
2487
|
}
|
|
2487
2488
|
const indexingWarnings = formatIndexingWarnings(results.indexingStatus);
|
|
@@ -2559,7 +2560,7 @@ function abbrevLang(lang) {
|
|
|
2559
2560
|
function truncate(text, maxLen) {
|
|
2560
2561
|
if (text.length <= maxLen)
|
|
2561
2562
|
return text;
|
|
2562
|
-
return text.slice(0, maxLen - 3)
|
|
2563
|
+
return `${text.slice(0, maxLen - 3)}...`;
|
|
2563
2564
|
}
|
|
2564
2565
|
function formatEntryCompact(entry, useColors) {
|
|
2565
2566
|
if (!entry)
|
|
@@ -2661,7 +2662,7 @@ function summarizeSearchResults(results) {
|
|
|
2661
2662
|
return lines.join(`
|
|
2662
2663
|
`);
|
|
2663
2664
|
}
|
|
2664
|
-
async function
|
|
2665
|
+
async function searchAction(queryArg, options, deps, defaultMode = "ALL") {
|
|
2665
2666
|
const { pkgseerService } = deps;
|
|
2666
2667
|
const query = Array.isArray(queryArg) ? queryArg.join(" ") : queryArg ?? "";
|
|
2667
2668
|
if (!query.trim()) {
|
|
@@ -2678,7 +2679,7 @@ async function docsSearchAction(queryArg, options, deps) {
|
|
|
2678
2679
|
}
|
|
2679
2680
|
const packages = parsePackageList(options.packages);
|
|
2680
2681
|
const limit = options.limit ? Number.parseInt(options.limit, 10) : 25;
|
|
2681
|
-
const mode = toSearchMode(options.mode);
|
|
2682
|
+
const mode = options.mode ? toSearchMode(options.mode) : defaultMode;
|
|
2682
2683
|
const useColors = shouldUseColors(options.noColor);
|
|
2683
2684
|
const result = await pkgseerService.combinedSearch(packages, query, {
|
|
2684
2685
|
mode,
|
|
@@ -2713,36 +2714,79 @@ Searches both code (functions, classes) and documentation pages
|
|
|
2713
2714
|
using the combined search endpoint. Returns results with snippets
|
|
2714
2715
|
showing matches.
|
|
2715
2716
|
|
|
2717
|
+
Package format: [registry:]name[@version]
|
|
2718
|
+
|
|
2716
2719
|
Examples:
|
|
2717
2720
|
# Search a single package (code + docs)
|
|
2718
|
-
pkgseer
|
|
2721
|
+
pkgseer search "error handling" -P express
|
|
2719
2722
|
|
|
2720
2723
|
# Search code only
|
|
2721
|
-
pkgseer
|
|
2724
|
+
pkgseer search "Router.use" -P express --mode code
|
|
2722
2725
|
|
|
2723
2726
|
# Search docs only
|
|
2724
|
-
pkgseer
|
|
2727
|
+
pkgseer search "middleware" -P express --mode docs
|
|
2725
2728
|
|
|
2726
2729
|
# Search multiple packages
|
|
2727
|
-
pkgseer
|
|
2730
|
+
pkgseer search "auth" -P express,passport
|
|
2728
2731
|
|
|
2729
2732
|
# With registry prefix (for non-npm packages)
|
|
2730
|
-
pkgseer
|
|
2733
|
+
pkgseer search "orm" -P pypi:django,pypi:sqlalchemy
|
|
2731
2734
|
|
|
2732
2735
|
# With specific version
|
|
2733
|
-
pkgseer
|
|
2736
|
+
pkgseer search "routing" -P express@4.18.2
|
|
2734
2737
|
|
|
2735
2738
|
# Output for piping
|
|
2736
|
-
pkgseer
|
|
2739
|
+
pkgseer search "routing" -P express --refs-only
|
|
2737
2740
|
|
|
2738
2741
|
# JSON output
|
|
2739
|
-
pkgseer
|
|
2742
|
+
pkgseer search "error" -P express --json`;
|
|
2740
2743
|
function addSearchOptions(cmd) {
|
|
2741
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");
|
|
2742
2745
|
}
|
|
2743
|
-
function
|
|
2744
|
-
const cmd = program.command("search [query...]").summary("Search code and documentation").description(SEARCH_DESCRIPTION);
|
|
2746
|
+
function registerSearchCommand(program) {
|
|
2747
|
+
const cmd = program.command("search [query...]").summary("Search code and documentation across packages").description(SEARCH_DESCRIPTION);
|
|
2745
2748
|
addSearchOptions(cmd).action(async (query, options) => {
|
|
2749
|
+
await withCliErrorHandling(options.json ?? false, async () => {
|
|
2750
|
+
const deps = await createContainer();
|
|
2751
|
+
await searchAction(query, options, deps);
|
|
2752
|
+
});
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2755
|
+
|
|
2756
|
+
// src/commands/docs/search.ts
|
|
2757
|
+
async function docsSearchAction(queryArg, options, deps) {
|
|
2758
|
+
await searchAction(queryArg, options, deps, "DOCS");
|
|
2759
|
+
}
|
|
2760
|
+
var DOCS_SEARCH_DESCRIPTION = `Search documentation across packages.
|
|
2761
|
+
|
|
2762
|
+
Searches documentation pages in package docs. For searching both
|
|
2763
|
+
code and docs, use the top-level 'pkgseer search' command instead.
|
|
2764
|
+
|
|
2765
|
+
Package format: [registry:]name[@version]
|
|
2766
|
+
|
|
2767
|
+
Examples:
|
|
2768
|
+
# Search documentation
|
|
2769
|
+
pkgseer docs search "middleware" -P express
|
|
2770
|
+
|
|
2771
|
+
# Search multiple packages
|
|
2772
|
+
pkgseer docs search "auth" -P express,passport
|
|
2773
|
+
|
|
2774
|
+
# With registry prefix (for non-npm packages)
|
|
2775
|
+
pkgseer docs search "orm" -P pypi:django,pypi:sqlalchemy
|
|
2776
|
+
|
|
2777
|
+
# With specific version
|
|
2778
|
+
pkgseer docs search "routing" -P express@4.18.2
|
|
2779
|
+
|
|
2780
|
+
# JSON output
|
|
2781
|
+
pkgseer docs search "error" -P express --json
|
|
2782
|
+
|
|
2783
|
+
Note: For code search, use 'pkgseer search --mode code' instead.`;
|
|
2784
|
+
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");
|
|
2786
|
+
}
|
|
2787
|
+
function registerDocsSearchCommand(program) {
|
|
2788
|
+
const cmd = program.command("search [query...]").summary("Search documentation").description(DOCS_SEARCH_DESCRIPTION);
|
|
2789
|
+
addDocsSearchOptions(cmd).action(async (query, options) => {
|
|
2746
2790
|
await withCliErrorHandling(options.json ?? false, async () => {
|
|
2747
2791
|
const deps = await createContainer();
|
|
2748
2792
|
await docsSearchAction(query, options, deps);
|
|
@@ -3276,7 +3320,7 @@ function shouldIgnorePath(relativePath, patterns) {
|
|
|
3276
3320
|
return ignored;
|
|
3277
3321
|
}
|
|
3278
3322
|
function shouldIgnoreDirectory(relativeDirPath, patterns) {
|
|
3279
|
-
const normalized = relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "")
|
|
3323
|
+
const normalized = `${relativeDirPath.replace(/\\/g, "/").replace(/\/$/, "")}/`;
|
|
3280
3324
|
return shouldIgnorePath(normalized, patterns);
|
|
3281
3325
|
}
|
|
3282
3326
|
|
|
@@ -3309,6 +3353,10 @@ var MANIFEST_TYPES = [
|
|
|
3309
3353
|
{
|
|
3310
3354
|
type: "hex",
|
|
3311
3355
|
filenames: ["mix.exs", "mix.lock"]
|
|
3356
|
+
},
|
|
3357
|
+
{
|
|
3358
|
+
type: "crates",
|
|
3359
|
+
filenames: ["Cargo.toml", "Cargo.lock"]
|
|
3312
3360
|
}
|
|
3313
3361
|
];
|
|
3314
3362
|
function suggestLabel(relativePath) {
|
|
@@ -3317,7 +3365,7 @@ function suggestLabel(relativePath) {
|
|
|
3317
3365
|
if (parts.length === 1) {
|
|
3318
3366
|
return "root";
|
|
3319
3367
|
}
|
|
3320
|
-
return parts[parts.length - 2];
|
|
3368
|
+
return parts[parts.length - 2] ?? "root";
|
|
3321
3369
|
}
|
|
3322
3370
|
async function scanDirectoryRecursive(directory, fs, rootDir, options, currentDepth = 0, gitignorePatterns = null) {
|
|
3323
3371
|
const detected = [];
|
|
@@ -3383,12 +3431,12 @@ function filterRedundantPackageJson(manifests) {
|
|
|
3383
3431
|
if (!dirToManifests.has(dir)) {
|
|
3384
3432
|
dirToManifests.set(dir, []);
|
|
3385
3433
|
}
|
|
3386
|
-
dirToManifests.get(dir)
|
|
3434
|
+
dirToManifests.get(dir)?.push(manifest);
|
|
3387
3435
|
}
|
|
3388
3436
|
const filtered = [];
|
|
3389
|
-
for (const [
|
|
3437
|
+
for (const [_dir, dirManifests] of dirToManifests.entries()) {
|
|
3390
3438
|
const hasLockFile = dirManifests.some((m) => m.filename === "package-lock.json");
|
|
3391
|
-
const
|
|
3439
|
+
const _hasPackageJson = dirManifests.some((m) => m.filename === "package.json");
|
|
3392
3440
|
for (const manifest of dirManifests) {
|
|
3393
3441
|
if (manifest.filename === "package.json" && hasLockFile) {
|
|
3394
3442
|
continue;
|
|
@@ -3869,50 +3917,55 @@ Search and analyze packages across npm, PyPI, and Hex. All commands support \`--
|
|
|
3869
3917
|
|
|
3870
3918
|
\`\`\`bash
|
|
3871
3919
|
# Search code and docs across packages
|
|
3872
|
-
${invocation} search "<query>" -P lodash,express
|
|
3873
|
-
${invocation} search "<query>" -P requests
|
|
3874
|
-
${invocation} search "authentication" -P phoenix,plug
|
|
3920
|
+
${invocation} search "<query>" -P lodash,express # npm packages
|
|
3921
|
+
${invocation} search "<query>" -P pypi:requests # PyPI packages
|
|
3922
|
+
${invocation} search "authentication" -P hex:phoenix,hex:plug
|
|
3875
3923
|
|
|
3876
3924
|
# Search modes
|
|
3877
|
-
${invocation} search "<query>" -P <packages> --code # Code only
|
|
3878
|
-
${invocation} search "<query>" -P <packages> --docs # Docs only
|
|
3925
|
+
${invocation} search "<query>" -P <packages> --mode code # Code only
|
|
3926
|
+
${invocation} search "<query>" -P <packages> --mode docs # Docs only
|
|
3879
3927
|
|
|
3880
|
-
#
|
|
3881
|
-
${invocation} docs search "<query>"
|
|
3928
|
+
# Docs-only search (shorthand)
|
|
3929
|
+
${invocation} docs search "<query>" -P <packages>
|
|
3882
3930
|
\`\`\`
|
|
3883
3931
|
|
|
3884
3932
|
## Package Analysis
|
|
3885
3933
|
|
|
3934
|
+
Package format: \`[registry:]name[@version]\`
|
|
3935
|
+
|
|
3886
3936
|
\`\`\`bash
|
|
3887
3937
|
# Overview: metadata, versions, quickstart
|
|
3888
|
-
${invocation} pkg info
|
|
3938
|
+
${invocation} pkg info lodash
|
|
3939
|
+
${invocation} pkg info pypi:requests
|
|
3889
3940
|
|
|
3890
3941
|
# Quality score (0-100) with category breakdown
|
|
3891
|
-
${invocation} pkg quality
|
|
3942
|
+
${invocation} pkg quality express@4.18.0
|
|
3943
|
+
${invocation} pkg quality pypi:django@4.2
|
|
3892
3944
|
|
|
3893
3945
|
# Security: CVEs, severity, upgrade paths
|
|
3894
|
-
${invocation} pkg vulns
|
|
3946
|
+
${invocation} pkg vulns lodash@4.17.21
|
|
3895
3947
|
|
|
3896
3948
|
# Dependencies: direct, transitive, tree view
|
|
3897
|
-
${invocation} pkg deps
|
|
3949
|
+
${invocation} pkg deps express --transitive
|
|
3898
3950
|
|
|
3899
3951
|
# Compare up to 10 packages
|
|
3900
3952
|
${invocation} pkg compare lodash underscore ramda
|
|
3901
|
-
${invocation} pkg compare
|
|
3953
|
+
${invocation} pkg compare axios pypi:httpx # cross-registry
|
|
3902
3954
|
\`\`\`
|
|
3903
3955
|
|
|
3904
3956
|
## Documentation
|
|
3905
3957
|
|
|
3906
3958
|
\`\`\`bash
|
|
3907
|
-
${invocation} docs list
|
|
3908
|
-
${invocation} docs get <package>/<page-id>
|
|
3959
|
+
${invocation} docs list pypi:requests # List pages
|
|
3960
|
+
${invocation} docs get <package>/<page-id> # Fetch content
|
|
3961
|
+
${invocation} docs search "<query>" -P <packages> # Search docs only
|
|
3909
3962
|
\`\`\`
|
|
3910
3963
|
|
|
3911
3964
|
## Tips
|
|
3912
3965
|
|
|
3913
|
-
-
|
|
3966
|
+
- Package format: \`[registry:]name[@version]\` (e.g., \`pypi:django@4.2\`)
|
|
3967
|
+
- Default registry: npm
|
|
3914
3968
|
- Use \`--json\` for structured output when parsing
|
|
3915
|
-
- Version defaults to latest; use \`-v\` for specific
|
|
3916
3969
|
`;
|
|
3917
3970
|
}
|
|
3918
3971
|
function extractSkillVersion(content) {
|
|
@@ -4247,9 +4300,9 @@ Project already configured: ${highlight(existingConfig.config.project, useColors
|
|
|
4247
4300
|
setupProject = false;
|
|
4248
4301
|
} else {
|
|
4249
4302
|
console.log(`
|
|
4250
|
-
|
|
4303
|
+
${"=".repeat(50)}`);
|
|
4251
4304
|
console.log("Project Configuration Setup");
|
|
4252
|
-
console.log("=".repeat(50)
|
|
4305
|
+
console.log(`${"=".repeat(50)}
|
|
4253
4306
|
`);
|
|
4254
4307
|
await projectInit({}, {
|
|
4255
4308
|
projectService: deps.projectService,
|
|
@@ -4270,9 +4323,9 @@ ${highlight("✓", useColors)} Project setup complete!
|
|
|
4270
4323
|
}
|
|
4271
4324
|
if (aiIntegration === "skill") {
|
|
4272
4325
|
console.log(`
|
|
4273
|
-
|
|
4326
|
+
${"=".repeat(50)}`);
|
|
4274
4327
|
console.log("Skill Setup");
|
|
4275
|
-
console.log("=".repeat(50)
|
|
4328
|
+
console.log(`${"=".repeat(50)}
|
|
4276
4329
|
`);
|
|
4277
4330
|
await skillInit({
|
|
4278
4331
|
fileSystemService: deps.fileSystemService,
|
|
@@ -4286,9 +4339,9 @@ ${highlight("✓", useColors)} Skill setup complete!
|
|
|
4286
4339
|
const currentConfig = await deps.configService.loadProjectConfig();
|
|
4287
4340
|
const hasProjectNow = currentConfig?.config.project !== undefined;
|
|
4288
4341
|
console.log(`
|
|
4289
|
-
|
|
4342
|
+
${"=".repeat(50)}`);
|
|
4290
4343
|
console.log("MCP Server Setup");
|
|
4291
|
-
console.log("=".repeat(50)
|
|
4344
|
+
console.log(`${"=".repeat(50)}
|
|
4292
4345
|
`);
|
|
4293
4346
|
await mcpInit({
|
|
4294
4347
|
fileSystemService: deps.fileSystemService,
|
|
@@ -4304,7 +4357,7 @@ ${highlight("✓", useColors)} MCP setup complete!
|
|
|
4304
4357
|
}
|
|
4305
4358
|
console.log("=".repeat(50));
|
|
4306
4359
|
console.log("Setup Complete!");
|
|
4307
|
-
console.log("=".repeat(50)
|
|
4360
|
+
console.log(`${"=".repeat(50)}
|
|
4308
4361
|
`);
|
|
4309
4362
|
if (setupProject) {
|
|
4310
4363
|
const finalConfig = await deps.configService.loadProjectConfig();
|
|
@@ -4345,8 +4398,7 @@ function showCliUsage(useColors) {
|
|
|
4345
4398
|
console.log(` ${highlight("pkgseer docs get <pkg>/<page>", useColors)} Fetch doc content`);
|
|
4346
4399
|
console.log(` ${highlight("pkgseer docs search <query>", useColors)} Search documentation
|
|
4347
4400
|
`);
|
|
4348
|
-
console.log(dim(
|
|
4349
|
-
` + "Tip: Run 'pkgseer quickstart' for a quick reference guide.", useColors));
|
|
4401
|
+
console.log(dim("All commands support --json for structured output.", useColors));
|
|
4350
4402
|
}
|
|
4351
4403
|
function registerInitCommand(program) {
|
|
4352
4404
|
program.command("init").summary("Set up project and AI integration").description(`Set up PkgSeer for your project.
|
|
@@ -4415,9 +4467,11 @@ async function loginAction(options, deps) {
|
|
|
4415
4467
|
let callback;
|
|
4416
4468
|
try {
|
|
4417
4469
|
callback = await Promise.race([serverPromise, timeoutPromise]);
|
|
4418
|
-
|
|
4470
|
+
if (timeoutId)
|
|
4471
|
+
clearTimeout(timeoutId);
|
|
4419
4472
|
} catch (error2) {
|
|
4420
|
-
|
|
4473
|
+
if (timeoutId)
|
|
4474
|
+
clearTimeout(timeoutId);
|
|
4421
4475
|
if (error2 instanceof Error) {
|
|
4422
4476
|
console.log(`${error2.message}.
|
|
4423
4477
|
`);
|
|
@@ -4534,12 +4588,13 @@ function toGraphQLRegistry2(registry) {
|
|
|
4534
4588
|
const map = {
|
|
4535
4589
|
npm: "NPM",
|
|
4536
4590
|
pypi: "PYPI",
|
|
4537
|
-
hex: "HEX"
|
|
4591
|
+
hex: "HEX",
|
|
4592
|
+
crates: "CRATES"
|
|
4538
4593
|
};
|
|
4539
4594
|
return map[registry.toLowerCase()] || "NPM";
|
|
4540
4595
|
}
|
|
4541
4596
|
var schemas = {
|
|
4542
|
-
registry: z2.enum(["npm", "pypi", "hex"]).describe("Package registry (npm, pypi, or
|
|
4597
|
+
registry: z2.enum(["npm", "pypi", "hex", "crates"]).describe("Package registry (npm, pypi, hex, or crates)"),
|
|
4543
4598
|
packageName: z2.string().max(255).describe("Name of the package"),
|
|
4544
4599
|
version: z2.string().max(100).optional().describe("Specific version (defaults to latest)")
|
|
4545
4600
|
};
|
|
@@ -4582,7 +4637,7 @@ function notFoundError(packageName, registry) {
|
|
|
4582
4637
|
|
|
4583
4638
|
// src/tools/compare-packages.ts
|
|
4584
4639
|
var packageInputSchema = z3.object({
|
|
4585
|
-
registry: z3.enum(["npm", "pypi", "hex"]),
|
|
4640
|
+
registry: z3.enum(["npm", "pypi", "hex", "crates"]),
|
|
4586
4641
|
name: z3.string().max(255),
|
|
4587
4642
|
version: z3.string().max(100).optional()
|
|
4588
4643
|
});
|
|
@@ -4592,7 +4647,7 @@ var argsSchema = {
|
|
|
4592
4647
|
function createComparePackagesTool(pkgseerService) {
|
|
4593
4648
|
return {
|
|
4594
4649
|
name: "compare_packages",
|
|
4595
|
-
description: "Compare 2-10 packages side-by-side. Use this when evaluating alternatives (e.g., react vs preact vs solid-js). " + "Returns for each package: quality score, download counts, vulnerability count, license, and latest version. " + "Supports cross-registry comparison (npm, pypi, hex). " + 'Format: [{"registry":"npm","name":"lodash"},{"registry":"npm","name":"underscore"}]',
|
|
4650
|
+
description: "Compare 2-10 packages side-by-side. Use this when evaluating alternatives (e.g., react vs preact vs solid-js). " + "Returns for each package: quality score, download counts, vulnerability count, license, and latest version. " + "Supports cross-registry comparison (npm, pypi, hex, crates). " + 'Format: [{"registry":"npm","name":"lodash"},{"registry":"npm","name":"underscore"}]',
|
|
4596
4651
|
schema: argsSchema,
|
|
4597
4652
|
handler: async ({ packages }, _extra) => {
|
|
4598
4653
|
return withErrorHandling("compare packages", async () => {
|
|
@@ -5125,7 +5180,7 @@ function showMcpSetupInstructions(deps) {
|
|
|
5125
5180
|
console.log(` ${highlight(`${deps.baseUrl}/docs/mcp-server`, useColors)}
|
|
5126
5181
|
`);
|
|
5127
5182
|
console.log("Alternative: Use CLI directly (no MCP setup needed)");
|
|
5128
|
-
console.log(` ${highlight("pkgseer
|
|
5183
|
+
console.log(` ${highlight("pkgseer pkg info <package>", useColors)}`);
|
|
5129
5184
|
}
|
|
5130
5185
|
function registerMcpCommand(program) {
|
|
5131
5186
|
const mcpCommand = program.command("mcp").summary("Show setup instructions or start MCP server").description(`Start the Model Context Protocol (MCP) server using STDIO transport.
|
|
@@ -5155,27 +5210,6 @@ in MCP configuration files. Use 'pkgseer mcp' for interactive setup.`).action(as
|
|
|
5155
5210
|
}
|
|
5156
5211
|
|
|
5157
5212
|
// src/commands/pkg/compare.ts
|
|
5158
|
-
function parsePackageSpec(spec) {
|
|
5159
|
-
let registry = "npm";
|
|
5160
|
-
let rest = spec;
|
|
5161
|
-
if (spec.includes(":")) {
|
|
5162
|
-
const colonIndex = spec.indexOf(":");
|
|
5163
|
-
const potentialRegistry = spec.slice(0, colonIndex).toLowerCase();
|
|
5164
|
-
if (["npm", "pypi", "hex"].includes(potentialRegistry)) {
|
|
5165
|
-
registry = potentialRegistry;
|
|
5166
|
-
rest = spec.slice(colonIndex + 1);
|
|
5167
|
-
}
|
|
5168
|
-
}
|
|
5169
|
-
const atIndex = rest.lastIndexOf("@");
|
|
5170
|
-
if (atIndex > 0) {
|
|
5171
|
-
return {
|
|
5172
|
-
registry,
|
|
5173
|
-
name: rest.slice(0, atIndex),
|
|
5174
|
-
version: rest.slice(atIndex + 1)
|
|
5175
|
-
};
|
|
5176
|
-
}
|
|
5177
|
-
return { registry, name: rest };
|
|
5178
|
-
}
|
|
5179
5213
|
function formatPackageComparison(comparison) {
|
|
5180
5214
|
const lines = [];
|
|
5181
5215
|
lines.push("\uD83D\uDCCA Package Comparison");
|
|
@@ -5228,10 +5262,10 @@ async function pkgCompareAction(packages, options, deps) {
|
|
|
5228
5262
|
if (options.json) {
|
|
5229
5263
|
const pkgs = result.data.comparePackages.packages?.filter((p) => p) ?? [];
|
|
5230
5264
|
const slim = pkgs.map((p) => ({
|
|
5231
|
-
package: `${p
|
|
5232
|
-
quality: p
|
|
5233
|
-
downloads: p
|
|
5234
|
-
vulnerabilities: p
|
|
5265
|
+
package: `${p?.packageName}@${p?.version}`,
|
|
5266
|
+
quality: p?.quality?.score,
|
|
5267
|
+
downloads: p?.downloadsLastMonth,
|
|
5268
|
+
vulnerabilities: p?.vulnerabilityCount
|
|
5235
5269
|
}));
|
|
5236
5270
|
output(slim, true);
|
|
5237
5271
|
} else {
|
|
@@ -5296,14 +5330,16 @@ function formatPackageDependencies(data, transitiveRequested) {
|
|
|
5296
5330
|
return lines.join(`
|
|
5297
5331
|
`);
|
|
5298
5332
|
}
|
|
5299
|
-
async function pkgDepsAction(
|
|
5333
|
+
async function pkgDepsAction(packageArg, options, deps) {
|
|
5300
5334
|
const { pkgseerService } = deps;
|
|
5301
|
-
const
|
|
5335
|
+
const parsed = parsePackageSpec(packageArg);
|
|
5336
|
+
const registry = toGraphQLRegistry(parsed.registry !== "npm" ? parsed.registry : options.registry);
|
|
5337
|
+
const version2 = parsed.version ?? options.pkgVersion;
|
|
5302
5338
|
const transitiveRequested = options.transitive ?? false;
|
|
5303
|
-
const result = await pkgseerService.cliPackageDeps(registry,
|
|
5339
|
+
const result = await pkgseerService.cliPackageDeps(registry, parsed.name, version2, options.transitive, options.maxDepth ? Number.parseInt(options.maxDepth, 10) : undefined);
|
|
5304
5340
|
handleErrors(result.errors, options.json ?? false);
|
|
5305
5341
|
if (!result.data.packageDependencies) {
|
|
5306
|
-
outputError(`Package not found: ${
|
|
5342
|
+
outputError(`Package not found: ${parsed.name} in ${parsed.registry}`, options.json ?? false);
|
|
5307
5343
|
return;
|
|
5308
5344
|
}
|
|
5309
5345
|
const format = options.json ? "json" : options.format ?? "human";
|
|
@@ -5342,12 +5378,14 @@ var DEPS_DESCRIPTION = `Get package dependencies.
|
|
|
5342
5378
|
Lists direct dependencies and shows version constraints and
|
|
5343
5379
|
dependency types (runtime, dev, optional).
|
|
5344
5380
|
|
|
5381
|
+
Package format: [registry:]name[@version]
|
|
5382
|
+
|
|
5345
5383
|
Examples:
|
|
5346
5384
|
pkgseer pkg deps express
|
|
5347
|
-
pkgseer pkg deps lodash --transitive
|
|
5348
|
-
pkgseer pkg deps requests --
|
|
5385
|
+
pkgseer pkg deps lodash@4.17.21 --transitive
|
|
5386
|
+
pkgseer pkg deps pypi:requests@2.28.0 --json`;
|
|
5349
5387
|
function registerPkgDepsCommand(program) {
|
|
5350
|
-
program.command("deps <package>").summary("Get package dependencies").description(DEPS_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-
|
|
5388
|
+
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) => {
|
|
5351
5389
|
await withCliErrorHandling(options.json ?? false, async () => {
|
|
5352
5390
|
const deps = await createContainer();
|
|
5353
5391
|
await pkgDepsAction(packageName, options, deps);
|
|
@@ -5355,6 +5393,15 @@ function registerPkgDepsCommand(program) {
|
|
|
5355
5393
|
});
|
|
5356
5394
|
}
|
|
5357
5395
|
// src/commands/pkg/info.ts
|
|
5396
|
+
function formatDownloads(count) {
|
|
5397
|
+
if (count >= 1e6) {
|
|
5398
|
+
return `${(count / 1e6).toFixed(1)}M`;
|
|
5399
|
+
}
|
|
5400
|
+
if (count >= 1000) {
|
|
5401
|
+
return `${(count / 1000).toFixed(1)}K`;
|
|
5402
|
+
}
|
|
5403
|
+
return count.toLocaleString();
|
|
5404
|
+
}
|
|
5358
5405
|
function formatPackageSummary(data) {
|
|
5359
5406
|
const lines = [];
|
|
5360
5407
|
const pkg = data.package;
|
|
@@ -5374,6 +5421,11 @@ function formatPackageSummary(data) {
|
|
|
5374
5421
|
["License", pkg.license]
|
|
5375
5422
|
]));
|
|
5376
5423
|
lines.push("");
|
|
5424
|
+
if (pkg.downloadsLastMonth != null && pkg.downloadsLastMonth > 0) {
|
|
5425
|
+
lines.push("Downloads:");
|
|
5426
|
+
lines.push(keyValueTable([["Last Month", formatDownloads(pkg.downloadsLastMonth)]]));
|
|
5427
|
+
lines.push("");
|
|
5428
|
+
}
|
|
5377
5429
|
if (pkg.homepage || pkg.repositoryUrl) {
|
|
5378
5430
|
lines.push("Links:");
|
|
5379
5431
|
const links = [];
|
|
@@ -5396,13 +5448,14 @@ function formatPackageSummary(data) {
|
|
|
5396
5448
|
return lines.join(`
|
|
5397
5449
|
`);
|
|
5398
5450
|
}
|
|
5399
|
-
async function pkgInfoAction(
|
|
5451
|
+
async function pkgInfoAction(packageArg, options, deps) {
|
|
5400
5452
|
const { pkgseerService } = deps;
|
|
5401
|
-
const
|
|
5402
|
-
const
|
|
5453
|
+
const parsed = parsePackageSpec(packageArg);
|
|
5454
|
+
const registry = toGraphQLRegistry(parsed.registry !== "npm" ? parsed.registry : options.registry);
|
|
5455
|
+
const result = await pkgseerService.cliPackageInfo(registry, parsed.name);
|
|
5403
5456
|
handleErrors(result.errors, options.json ?? false);
|
|
5404
5457
|
if (!result.data.packageSummary) {
|
|
5405
|
-
outputError(`Package not found: ${
|
|
5458
|
+
outputError(`Package not found: ${parsed.name} in ${parsed.registry}`, options.json ?? false);
|
|
5406
5459
|
return;
|
|
5407
5460
|
}
|
|
5408
5461
|
if (options.json) {
|
|
@@ -5423,16 +5476,18 @@ async function pkgInfoAction(packageName, options, deps) {
|
|
|
5423
5476
|
}
|
|
5424
5477
|
var INFO_DESCRIPTION = `Get package summary and metadata.
|
|
5425
5478
|
|
|
5426
|
-
Displays
|
|
5479
|
+
Displays information about a package's latest version including:
|
|
5427
5480
|
- Basic metadata (version, license, description)
|
|
5428
5481
|
- Download statistics
|
|
5429
|
-
- Security advisories
|
|
5430
5482
|
- Quick start instructions
|
|
5431
5483
|
|
|
5484
|
+
Note: This command shows the latest version only.
|
|
5485
|
+
For version-specific info, use 'pkg vulns', 'pkg quality', or 'pkg deps'.
|
|
5486
|
+
|
|
5432
5487
|
Examples:
|
|
5433
5488
|
pkgseer pkg info lodash
|
|
5434
|
-
pkgseer pkg info requests
|
|
5435
|
-
pkgseer pkg info phoenix --
|
|
5489
|
+
pkgseer pkg info pypi:requests
|
|
5490
|
+
pkgseer pkg info hex:phoenix --json`;
|
|
5436
5491
|
function registerPkgInfoCommand(program) {
|
|
5437
5492
|
program.command("info <package>").summary("Get package summary and metadata").description(INFO_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("--json", "Output as JSON").action(async (packageName, options) => {
|
|
5438
5493
|
await withCliErrorHandling(options.json ?? false, async () => {
|
|
@@ -5460,13 +5515,15 @@ function formatPackageQuality(data) {
|
|
|
5460
5515
|
return lines.join(`
|
|
5461
5516
|
`);
|
|
5462
5517
|
}
|
|
5463
|
-
async function pkgQualityAction(
|
|
5518
|
+
async function pkgQualityAction(packageArg, options, deps) {
|
|
5464
5519
|
const { pkgseerService } = deps;
|
|
5465
|
-
const
|
|
5466
|
-
const
|
|
5520
|
+
const parsed = parsePackageSpec(packageArg);
|
|
5521
|
+
const registry = toGraphQLRegistry(parsed.registry !== "npm" ? parsed.registry : options.registry);
|
|
5522
|
+
const version2 = parsed.version ?? options.pkgVersion;
|
|
5523
|
+
const result = await pkgseerService.cliPackageQuality(registry, parsed.name, version2);
|
|
5467
5524
|
handleErrors(result.errors, options.json ?? false);
|
|
5468
5525
|
if (!result.data.packageQuality) {
|
|
5469
|
-
outputError(`Package not found: ${
|
|
5526
|
+
outputError(`Package not found: ${parsed.name} in ${parsed.registry}`, options.json ?? false);
|
|
5470
5527
|
return;
|
|
5471
5528
|
}
|
|
5472
5529
|
const format = options.json ? "json" : options.format ?? "human";
|
|
@@ -5477,8 +5534,8 @@ async function pkgQualityAction(packageName, options, deps) {
|
|
|
5477
5534
|
score: quality?.overallScore,
|
|
5478
5535
|
grade: quality?.grade,
|
|
5479
5536
|
categories: quality?.categories?.filter((c) => c).map((c) => ({
|
|
5480
|
-
name: c
|
|
5481
|
-
score: c
|
|
5537
|
+
name: c?.category,
|
|
5538
|
+
score: c?.score
|
|
5482
5539
|
}))
|
|
5483
5540
|
};
|
|
5484
5541
|
output(slim, true);
|
|
@@ -5494,12 +5551,14 @@ Analyzes package quality across multiple dimensions:
|
|
|
5494
5551
|
- Security practices
|
|
5495
5552
|
- Community engagement
|
|
5496
5553
|
|
|
5554
|
+
Package format: [registry:]name[@version]
|
|
5555
|
+
|
|
5497
5556
|
Examples:
|
|
5498
5557
|
pkgseer pkg quality lodash
|
|
5499
|
-
pkgseer pkg quality express
|
|
5500
|
-
pkgseer pkg quality requests --
|
|
5558
|
+
pkgseer pkg quality express@4.18.0
|
|
5559
|
+
pkgseer pkg quality pypi:requests@2.28.0 --json`;
|
|
5501
5560
|
function registerPkgQualityCommand(program) {
|
|
5502
|
-
program.command("quality <package>").summary("Get package quality score").description(QUALITY_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-
|
|
5561
|
+
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) => {
|
|
5503
5562
|
await withCliErrorHandling(options.json ?? false, async () => {
|
|
5504
5563
|
const deps = await createContainer();
|
|
5505
5564
|
await pkgQualityAction(packageName, options, deps);
|
|
@@ -5559,13 +5618,15 @@ function formatPackageVulnerabilities(data) {
|
|
|
5559
5618
|
return lines.join(`
|
|
5560
5619
|
`);
|
|
5561
5620
|
}
|
|
5562
|
-
async function pkgVulnsAction(
|
|
5621
|
+
async function pkgVulnsAction(packageArg, options, deps) {
|
|
5563
5622
|
const { pkgseerService } = deps;
|
|
5564
|
-
const
|
|
5565
|
-
const
|
|
5623
|
+
const parsed = parsePackageSpec(packageArg);
|
|
5624
|
+
const registry = toGraphQLRegistry(parsed.registry !== "npm" ? parsed.registry : options.registry);
|
|
5625
|
+
const version2 = parsed.version ?? options.pkgVersion;
|
|
5626
|
+
const result = await pkgseerService.cliPackageVulns(registry, parsed.name, version2);
|
|
5566
5627
|
handleErrors(result.errors, options.json ?? false);
|
|
5567
5628
|
if (!result.data.packageVulnerabilities) {
|
|
5568
|
-
outputError(`Package not found: ${
|
|
5629
|
+
outputError(`Package not found: ${parsed.name} in ${parsed.registry}`, options.json ?? false);
|
|
5569
5630
|
return;
|
|
5570
5631
|
}
|
|
5571
5632
|
if (options.json) {
|
|
@@ -5575,10 +5636,10 @@ async function pkgVulnsAction(packageName, options, deps) {
|
|
|
5575
5636
|
package: `${data.package?.name}@${data.package?.version}`,
|
|
5576
5637
|
count: data.security?.vulnerabilityCount ?? 0,
|
|
5577
5638
|
vulnerabilities: vulns.filter((v) => v).map((v) => ({
|
|
5578
|
-
id: v
|
|
5579
|
-
severity: v
|
|
5580
|
-
summary: v
|
|
5581
|
-
fixed: v
|
|
5639
|
+
id: v?.osvId,
|
|
5640
|
+
severity: v?.severityScore,
|
|
5641
|
+
summary: v?.summary,
|
|
5642
|
+
fixed: v?.fixedInVersions
|
|
5582
5643
|
}))
|
|
5583
5644
|
};
|
|
5584
5645
|
output(slim, true);
|
|
@@ -5594,12 +5655,14 @@ Scans for known security vulnerabilities and provides:
|
|
|
5594
5655
|
- Affected version ranges
|
|
5595
5656
|
- Upgrade recommendations
|
|
5596
5657
|
|
|
5658
|
+
Package format: [registry:]name[@version]
|
|
5659
|
+
|
|
5597
5660
|
Examples:
|
|
5598
5661
|
pkgseer pkg vulns lodash
|
|
5599
|
-
pkgseer pkg vulns express
|
|
5600
|
-
pkgseer pkg vulns requests --
|
|
5662
|
+
pkgseer pkg vulns express@4.17.0
|
|
5663
|
+
pkgseer pkg vulns pypi:requests@2.28.0 --json`;
|
|
5601
5664
|
function registerPkgVulnsCommand(program) {
|
|
5602
|
-
program.command("vulns <package>").summary("Check for security vulnerabilities").description(VULNS_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-
|
|
5665
|
+
program.command("vulns <package>").summary("Check for security vulnerabilities").description(VULNS_DESCRIPTION).option("-r, --registry <registry>", "Package registry", "npm").option("-V, --pkg-version <version>", "Package version").option("--json", "Output as JSON").action(async (packageName, options) => {
|
|
5603
5666
|
await withCliErrorHandling(options.json ?? false, async () => {
|
|
5604
5667
|
const deps = await createContainer();
|
|
5605
5668
|
await pkgVulnsAction(packageName, options, deps);
|
|
@@ -5617,7 +5680,7 @@ function matchManifestsWithConfig(detectedGroups, existingManifests) {
|
|
|
5617
5680
|
});
|
|
5618
5681
|
}
|
|
5619
5682
|
}
|
|
5620
|
-
const
|
|
5683
|
+
const _labelToFiles = new Map;
|
|
5621
5684
|
const labelToConfig = new Map;
|
|
5622
5685
|
for (const detectedGroup of detectedGroups) {
|
|
5623
5686
|
for (const manifest of detectedGroup.manifests) {
|
|
@@ -5630,13 +5693,14 @@ function matchManifestsWithConfig(detectedGroups, existingManifests) {
|
|
|
5630
5693
|
label = existingConfig?.label ?? detectedGroup.label;
|
|
5631
5694
|
}
|
|
5632
5695
|
const allowMixDeps = existingConfig?.allow_mix_deps;
|
|
5633
|
-
|
|
5634
|
-
|
|
5696
|
+
let config = labelToConfig.get(label);
|
|
5697
|
+
if (!config) {
|
|
5698
|
+
config = {
|
|
5635
5699
|
files: new Set,
|
|
5636
5700
|
allow_mix_deps: allowMixDeps
|
|
5637
|
-
}
|
|
5701
|
+
};
|
|
5702
|
+
labelToConfig.set(label, config);
|
|
5638
5703
|
}
|
|
5639
|
-
const config = labelToConfig.get(label);
|
|
5640
5704
|
config.files.add(manifest.relativePath);
|
|
5641
5705
|
if (allowMixDeps) {
|
|
5642
5706
|
config.allow_mix_deps = true;
|
|
@@ -5673,7 +5737,7 @@ function matchManifestsWithConfig(detectedGroups, existingManifests) {
|
|
|
5673
5737
|
});
|
|
5674
5738
|
}
|
|
5675
5739
|
async function projectDetectAction(options, deps) {
|
|
5676
|
-
const { configService, fileSystemService, promptService
|
|
5740
|
+
const { configService, fileSystemService, promptService } = deps;
|
|
5677
5741
|
const projectConfig = await configService.loadProjectConfig();
|
|
5678
5742
|
if (!projectConfig?.config.project) {
|
|
5679
5743
|
console.error(`✗ No project is configured in pkgseer.yml`);
|
|
@@ -5730,7 +5794,7 @@ Suggested configuration:`);
|
|
|
5730
5794
|
console.log(`${prefix}${file}`);
|
|
5731
5795
|
}
|
|
5732
5796
|
}
|
|
5733
|
-
const
|
|
5797
|
+
const _hasHexManifests = detectedGroups.some((group) => group.manifests.some((m) => m.type === "hex"));
|
|
5734
5798
|
const hasHexInSuggested = suggestedManifests.some((g) => g.files.some((f) => f.endsWith("mix.exs") || f.endsWith("mix.lock")));
|
|
5735
5799
|
let allowMixDeps = false;
|
|
5736
5800
|
if (hasHexInSuggested) {
|
|
@@ -6038,6 +6102,9 @@ Getting started:
|
|
|
6038
6102
|
pkgseer login Authenticate with your account
|
|
6039
6103
|
pkgseer skill init Install AI agent skill
|
|
6040
6104
|
|
|
6105
|
+
Search:
|
|
6106
|
+
pkgseer search <query> -P <packages> Search code and docs
|
|
6107
|
+
|
|
6041
6108
|
Package commands:
|
|
6042
6109
|
pkgseer pkg info <package> Get package summary
|
|
6043
6110
|
pkgseer pkg vulns <package> Check vulnerabilities
|
|
@@ -6048,7 +6115,7 @@ Package commands:
|
|
|
6048
6115
|
Documentation commands:
|
|
6049
6116
|
pkgseer docs list <package> List doc pages
|
|
6050
6117
|
pkgseer docs get <pkg> <page> Fetch a doc page
|
|
6051
|
-
pkgseer docs search <query> Search
|
|
6118
|
+
pkgseer docs search <query> Search docs only
|
|
6052
6119
|
|
|
6053
6120
|
Learn more at https://pkgseer.dev`);
|
|
6054
6121
|
registerInitCommand(program);
|
|
@@ -6056,6 +6123,7 @@ registerMcpCommand(program);
|
|
|
6056
6123
|
registerSkillCommand(program);
|
|
6057
6124
|
registerLoginCommand(program);
|
|
6058
6125
|
registerLogoutCommand(program);
|
|
6126
|
+
registerSearchCommand(program);
|
|
6059
6127
|
var auth = program.command("auth").description("View and manage authentication");
|
|
6060
6128
|
registerAuthStatusCommand(auth);
|
|
6061
6129
|
var config = program.command("config").description("View and manage configuration");
|
package/dist/index.js
CHANGED