cline 3.0.42 → 3.0.43

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
@@ -346,9 +346,24 @@ Desktop-integrated approval mode is also supported via env wiring (`CLINE_TOOL_A
346
346
  - `CLINE_LOG_LEVEL` - Runtime log level (`trace|debug|info|warn|error|fatal|silent`, default `info`)
347
347
  - `CLINE_LOG_PATH` - Runtime log file path (default `<CLINE_DATA_DIR>/logs/cline.log`)
348
348
  - `CLINE_LOG_NAME` - Logger name embedded in runtime log records
349
+ - `CLINE_DEBUG` - Set to `1`/`true` to print wrapper diagnostics (e.g. the CA bundle summary)
349
350
 
350
351
  `--key` takes precedence over environment variables.
351
352
 
353
+ ## Certificate trust
354
+
355
+ The CLI automatically trusts your operating system's certificate store, so it
356
+ works behind corporate TLS-inspecting proxies and with self-signed/internal
357
+ endpoints without any setup. On launch the `cline` wrapper harvests the OS trust
358
+ anchors and writes them to `~/.cline/cli-node-extra-ca-certs.pem`, then points
359
+ the runtime's `NODE_EXTRA_CA_CERTS` at that bundle. The file is regenerated when
360
+ it changes and is safe to delete (it is rebuilt on the next run).
361
+
362
+ If you set `NODE_EXTRA_CA_CERTS` yourself, your certificates are **merged** into
363
+ that bundle alongside the system store rather than replacing it. Run with
364
+ `CLINE_DEBUG=1` to see how many OS and user CAs were loaded and where the bundle
365
+ was written.
366
+
352
367
  ## Contributing
353
368
 
354
369
  See [DEVELOPMENT.md](./DEVELOPMENT.md) for local development setup, monorepo structure, and TUI architecture. See [DISTRIBUTION.md](./DISTRIBUTION.md) for how the CLI is packaged and distributed.
@@ -0,0 +1,281 @@
1
+ // Auto-discovery of OS trust anchors for the Cline CLI.
2
+ //
3
+ // Bun does not read the OS trust store, so the 3.x CLI cannot see corporate
4
+ // MITM / self-signed CAs out of the box. This runs in the Node `bin/cline`
5
+ // wrapper (not Bun), reads the full OS store via tls.getCACertificates("system")
6
+ // (Node >= 22, no --use-system-ca flag), and hands the certs to the Bun child
7
+ // via NODE_EXTRA_CA_CERTS, which both runtimes honor. Mirrors the JetBrains
8
+ // plugin's configureCertificates(), sourcing from the OS instead of the IDE.
9
+ //
10
+ // Dependency-free CommonJS with injectable modules so it is unit-testable and
11
+ // ships verbatim in the published wrapper package.
12
+
13
+ const PEM_MARKER = "-----BEGIN CERTIFICATE-----";
14
+ const CERT_BLOCK =
15
+ /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g;
16
+
17
+ /**
18
+ * Returns only the complete certificate blocks from PEM text, or null when
19
+ * there are none. User files may also hold private keys (combined cert+key
20
+ * PEMs) or other sections, which must never be copied into the managed
21
+ * bundle. Files that contain nothing but certificates pass through verbatim
22
+ * so unchanged bundles keep hash-skipping the rewrite.
23
+ */
24
+ function sanitizePem(text) {
25
+ const blocks = text.match(CERT_BLOCK) ?? [];
26
+ if (blocks.length === 0) {
27
+ return null;
28
+ }
29
+ const rest = text.replace(CERT_BLOCK, "");
30
+ if (/^\s*$/.test(rest)) {
31
+ return text;
32
+ }
33
+ return `${blocks.join("\n")}\n`;
34
+ }
35
+
36
+ /**
37
+ * Returns OS-trusted certificates as PEM strings, or [] when unavailable.
38
+ * tls.getCACertificates("system") requires Node >= 22.
39
+ */
40
+ function harvestSystemCerts(tlsModule) {
41
+ try {
42
+ const tls = tlsModule || require("node:tls");
43
+ if (typeof tls.getCACertificates !== "function") {
44
+ return [];
45
+ }
46
+ const certs = tls.getCACertificates("system");
47
+ if (!Array.isArray(certs)) {
48
+ return [];
49
+ }
50
+ return certs.filter(
51
+ (cert) => typeof cert === "string" && cert.includes(PEM_MARKER),
52
+ );
53
+ } catch {
54
+ return [];
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Returns the file's certificate blocks as PEM text, or null when missing,
60
+ * unreadable, or holding no complete certificate block.
61
+ */
62
+ function readUserBundle(fsModule, userPath) {
63
+ if (!userPath) {
64
+ return null;
65
+ }
66
+ try {
67
+ const fs = fsModule || require("node:fs");
68
+ const stat = fs.statSync(userPath, { throwIfNoEntry: false });
69
+ if (!stat || !stat.isFile()) {
70
+ return null;
71
+ }
72
+ // Binary DER would not have loaded in the runtime either; require PEM.
73
+ return sanitizePem(fs.readFileSync(userPath, "utf8"));
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Reads the user's NODE_EXTRA_CA_CERTS value into PEM strings. Node treats the
81
+ * value as a single file, but some users set an OS-path-delimited list; the
82
+ * whole value is tried as one file first, then split.
83
+ * The managed bundle is excluded so reading it back never re-appends its certs.
84
+ */
85
+ function readUserCerts(fsModule, pathModule, value, managedPath) {
86
+ if (!value) {
87
+ return [];
88
+ }
89
+ const fs = fsModule || require("node:fs");
90
+ const path = pathModule || require("node:path");
91
+ const candidates = [];
92
+ const whole = readUserBundle(fs, value);
93
+ if (whole) {
94
+ candidates.push({ filePath: value, pem: whole });
95
+ } else if (value.includes(path.delimiter)) {
96
+ for (const segment of value.split(path.delimiter)) {
97
+ const trimmed = segment.trim();
98
+ if (!trimmed) {
99
+ continue;
100
+ }
101
+ const pem = readUserBundle(fs, trimmed);
102
+ if (pem) {
103
+ candidates.push({ filePath: trimmed, pem });
104
+ }
105
+ }
106
+ }
107
+ const pems = [];
108
+ for (const candidate of candidates) {
109
+ const isManaged =
110
+ managedPath &&
111
+ path.resolve(candidate.filePath) === path.resolve(managedPath);
112
+ if (!isManaged) {
113
+ pems.push(candidate.pem);
114
+ }
115
+ }
116
+ return pems;
117
+ }
118
+
119
+ /**
120
+ * Concatenates the user PEMs (if any) and the system certificates into one
121
+ * bundle. A separating newline is inserted between parts so adjacent END/BEGIN
122
+ * markers cannot fuse into one invalid line.
123
+ */
124
+ function buildBundle({ systemCerts, userPems }) {
125
+ const parts = [...(userPems ?? []), ...systemCerts];
126
+ return parts
127
+ .map((part) => (part.endsWith("\n") ? part : `${part}\n`))
128
+ .join("");
129
+ }
130
+
131
+ /** Counts individual PEM certificates across the given bundle strings. */
132
+ function countCerts(pems) {
133
+ let count = 0;
134
+ for (const pem of pems) {
135
+ count += pem.split(PEM_MARKER).length - 1;
136
+ }
137
+ return count;
138
+ }
139
+
140
+ function readFileIfExists(fs, filePath) {
141
+ try {
142
+ return fs.readFileSync(filePath, "utf8");
143
+ } catch {
144
+ return null;
145
+ }
146
+ }
147
+
148
+ function resolveClineDir(env, os, path) {
149
+ return env.CLINE_DIR?.trim() || path.join(os.homedir(), ".cline");
150
+ }
151
+
152
+ /**
153
+ * True when the api-unavailable warning should print. Stamped per Node version
154
+ * in the cline dir so the nudge shows once rather than on every command; a
155
+ * version change (upgrade that still falls short, or downgrade) re-arms it.
156
+ * When the stamp cannot be read or written, warn — bookkeeping failures must
157
+ * never suppress a real diagnostic.
158
+ */
159
+ function shouldWarnApiUnavailable(env, deps = {}) {
160
+ const fs = deps.fs || require("node:fs");
161
+ const os = deps.os || require("node:os");
162
+ const path = deps.path || require("node:path");
163
+ const version = deps.nodeVersion || process.versions.node;
164
+ const dir = resolveClineDir(env, os, path);
165
+ const stamp = path.join(dir, `.ca-api-warned-${version}`);
166
+ try {
167
+ if (fs.existsSync(stamp)) {
168
+ return false;
169
+ }
170
+ fs.mkdirSync(dir, { recursive: true });
171
+ fs.writeFileSync(stamp, "", { mode: 0o600 });
172
+ return true;
173
+ } catch {
174
+ return true;
175
+ }
176
+ }
177
+
178
+ /** Atomically writes [content] to [target]; returns true on success. */
179
+ function writeBundle(fs, dir, target, content) {
180
+ const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
181
+ try {
182
+ fs.mkdirSync(dir, { recursive: true });
183
+ // Owner read/write: the bundle holds public CA material, not secrets,
184
+ // but there is no reason to make it world-writable.
185
+ fs.writeFileSync(tmp, content, { mode: 0o600 });
186
+ try {
187
+ fs.renameSync(tmp, target);
188
+ } catch {
189
+ // Windows can reject rename over a file a concurrent child holds open.
190
+ fs.rmSync(target, { force: true });
191
+ fs.renameSync(tmp, target);
192
+ }
193
+ return true;
194
+ } catch {
195
+ // Never leave a partial temp file behind (e.g. ENOSPC mid-write).
196
+ try {
197
+ fs.rmSync(tmp, { force: true });
198
+ } catch {
199
+ // Ignore: best-effort cleanup.
200
+ }
201
+ return false;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Harvests OS trust anchors, merges them with any user NODE_EXTRA_CA_CERTS, and
207
+ * points env.NODE_EXTRA_CA_CERTS at a single managed PEM bundle. Mutates `env`
208
+ * in place. Returns an outcome the caller can log; `action` is one of
209
+ * "unchanged" | "written" | "write-failed-reused" | "write-failed" |
210
+ * "no-system-certs" | "api-unavailable".
211
+ */
212
+ function configureNodeExtraCaCerts(env, deps = {}) {
213
+ const fs = deps.fs || require("node:fs");
214
+ const os = deps.os || require("node:os");
215
+ const path = deps.path || require("node:path");
216
+ const tls = deps.tls || require("node:tls");
217
+
218
+ // tls.getCACertificates("system") needs Node >= 22.15; on older Nodes the
219
+ // harvest cannot run at all, which the caller should surface to the user.
220
+ if (typeof tls.getCACertificates !== "function") {
221
+ return {
222
+ action: "api-unavailable",
223
+ path: null,
224
+ systemCertCount: 0,
225
+ userCertCount: 0,
226
+ };
227
+ }
228
+
229
+ const systemCerts = harvestSystemCerts(tls);
230
+ if (systemCerts.length === 0) {
231
+ // Nothing to add: leave any user-provided NODE_EXTRA_CA_CERTS untouched
232
+ // and let the runtime fall back to its bundled CAs.
233
+ return {
234
+ action: "no-system-certs",
235
+ path: null,
236
+ systemCertCount: 0,
237
+ userCertCount: 0,
238
+ };
239
+ }
240
+
241
+ const managedDir = resolveClineDir(env, os, path);
242
+ const managedPath = path.join(managedDir, "cli-node-extra-ca-certs.pem");
243
+ const userValue = (env.NODE_EXTRA_CA_CERTS || "").trim() || null;
244
+ const userPems = readUserCerts(fs, path, userValue, managedPath);
245
+ const bundle = buildBundle({ systemCerts, userPems });
246
+ const base = {
247
+ path: managedPath,
248
+ systemCertCount: systemCerts.length,
249
+ userCertCount: countCerts(userPems),
250
+ };
251
+
252
+ // Skip the rewrite when the bundle is already current. Avoids per-launch I/O
253
+ // and the concurrent-rename race in the steady state.
254
+ if (readFileIfExists(fs, managedPath) === bundle) {
255
+ env.NODE_EXTRA_CA_CERTS = managedPath;
256
+ return { ...base, action: "unchanged" };
257
+ }
258
+
259
+ if (writeBundle(fs, managedDir, managedPath, bundle)) {
260
+ env.NODE_EXTRA_CA_CERTS = managedPath;
261
+ return { ...base, action: "written" };
262
+ }
263
+
264
+ // Write failed: fall back to a previously-written bundle if one exists.
265
+ if (readFileIfExists(fs, managedPath)) {
266
+ env.NODE_EXTRA_CA_CERTS = managedPath;
267
+ return { ...base, action: "write-failed-reused" };
268
+ }
269
+ return { ...base, path: null, action: "write-failed" };
270
+ }
271
+
272
+ module.exports = {
273
+ harvestSystemCerts,
274
+ sanitizePem,
275
+ readUserBundle,
276
+ readUserCerts,
277
+ buildBundle,
278
+ countCerts,
279
+ configureNodeExtraCaCerts,
280
+ shouldWarnApiUnavailable,
281
+ };
package/bin/cline CHANGED
@@ -23,6 +23,48 @@ const childEnv = {
23
23
  CLINE_WRAPPER_PATH: scriptPath,
24
24
  };
25
25
 
26
+ // Auto-discover OS trust anchors and pass them to the Bun child via
27
+ // NODE_EXTRA_CA_CERTS. The Bun runtime does not read the OS store on its own,
28
+ // so corporate/self-signed CAs would otherwise fail. This wrapper runs on
29
+ // Node, which can read the full store here.
30
+ try {
31
+ const caCerts = require("./ca-certs.cjs");
32
+ const outcome = caCerts.configureNodeExtraCaCerts(childEnv);
33
+ const debug =
34
+ process.env.CLINE_DEBUG === "1" || process.env.CLINE_DEBUG === "true";
35
+ // Not debug-gated: on old Nodes the harvest silently doing nothing is
36
+ // indistinguishable from a broken corporate proxy. Stamped per Node
37
+ // version so the nudge shows once, not on every command.
38
+ if (
39
+ outcome &&
40
+ outcome.action === "api-unavailable" &&
41
+ !childEnv.NODE_EXTRA_CA_CERTS &&
42
+ caCerts.shouldWarnApiUnavailable(childEnv)
43
+ ) {
44
+ console.warn(
45
+ `[cline] Node ${process.versions.node} cannot read the OS trust store (needs >= 22.15); ` +
46
+ "corporate or self-signed CAs may fail TLS. Upgrade Node or set NODE_EXTRA_CA_CERTS.",
47
+ );
48
+ }
49
+ if (debug && outcome) {
50
+ if (outcome.action === "no-system-certs") {
51
+ console.warn(
52
+ "[cline] No OS trust anchors found; relying on the runtime's bundled CAs.",
53
+ );
54
+ } else if (outcome.action === "write-failed") {
55
+ console.warn(
56
+ "[cline] Could not write the managed CA bundle; relying on the runtime's bundled CAs.",
57
+ );
58
+ } else {
59
+ console.warn(
60
+ `[cline] Trust: ${outcome.systemCertCount} OS + ${outcome.userCertCount} user CAs (${outcome.action}) -> ${outcome.path}`,
61
+ );
62
+ }
63
+ }
64
+ } catch {
65
+ // Best effort: fall back to the runtime's default trust on any failure.
66
+ }
67
+
26
68
  function run(target) {
27
69
  const result = childProcess.spawnSync(target, process.argv.slice(2), {
28
70
  stdio: "inherit",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cline",
3
- "version": "3.0.42",
3
+ "version": "3.0.43",
4
4
  "description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [
@@ -37,18 +37,18 @@
37
37
  "postinstall": "node ./postinstall.mjs || true"
38
38
  },
39
39
  "dependencies": {
40
- "@cline/sdk": "0.0.62",
41
- "@cline/core": "0.0.62",
42
- "@cline/agents": "0.0.62",
43
- "@cline/llms": "0.0.62",
44
- "@cline/shared": "0.0.62"
40
+ "@cline/sdk": "0.0.63",
41
+ "@cline/core": "0.0.63",
42
+ "@cline/agents": "0.0.63",
43
+ "@cline/llms": "0.0.63",
44
+ "@cline/shared": "0.0.63"
45
45
  },
46
46
  "optionalDependencies": {
47
- "@cline/cli-darwin-arm64": "3.0.42",
48
- "@cline/cli-darwin-x64": "3.0.42",
49
- "@cline/cli-windows-arm64": "3.0.42",
50
- "@cline/cli-linux-x64": "3.0.42",
51
- "@cline/cli-linux-arm64": "3.0.42",
52
- "@cline/cli-windows-x64": "3.0.42"
47
+ "@cline/cli-darwin-arm64": "3.0.43",
48
+ "@cline/cli-darwin-x64": "3.0.43",
49
+ "@cline/cli-windows-arm64": "3.0.43",
50
+ "@cline/cli-linux-x64": "3.0.43",
51
+ "@cline/cli-linux-arm64": "3.0.43",
52
+ "@cline/cli-windows-x64": "3.0.43"
53
53
  }
54
54
  }