npq 3.24.0 → 3.25.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 CHANGED
@@ -230,6 +230,11 @@ 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 [JSON audit output](docs/feature/json-output.md#coding-agent-detection).
237
+
233
238
  JSON mode never invokes the package manager. It exits `0` for a clean audit,
234
239
  `1` for findings, and `2` when the audit cannot complete. See
235
240
  [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 cliArgs = CliParser.parseArgsMinimal()
24
+ const codingAgentEnvironment = isCodingAgentEnvironment()
25
+ let cliArgs
22
26
 
23
- const silentModeNoPackages = !cliArgs || !cliArgs.packages || cliArgs.packages.length === 0
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
- const isInteractive = cliSupport.isInteractiveTerminal() && !silentModeNoPackages
26
- const spinner = isInteractive ? new Spinner({ text: 'Initiating...' }) : null
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
- if (spinner) {
29
- spinner.start()
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
- .then((registryConfig) => {
34
- const registryClient = new RegistryClient(registryConfig)
35
- const marshall = new Marshall({
36
- pkgs: cliArgs.packages,
37
- registryClient,
38
- progressManager: spinner,
39
- promiseThrottleHelper
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
- return marshall.process()
42
- })
43
- .then((marshallResults) => {
44
- if (spinner) {
45
- spinner.stop()
46
- }
58
+ .then((marshallResults) => {
59
+ if (spinner) {
60
+ spinner.stop()
61
+ }
47
62
 
48
- const results = reportResults(marshallResults)
49
- if (
50
- results &&
51
- Object.hasOwn(results, 'countErrors') &&
52
- Object.hasOwn(results, 'countWarnings')
53
- ) {
54
- const { countErrors, countWarnings, countNotEvaluated = 0, useRichFormatting } = results
55
- const hasFindings = countErrors > 0 || countWarnings > 0
56
- const hasReportableResults = hasFindings || countNotEvaluated > 0
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
- if (hasReportableResults) {
59
- console.log()
60
- console.log(hasFindings ? 'Packages with issues found:' : 'Package checks not evaluated:')
73
+ if (hasReportableResults) {
74
+ console.log()
75
+ console.log(hasFindings ? 'Packages with issues found:' : 'Package checks not evaluated:')
61
76
 
62
- if (useRichFormatting) {
63
- console.log(results.resultsForPrettyPrint)
64
- console.log(results.summaryForPrettyPrint)
65
- } else {
66
- console.log(results.resultsForPlainTextPrint)
67
- console.log(results.summaryForPlainTextPrint)
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
- return {
72
- anyIssues: hasFindings,
73
- countErrors,
74
- countWarnings,
75
- countNotEvaluated
86
+ return {
87
+ anyIssues: hasFindings,
88
+ countErrors,
89
+ countWarnings,
90
+ countNotEvaluated
91
+ }
76
92
  }
77
- }
78
- return undefined
79
- })
80
- .then((result) => {
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
- // Check if auto-continue is disabled via environment variable
92
- if (DISABLE_AUTO_CONTINUE) {
93
- return cliPrompt.prompt({
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: 'Continue install ?',
96
- default: false
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
- return { install: true }
108
- })
109
- .then((status) => {
110
- if (
111
- status &&
112
- Object.prototype.hasOwnProperty.call(status, 'install') &&
113
- status.install === true
114
- ) {
115
- return pkgMgr.process(PACKAGE_MANAGER_TOOL)
116
- }
117
- })
118
- .then((exitCode) => {
119
- if (typeof exitCode === 'number') {
120
- process.exitCode = exitCode
121
- }
122
- })
123
- .catch((error) => {
124
- // Ensure errorCode is always a number
125
- let errorCode = -1
126
- if (typeof error.code === 'number') {
127
- errorCode = error.code
128
- } else if (error.code === 'ABORT_ERR') {
129
- errorCode = 1
130
- } else if (error.code === 'USER_ABORT') {
131
- errorCode = error.exitCode || 1
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
- CliParser.exit({
135
- errorCode,
136
- message: error.message || 'An error occurred',
137
- spinner
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 normalizedPackages = this._extractPackagesFromPositionals(
136
- positionals,
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: values.json || false,
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
- const earlyExitNoInstall = true
164
- const normalizedPackages = this._extractPackagesFromPositionals(positionals, earlyExitNoInstall)
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,23 @@
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
+ ])
16
+
17
+ function isCodingAgentEnvironment(env = process.env) {
18
+ return CODING_AGENT_ENVIRONMENT_VARIABLES.some(
19
+ (name) => typeof env[name] === 'string' && env[name].length > 0
20
+ )
21
+ }
22
+
23
+ module.exports = { CODING_AGENT_ENVIRONMENT_VARIABLES, isCodingAgentEnvironment }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "npq",
3
- "version": "3.24.0",
3
+ "version": "3.25.0",
4
4
  "description": "marshall your npm/npm package installs with high quality and class 🎖",
5
5
  "bin": {
6
6
  "npq": "./bin/npq.js",