npq 3.24.0 → 3.26.0
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 +7 -0
- package/bin/npq-hero.js +116 -100
- package/bin/npq.js +2 -1
- package/lib/cli.js +21 -11
- package/lib/helpers/codingAgentEnvironment.js +44 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -230,6 +230,13 @@ Use `--json` for a non-interactive, audit-only result suitable for CI:
|
|
|
230
230
|
npq install express --json
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
+
When npq detects a supported coding-agent environment, it automatically uses
|
|
234
|
+
the same audit-only JSON mode. This applies to `npq` and to install commands
|
|
235
|
+
intercepted by `npq-hero`; non-install `npq-hero` commands still pass through to
|
|
236
|
+
the configured package manager. See the [full coding-agent detection signal
|
|
237
|
+
table](docs/feature/json-output.md#coding-agent-detection) for the supported
|
|
238
|
+
signals and contextual matchers.
|
|
239
|
+
|
|
233
240
|
JSON mode never invokes the package manager. It exits `0` for a clean audit,
|
|
234
241
|
`1` for findings, and `2` when the audit cannot complete. See
|
|
235
242
|
[JSON audit output](docs/feature/json-output.md).
|
package/bin/npq-hero.js
CHANGED
|
@@ -14,126 +14,142 @@ const { Spinner } = require('../lib/helpers/cliSpinner')
|
|
|
14
14
|
const { promiseThrottleHelper } = require('../lib/helpers/promiseThrottler')
|
|
15
15
|
const RegistryConfig = require('../lib/helpers/registryConfig')
|
|
16
16
|
const RegistryClient = require('../lib/helpers/registryClient')
|
|
17
|
+
const { createJsonOutput } = require('../lib/helpers/jsonOutput')
|
|
18
|
+
const { runJsonCli, writeInvalidJsonInvocation } = require('../lib/jsonCli')
|
|
19
|
+
const { isCodingAgentEnvironment } = require('../lib/helpers/codingAgentEnvironment')
|
|
17
20
|
|
|
18
21
|
const PACKAGE_MANAGER_TOOL = process.env.NPQ_PKG_MGR
|
|
19
22
|
const DISABLE_AUTO_CONTINUE = process.env.NPQ_DISABLE_AUTO_CONTINUE === 'true'
|
|
20
23
|
|
|
21
|
-
const
|
|
24
|
+
const codingAgentEnvironment = isCodingAgentEnvironment()
|
|
25
|
+
let cliArgs
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
try {
|
|
28
|
+
cliArgs = CliParser.parseArgsMinimal({ codingAgentEnvironment })
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (!error || error.npqJsonMode !== true) throw error
|
|
31
|
+
writeInvalidJsonInvocation(createJsonOutput())
|
|
32
|
+
process.exitCode = 2
|
|
33
|
+
}
|
|
24
34
|
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
if (cliArgs && cliArgs.json) {
|
|
36
|
+
runJsonCli(cliArgs, { output: createJsonOutput() })
|
|
37
|
+
} else if (cliArgs) {
|
|
38
|
+
const silentModeNoPackages = !cliArgs || !cliArgs.packages || cliArgs.packages.length === 0
|
|
27
39
|
|
|
28
|
-
|
|
29
|
-
spinner
|
|
30
|
-
|
|
40
|
+
const isInteractive = cliSupport.isInteractiveTerminal() && !silentModeNoPackages
|
|
41
|
+
const spinner = isInteractive ? new Spinner({ text: 'Initiating...' }) : null
|
|
42
|
+
|
|
43
|
+
if (spinner) {
|
|
44
|
+
spinner.start()
|
|
45
|
+
}
|
|
31
46
|
|
|
32
|
-
RegistryConfig.load({ argv: cliArgs.registryConfigArgs || [] })
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
47
|
+
RegistryConfig.load({ argv: cliArgs.registryConfigArgs || [] })
|
|
48
|
+
.then((registryConfig) => {
|
|
49
|
+
const registryClient = new RegistryClient(registryConfig)
|
|
50
|
+
const marshall = new Marshall({
|
|
51
|
+
pkgs: cliArgs.packages,
|
|
52
|
+
registryClient,
|
|
53
|
+
progressManager: spinner,
|
|
54
|
+
promiseThrottleHelper
|
|
55
|
+
})
|
|
56
|
+
return marshall.process()
|
|
40
57
|
})
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
spinner.stop()
|
|
46
|
-
}
|
|
58
|
+
.then((marshallResults) => {
|
|
59
|
+
if (spinner) {
|
|
60
|
+
spinner.stop()
|
|
61
|
+
}
|
|
47
62
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
const results = reportResults(marshallResults)
|
|
64
|
+
if (
|
|
65
|
+
results &&
|
|
66
|
+
Object.hasOwn(results, 'countErrors') &&
|
|
67
|
+
Object.hasOwn(results, 'countWarnings')
|
|
68
|
+
) {
|
|
69
|
+
const { countErrors, countWarnings, countNotEvaluated = 0, useRichFormatting } = results
|
|
70
|
+
const hasFindings = countErrors > 0 || countWarnings > 0
|
|
71
|
+
const hasReportableResults = hasFindings || countNotEvaluated > 0
|
|
57
72
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
73
|
+
if (hasReportableResults) {
|
|
74
|
+
console.log()
|
|
75
|
+
console.log(hasFindings ? 'Packages with issues found:' : 'Package checks not evaluated:')
|
|
61
76
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
77
|
+
if (useRichFormatting) {
|
|
78
|
+
console.log(results.resultsForPrettyPrint)
|
|
79
|
+
console.log(results.summaryForPrettyPrint)
|
|
80
|
+
} else {
|
|
81
|
+
console.log(results.resultsForPlainTextPrint)
|
|
82
|
+
console.log(results.summaryForPlainTextPrint)
|
|
83
|
+
}
|
|
68
84
|
}
|
|
69
|
-
}
|
|
70
85
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
return {
|
|
87
|
+
anyIssues: hasFindings,
|
|
88
|
+
countErrors,
|
|
89
|
+
countWarnings,
|
|
90
|
+
countNotEvaluated
|
|
91
|
+
}
|
|
76
92
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (result && result.countErrors > 0) {
|
|
82
|
-
console.log()
|
|
83
|
-
return cliPrompt.prompt({
|
|
84
|
-
name: 'install',
|
|
85
|
-
message: 'Continue install ?',
|
|
86
|
-
default: false
|
|
87
|
-
})
|
|
88
|
-
} else {
|
|
89
|
-
if (result && result.countWarnings > 0) {
|
|
93
|
+
return undefined
|
|
94
|
+
})
|
|
95
|
+
.then((result) => {
|
|
96
|
+
if (result && result.countErrors > 0) {
|
|
90
97
|
console.log()
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
return cliPrompt.prompt({
|
|
99
|
+
name: 'install',
|
|
100
|
+
message: 'Continue install ?',
|
|
101
|
+
default: false
|
|
102
|
+
})
|
|
103
|
+
} else {
|
|
104
|
+
if (result && result.countWarnings > 0) {
|
|
105
|
+
console.log()
|
|
106
|
+
// Check if auto-continue is disabled via environment variable
|
|
107
|
+
if (DISABLE_AUTO_CONTINUE) {
|
|
108
|
+
return cliPrompt.prompt({
|
|
109
|
+
name: 'install',
|
|
110
|
+
message: 'Continue install ?',
|
|
111
|
+
default: false
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
return cliPrompt.autoContinue({
|
|
94
115
|
name: 'install',
|
|
95
|
-
message: '
|
|
96
|
-
|
|
116
|
+
message: 'Auto-continue with install in... ',
|
|
117
|
+
timeInSeconds: 15
|
|
97
118
|
})
|
|
98
119
|
}
|
|
99
|
-
return cliPrompt.autoContinue({
|
|
100
|
-
name: 'install',
|
|
101
|
-
message: 'Auto-continue with install in... ',
|
|
102
|
-
timeInSeconds: 15
|
|
103
|
-
})
|
|
104
120
|
}
|
|
105
|
-
}
|
|
106
121
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
122
|
+
return { install: true }
|
|
123
|
+
})
|
|
124
|
+
.then((status) => {
|
|
125
|
+
if (
|
|
126
|
+
status &&
|
|
127
|
+
Object.prototype.hasOwnProperty.call(status, 'install') &&
|
|
128
|
+
status.install === true
|
|
129
|
+
) {
|
|
130
|
+
return pkgMgr.process(PACKAGE_MANAGER_TOOL)
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
.then((exitCode) => {
|
|
134
|
+
if (typeof exitCode === 'number') {
|
|
135
|
+
process.exitCode = exitCode
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
.catch((error) => {
|
|
139
|
+
// Ensure errorCode is always a number
|
|
140
|
+
let errorCode = -1
|
|
141
|
+
if (typeof error.code === 'number') {
|
|
142
|
+
errorCode = error.code
|
|
143
|
+
} else if (error.code === 'ABORT_ERR') {
|
|
144
|
+
errorCode = 1
|
|
145
|
+
} else if (error.code === 'USER_ABORT') {
|
|
146
|
+
errorCode = error.exitCode || 1
|
|
147
|
+
}
|
|
133
148
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
CliParser.exit({
|
|
150
|
+
errorCode,
|
|
151
|
+
message: error.message || 'An error occurred',
|
|
152
|
+
spinner
|
|
153
|
+
})
|
|
138
154
|
})
|
|
139
|
-
|
|
155
|
+
}
|
package/bin/npq.js
CHANGED
|
@@ -17,12 +17,13 @@ const { Spinner } = require('../lib/helpers/cliSpinner')
|
|
|
17
17
|
const { promiseThrottleHelper } = require('../lib/helpers/promiseThrottler')
|
|
18
18
|
const { createJsonOutput } = require('../lib/helpers/jsonOutput')
|
|
19
19
|
const { runJsonCli, writeInvalidJsonInvocation } = require('../lib/jsonCli')
|
|
20
|
+
const { isCodingAgentEnvironment } = require('../lib/helpers/codingAgentEnvironment')
|
|
20
21
|
const RegistryConfig = require('../lib/helpers/registryConfig')
|
|
21
22
|
const RegistryClient = require('../lib/helpers/registryClient')
|
|
22
23
|
|
|
23
24
|
const debug = util.debuglog('npq')
|
|
24
25
|
|
|
25
|
-
const jsonRequested = process.argv.slice(2).includes('--json')
|
|
26
|
+
const jsonRequested = process.argv.slice(2).includes('--json') || isCodingAgentEnvironment()
|
|
26
27
|
const jsonOutput = jsonRequested ? createJsonOutput() : null
|
|
27
28
|
let cliArgs
|
|
28
29
|
|
package/lib/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { parseArgs } = require('node:util')
|
|
4
4
|
const npa = require('npm-package-arg')
|
|
5
5
|
const { parseJsonRegistryPackageSpec } = require('./helpers/jsonPackageSpec')
|
|
6
|
+
const { isCodingAgentEnvironment } = require('./helpers/codingAgentEnvironment')
|
|
6
7
|
const pkg = require('../package.json')
|
|
7
8
|
const { parsePackageManagerArguments } = require('./helpers/packageManagerArgumentParser')
|
|
8
9
|
|
|
@@ -132,11 +133,8 @@ curated by Liran Tal at https://github.com/lirantal/npq`)
|
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
// Process install command and packages
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
false,
|
|
138
|
-
values.json === true
|
|
139
|
-
)
|
|
136
|
+
const json = values.json === true || isCodingAgentEnvironment()
|
|
137
|
+
const normalizedPackages = this._extractPackagesFromPositionals(positionals, false, json)
|
|
140
138
|
const installSubcommandExplicit =
|
|
141
139
|
positionals.length > 0 && this.isInstallSubcommand(positionals[0])
|
|
142
140
|
|
|
@@ -145,7 +143,7 @@ curated by Liran Tal at https://github.com/lirantal/npq`)
|
|
|
145
143
|
packageManager: process.env.NPQ_PKG_MGR || values.packageManager || values.pkgMgr || 'npm',
|
|
146
144
|
dryRun: values['dry-run'] || false,
|
|
147
145
|
plain: values.plain || false,
|
|
148
|
-
json
|
|
146
|
+
json,
|
|
149
147
|
disableAutoContinue:
|
|
150
148
|
values['disable-auto-continue'] || process.env.NPQ_DISABLE_AUTO_CONTINUE === 'true',
|
|
151
149
|
registryConfigArgs: this.registryConfigArgs(values),
|
|
@@ -153,19 +151,31 @@ curated by Liran Tal at https://github.com/lirantal/npq`)
|
|
|
153
151
|
}
|
|
154
152
|
}
|
|
155
153
|
|
|
156
|
-
static parseArgsMinimal() {
|
|
154
|
+
static parseArgsMinimal({ codingAgentEnvironment = isCodingAgentEnvironment() } = {}) {
|
|
157
155
|
const packageManager = process.env.NPQ_PKG_MGR || 'npm'
|
|
158
156
|
const { values, positionals } = parsePackageManagerArguments({
|
|
159
157
|
packageManager,
|
|
160
158
|
args: process.argv.slice(2)
|
|
161
159
|
})
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const
|
|
160
|
+
const installSubcommandExplicit =
|
|
161
|
+
positionals.length > 0 && this.isInstallSubcommand(positionals[0])
|
|
162
|
+
const json = codingAgentEnvironment && installSubcommandExplicit
|
|
163
|
+
let normalizedPackages
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
normalizedPackages = this._extractPackagesFromPositionals(positionals, true, json)
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (json && error && typeof error === 'object') {
|
|
169
|
+
Object.defineProperty(error, 'npqJsonMode', { value: true })
|
|
170
|
+
}
|
|
171
|
+
throw error
|
|
172
|
+
}
|
|
165
173
|
|
|
166
174
|
return {
|
|
167
175
|
packages: normalizedPackages,
|
|
168
|
-
registryConfigArgs: this.registryConfigArgs(values)
|
|
176
|
+
registryConfigArgs: this.registryConfigArgs(values),
|
|
177
|
+
installSubcommandExplicit,
|
|
178
|
+
json
|
|
169
179
|
}
|
|
170
180
|
}
|
|
171
181
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const CODING_AGENT_ENVIRONMENT_VARIABLES = Object.freeze([
|
|
4
|
+
'CLAUDECODE',
|
|
5
|
+
'CLAUDE_CODE_CHILD_SESSION',
|
|
6
|
+
'CURSOR_AGENT',
|
|
7
|
+
'PI_CODING_AGENT',
|
|
8
|
+
'CODEX_SANDBOX',
|
|
9
|
+
'CODEX_THREAD_ID',
|
|
10
|
+
'GEMINI_CLI',
|
|
11
|
+
'WINDSURF_AGENT',
|
|
12
|
+
'CODEIUM_AGENT',
|
|
13
|
+
'AGENT',
|
|
14
|
+
'AI_AGENT',
|
|
15
|
+
'CLAUDE_CODE',
|
|
16
|
+
'REPL_ID',
|
|
17
|
+
'OPENCODE',
|
|
18
|
+
'AUGMENT_AGENT',
|
|
19
|
+
'GOOSE_PROVIDER',
|
|
20
|
+
'JUNIE_DATA',
|
|
21
|
+
'JUNIE_SHIM_PATH'
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
function hasNonEmptyValue(env, name) {
|
|
25
|
+
return typeof env[name] === 'string' && env[name].length > 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isCodingAgentEnvironment(env = process.env, { stdoutIsTTY = process.stdout?.isTTY } = {}) {
|
|
29
|
+
if (CODING_AGENT_ENVIRONMENT_VARIABLES.some((name) => hasNonEmptyValue(env, name))) {
|
|
30
|
+
return true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof env.PATH === 'string' && /\.pi[\\/]agent/.test(env.PATH)) {
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof env.EDITOR === 'string' && /devin/i.test(env.EDITOR)) {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return !stdoutIsTTY && typeof env.TERM_PROGRAM === 'string' && /kiro/i.test(env.TERM_PROGRAM)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { CODING_AGENT_ENVIRONMENT_VARIABLES, isCodingAgentEnvironment }
|