@chainpatrol/cli 0.3.0 → 0.3.2
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/CHANGELOG.md +88 -0
- package/dist/{chunk-T4DYUWUD.js → chunk-S7PQNG4E.js} +28 -4
- package/dist/chunk-ZVM45CTB.js +19 -0
- package/dist/cli.js +5 -5
- package/dist/{login-G7LPHKDR.js → login-ML2EKF44.js} +27 -7
- package/dist/{login-json-LKB72OFY.js → login-json-Y3AIQIIB.js} +5 -1
- package/dist/{setup-skill-ZUZ5MYLI.js → setup-skill-XZRLJE3A.js} +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @chainpatrol/cli
|
|
2
|
+
|
|
3
|
+
## 0.3.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 06ff227: `chainpatrol login`:
|
|
8
|
+
|
|
9
|
+
- Skip the browser auto-open when running in a headless / cloud context
|
|
10
|
+
(Claude Code on the web, GitHub Codespaces, Gitpod, Replit, CI, plain
|
|
11
|
+
SSH sessions, or Linux without a display server). Detection is opt-in
|
|
12
|
+
overridable with `CHAINPATROL_HEADLESS=1` / `CHAINPATROL_HEADLESS=0`.
|
|
13
|
+
- Print `verification_uri_complete` as the primary, one-step link when
|
|
14
|
+
the server returns it, so a copy-paste to another device (e.g. a
|
|
15
|
+
phone) works without separately typing the user code. The bare
|
|
16
|
+
`verification_uri` + code is shown as a fallback.
|
|
17
|
+
- `login --json` output now includes a `headless` boolean so automation
|
|
18
|
+
can adapt the way it presents the URL.
|
|
19
|
+
|
|
20
|
+
Claude Code skill bundled with the CLI:
|
|
21
|
+
|
|
22
|
+
- Stop hard-coding `/usr/local/bin/chainpatrol`. The "Running the CLI"
|
|
23
|
+
section now documents typical local vs cloud install locations
|
|
24
|
+
(e.g. `/opt/node*/bin/chainpatrol`), includes a command to discover
|
|
25
|
+
the binary path, and tells the assistant to substitute the resolved
|
|
26
|
+
full path in all invocations.
|
|
27
|
+
|
|
28
|
+
## 0.3.1
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- ea3c3dc: Switch the release pipeline from `changeset publish` to
|
|
33
|
+
`yarn workspaces foreach -A --no-private npm publish --tolerate-republish`
|
|
34
|
+
followed by `changeset tag`. `yarn npm publish` expands Yarn 4's
|
|
35
|
+
`catalog:` and `workspace:` protocols to concrete versions at pack time,
|
|
36
|
+
so the published `package.json` no longer leaks `"react": "catalog:"`
|
|
37
|
+
and `"zod": "catalog:"` (which npm cannot resolve — `npm i -g
|
|
38
|
+
@chainpatrol/cli` was previously failing with `EUNSUPPORTEDPROTOCOL`).
|
|
39
|
+
|
|
40
|
+
`--tolerate-republish` makes the publish step idempotent on re-runs.
|
|
41
|
+
`changeset tag` continues to create per-package git tags after a
|
|
42
|
+
successful publish, so the rest of the release flow (and tag-driven
|
|
43
|
+
workflows like extension uploads) is unchanged.
|
|
44
|
+
|
|
45
|
+
## 0.3.0
|
|
46
|
+
|
|
47
|
+
### Minor Changes
|
|
48
|
+
|
|
49
|
+
- fb27d31: Add version awareness to the CLI, plus an Organization HealthCheck Guide
|
|
50
|
+
in the Claude Code skill:
|
|
51
|
+
- Skill now includes an Organization HealthCheck Guide walking through the
|
|
52
|
+
full detection → reviewing → blocklisting → takedowns pipeline, so the
|
|
53
|
+
assistant can proactively look for what may be wrong for a given org
|
|
54
|
+
(config gaps, detection spikes/drops, review backlogs, auto-approve
|
|
55
|
+
spikes, Google Safe Browsing errors, stuck or cancelled takedowns,
|
|
56
|
+
automated takedowns disabled for too long, etc.). Skill description
|
|
57
|
+
expanded so phrasings like "org healthcheck", "audit my org", or
|
|
58
|
+
"what's wrong with org X" trigger the skill.
|
|
59
|
+
- Embed a `version` field in the Claude Code skill frontmatter (matches the
|
|
60
|
+
CLI package version) so the installed skill can be diffed against the
|
|
61
|
+
bundled one.
|
|
62
|
+
- On every command, run two lightweight version checks (skill freshness +
|
|
63
|
+
npm registry freshness) in parallel with the command, and print a one-line
|
|
64
|
+
stderr nudge if either is out of date. First-time users with no skill
|
|
65
|
+
installed are prompted to run `chainpatrol setup`.
|
|
66
|
+
- The npm check is throttled to once per 24 hours, capped at a 1.5s timeout,
|
|
67
|
+
and skipped in JSON / quiet modes. Set `CHAINPATROL_NO_UPDATE_CHECK=1` to
|
|
68
|
+
silence both checks.
|
|
69
|
+
- Fix the `--version` flag to read from `package.json` (was hardcoded to
|
|
70
|
+
`0.1.0`).
|
|
71
|
+
|
|
72
|
+
## 0.2.2
|
|
73
|
+
|
|
74
|
+
### Patch Changes
|
|
75
|
+
|
|
76
|
+
- bump patch to test publishing workflow
|
|
77
|
+
|
|
78
|
+
## 0.2.1
|
|
79
|
+
|
|
80
|
+
### Patch Changes
|
|
81
|
+
|
|
82
|
+
- bump ink version
|
|
83
|
+
|
|
84
|
+
## 0.2.0
|
|
85
|
+
|
|
86
|
+
### Minor Changes
|
|
87
|
+
|
|
88
|
+
- 32986bf: Various improvements and fixes to the ChainPatrol CLI.
|
|
@@ -97,15 +97,39 @@ platform using the CLI tool.
|
|
|
97
97
|
|
|
98
98
|
## Running the CLI
|
|
99
99
|
|
|
100
|
-
IMPORTANT: Claude Code's sandbox shell has a minimal PATH
|
|
101
|
-
|
|
100
|
+
IMPORTANT: Claude Code's sandbox shell often has a minimal PATH
|
|
101
|
+
(\`/usr/bin:/bin:/usr/sbin:/sbin\`) that may not include the directory where
|
|
102
|
+
\`chainpatrol\` is installed, so bare \`chainpatrol\` calls may fail with
|
|
103
|
+
"command not found". Always invoke the CLI by its full path.
|
|
104
|
+
|
|
105
|
+
The install location depends on the environment:
|
|
106
|
+
|
|
107
|
+
- **Local installs** typically land at \`/usr/local/bin/chainpatrol\` (when
|
|
108
|
+
installed globally via \`npm install -g @chainpatrol/cli\`).
|
|
109
|
+
- **Cloud / sandboxed environments** (e.g. Claude Code on the web, Cursor
|
|
110
|
+
Cloud) often install Node into \`/opt\` and the binary ends up under a
|
|
111
|
+
Node-version-specific path like \`/opt/node22/bin/chainpatrol\`. Variants
|
|
112
|
+
such as \`/opt/node20/bin/chainpatrol\` or \`/opt/node21/bin/chainpatrol\`
|
|
113
|
+
are also possible depending on which Node version is active.
|
|
114
|
+
|
|
115
|
+
To find the binary, try (in order):
|
|
116
|
+
|
|
117
|
+
\`\`\`bash
|
|
118
|
+
command -v chainpatrol \\
|
|
119
|
+
|| ls /usr/local/bin/chainpatrol /opt/node*/bin/chainpatrol 2>/dev/null \\
|
|
120
|
+
| head -n 1
|
|
121
|
+
\`\`\`
|
|
122
|
+
|
|
123
|
+
Then use that full path for every subsequent command, e.g.:
|
|
102
124
|
|
|
103
125
|
\`\`\`bash
|
|
126
|
+
/opt/node22/bin/chainpatrol <command> [options]
|
|
127
|
+
# or
|
|
104
128
|
/usr/local/bin/chainpatrol <command> [options]
|
|
105
129
|
\`\`\`
|
|
106
130
|
|
|
107
|
-
All examples below use the short name for readability, but you
|
|
108
|
-
|
|
131
|
+
All examples below use the short name \`chainpatrol\` for readability, but you
|
|
132
|
+
MUST substitute the full resolved path in your Bash commands.
|
|
109
133
|
|
|
110
134
|
## Available Commands
|
|
111
135
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// src/lib/headless.ts
|
|
2
|
+
function isHeadlessEnv(env = process.env, platform = process.platform) {
|
|
3
|
+
if (env.CHAINPATROL_HEADLESS === "1") return true;
|
|
4
|
+
if (env.CHAINPATROL_HEADLESS === "0") return false;
|
|
5
|
+
if (env.CLAUDE_CODE_REMOTE === "true") return true;
|
|
6
|
+
if (env.CODESPACES === "true") return true;
|
|
7
|
+
if (env.GITPOD_WORKSPACE_ID) return true;
|
|
8
|
+
if (env.REPL_ID) return true;
|
|
9
|
+
if (env.CI === "true") return true;
|
|
10
|
+
if (env.SSH_CONNECTION || env.SSH_TTY) return true;
|
|
11
|
+
if (platform === "linux" && !env.DISPLAY && !env.WAYLAND_DISPLAY && !env.MIR_SOCKET) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
isHeadlessEnv
|
|
19
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
getCliVersion,
|
|
14
14
|
isSkillInstalled,
|
|
15
15
|
readInstalledSkillVersion
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-S7PQNG4E.js";
|
|
17
17
|
import "./chunk-IUZB3DQW.js";
|
|
18
18
|
import {
|
|
19
19
|
DateTime
|
|
@@ -746,11 +746,11 @@ async function main() {
|
|
|
746
746
|
);
|
|
747
747
|
}
|
|
748
748
|
if (jsonMode) {
|
|
749
|
-
const { loginJson } = await import("./login-json-
|
|
749
|
+
const { loginJson } = await import("./login-json-Y3AIQIIB.js");
|
|
750
750
|
await loginJson();
|
|
751
751
|
} else {
|
|
752
752
|
const { render } = await import("ink");
|
|
753
|
-
const { default: Login } = await import("./login-
|
|
753
|
+
const { default: Login } = await import("./login-ML2EKF44.js");
|
|
754
754
|
const { default: React } = await import("react");
|
|
755
755
|
render(React.createElement(Login));
|
|
756
756
|
}
|
|
@@ -1029,12 +1029,12 @@ async function main() {
|
|
|
1029
1029
|
case "setup":
|
|
1030
1030
|
case "install":
|
|
1031
1031
|
case "i": {
|
|
1032
|
-
const { setupSkill } = await import("./setup-skill-
|
|
1032
|
+
const { setupSkill } = await import("./setup-skill-XZRLJE3A.js");
|
|
1033
1033
|
setupSkill({ json: jsonMode });
|
|
1034
1034
|
break;
|
|
1035
1035
|
}
|
|
1036
1036
|
case "uninstall": {
|
|
1037
|
-
const { uninstallSkill } = await import("./setup-skill-
|
|
1037
|
+
const { uninstallSkill } = await import("./setup-skill-XZRLJE3A.js");
|
|
1038
1038
|
uninstallSkill({ json: jsonMode });
|
|
1039
1039
|
break;
|
|
1040
1040
|
}
|
|
@@ -2,6 +2,9 @@ import {
|
|
|
2
2
|
ErrorDisplay,
|
|
3
3
|
Spinner
|
|
4
4
|
} from "./chunk-JCMWDZYY.js";
|
|
5
|
+
import {
|
|
6
|
+
isHeadlessEnv
|
|
7
|
+
} from "./chunk-ZVM45CTB.js";
|
|
5
8
|
import {
|
|
6
9
|
fetchUserEmail,
|
|
7
10
|
getCredentials,
|
|
@@ -43,9 +46,11 @@ function Login() {
|
|
|
43
46
|
setState({ phase: "requesting-code" });
|
|
44
47
|
requestDeviceCode().then((deviceCode) => {
|
|
45
48
|
setState({ phase: "waiting-for-approval", deviceCode });
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
if (!isHeadlessEnv()) {
|
|
50
|
+
const uri = deviceCode.verification_uri_complete ?? deviceCode.verification_uri;
|
|
51
|
+
open(uri).catch(() => {
|
|
52
|
+
});
|
|
53
|
+
}
|
|
49
54
|
}).catch((err) => {
|
|
50
55
|
setState({
|
|
51
56
|
phase: "error",
|
|
@@ -130,19 +135,34 @@ function Login() {
|
|
|
130
135
|
] });
|
|
131
136
|
case "requesting-code":
|
|
132
137
|
return /* @__PURE__ */ jsx(Spinner, { label: "Requesting device code..." });
|
|
133
|
-
case "waiting-for-approval":
|
|
138
|
+
case "waiting-for-approval": {
|
|
139
|
+
const completeUri = state.deviceCode.verification_uri_complete;
|
|
134
140
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 1, children: [
|
|
135
141
|
/* @__PURE__ */ jsxs(Box, { children: [
|
|
136
|
-
/* @__PURE__ */ jsx(Text, { children: "Your code
|
|
142
|
+
/* @__PURE__ */ jsx(Text, { children: "Your code: " }),
|
|
137
143
|
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: state.deviceCode.user_code.length === 8 ? `${state.deviceCode.user_code.slice(0, 4)}-${state.deviceCode.user_code.slice(4)}` : state.deviceCode.user_code })
|
|
138
144
|
] }),
|
|
139
|
-
/* @__PURE__ */ jsxs(
|
|
145
|
+
completeUri ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
146
|
+
/* @__PURE__ */ jsx(Text, { children: "Open this URL on any device to approve in one step:" }),
|
|
147
|
+
/* @__PURE__ */ jsxs(Text, { color: "blue", underline: true, children: [
|
|
148
|
+
" ",
|
|
149
|
+
completeUri
|
|
150
|
+
] }),
|
|
151
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
152
|
+
"Or visit ",
|
|
153
|
+
state.deviceCode.verification_uri,
|
|
154
|
+
" and enter the code above."
|
|
155
|
+
] })
|
|
156
|
+
] }) : /* @__PURE__ */ jsxs(Text, { children: [
|
|
140
157
|
"Open this URL in your browser:",
|
|
141
158
|
" ",
|
|
142
|
-
/* @__PURE__ */ jsx(Text, { color: "blue", underline: true, children: state.deviceCode.verification_uri })
|
|
159
|
+
/* @__PURE__ */ jsx(Text, { color: "blue", underline: true, children: state.deviceCode.verification_uri }),
|
|
160
|
+
" ",
|
|
161
|
+
"and enter the code above."
|
|
143
162
|
] }),
|
|
144
163
|
/* @__PURE__ */ jsx(Spinner, { label: "Waiting for approval..." })
|
|
145
164
|
] });
|
|
165
|
+
}
|
|
146
166
|
case "success":
|
|
147
167
|
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
148
168
|
/* @__PURE__ */ jsx(Text, { color: "green", bold: true, children: "\u2713" }),
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isHeadlessEnv
|
|
3
|
+
} from "./chunk-ZVM45CTB.js";
|
|
1
4
|
import {
|
|
2
5
|
fetchUserEmail,
|
|
3
6
|
getCredentials,
|
|
@@ -24,7 +27,8 @@ async function loginJson() {
|
|
|
24
27
|
user_code: deviceCode.user_code,
|
|
25
28
|
verification_uri: deviceCode.verification_uri,
|
|
26
29
|
verification_uri_complete: deviceCode.verification_uri_complete ?? null,
|
|
27
|
-
expires_in: deviceCode.expires_in
|
|
30
|
+
expires_in: deviceCode.expires_in,
|
|
31
|
+
headless: isHeadlessEnv()
|
|
28
32
|
})
|
|
29
33
|
);
|
|
30
34
|
let interval = deviceCode.interval * 1e3;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@chainpatrol/cli",
|
|
3
3
|
"description": "The official ChainPatrol CLI — terminal interface for threat detection",
|
|
4
4
|
"author": "Umar Ahmed <umar@chainpatrol.io>",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.2",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"homepage": "https://chainpatrol.com/docs/cli",
|
|
8
8
|
"keywords": [
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"ink": "^7.0.1",
|
|
37
37
|
"meow": "^14.1.0",
|
|
38
38
|
"open": "^11.0.0",
|
|
39
|
-
"react": "
|
|
40
|
-
"zod": "
|
|
39
|
+
"react": "^19.2.4",
|
|
40
|
+
"zod": "^3.25.76"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@types/react": "
|
|
43
|
+
"@types/react": "^19.2.7",
|
|
44
44
|
"ink-testing-library": "^4.0.0",
|
|
45
45
|
"msw": "^2.0.0",
|
|
46
46
|
"tsup": "^8.5.0",
|
|
47
|
-
"tsx": "
|
|
48
|
-
"typescript": "
|
|
49
|
-
"vitest": "
|
|
47
|
+
"tsx": "^4.19.4",
|
|
48
|
+
"typescript": "6.0.2",
|
|
49
|
+
"vitest": "^3.2.4"
|
|
50
50
|
}
|
|
51
|
-
}
|
|
51
|
+
}
|