@chainpatrol/cli 0.3.1 → 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 CHANGED
@@ -1,5 +1,30 @@
1
1
  # @chainpatrol/cli
2
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
+
3
28
  ## 0.3.1
4
29
 
5
30
  ### Patch Changes
@@ -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 (/usr/bin:/bin:/usr/sbin:/sbin)
101
- that does NOT include /usr/local/bin. Always use the full path to the binary:
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 MUST use
108
- \`/usr/local/bin/chainpatrol\` in all Bash commands.
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-T4DYUWUD.js";
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-LKB72OFY.js");
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-G7LPHKDR.js");
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-ZUZ5MYLI.js");
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-ZUZ5MYLI.js");
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
- const uri = deviceCode.verification_uri_complete ?? deviceCode.verification_uri;
47
- open(uri).catch(() => {
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 is: " }),
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(Text, { children: [
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;
@@ -6,7 +6,7 @@ import {
6
6
  readInstalledSkillVersion,
7
7
  setupSkill,
8
8
  uninstallSkill
9
- } from "./chunk-T4DYUWUD.js";
9
+ } from "./chunk-S7PQNG4E.js";
10
10
  import "./chunk-IUZB3DQW.js";
11
11
  export {
12
12
  getBundledSkillContent,
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.1",
5
+ "version": "0.3.2",
6
6
  "license": "UNLICENSED",
7
7
  "homepage": "https://chainpatrol.com/docs/cli",
8
8
  "keywords": [