@github/copilot-sdk 0.3.0 → 1.0.0-beta.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/README.md CHANGED
@@ -88,6 +88,7 @@ new CopilotClient(options?: CopilotClientOptions)
88
88
  - `autoStart?: boolean` - Auto-start server (default: true)
89
89
  - `gitHubToken?: string` - GitHub token for authentication. When provided, takes priority over other auth methods.
90
90
  - `useLoggedInUser?: boolean` - Whether to use logged-in user for authentication (default: true, but false when `gitHubToken` is provided). Cannot be used with `cliUrl`.
91
+ - `copilotHome?: string` - Base directory for Copilot data (session state, config, etc.). Sets `COPILOT_HOME` on the spawned CLI process. When not set, the CLI defaults to `~/.copilot`. Useful in restricted environments where only specific directories are writable. Ignored when using `cliUrl`.
91
92
  - `telemetry?: TelemetryConfig` - OpenTelemetry configuration for the CLI process. Providing this object enables telemetry — no separate flag needed. See [Telemetry](#telemetry) below.
92
93
  - `onGetTraceContext?: TraceContextProvider` - Advanced: callback for linking your application's own OpenTelemetry spans into the same distributed trace as the CLI's spans. Not needed for normal telemetry collection. See [Telemetry](#telemetry) below.
93
94
 
@@ -36,6 +36,11 @@ var import_sessionFsProvider = require("./sessionFsProvider.js");
36
36
  var import_telemetry = require("./telemetry.js");
37
37
  var import_types = require("./types.js");
38
38
  const import_meta = {};
39
+ function toWireProviderConfig(provider) {
40
+ const { maxInputTokens, ...rest } = provider;
41
+ if (maxInputTokens === void 0) return rest;
42
+ return { ...rest, maxPromptTokens: maxInputTokens };
43
+ }
39
44
  const MIN_PROTOCOL_VERSION = 2;
40
45
  function isZodSchema(value) {
41
46
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
@@ -109,6 +114,8 @@ class CopilotClient {
109
114
  options;
110
115
  isExternalServer = false;
111
116
  forceStopping = false;
117
+ /** Token sent in `connect`; auto-generated when the SDK spawns its own CLI in TCP mode. */
118
+ effectiveConnectionToken;
112
119
  onListModels;
113
120
  onGetTraceContext;
114
121
  modelsCache = null;
@@ -116,6 +123,7 @@ class CopilotClient {
116
123
  sessionLifecycleHandlers = /* @__PURE__ */ new Set();
117
124
  typedLifecycleHandlers = /* @__PURE__ */ new Map();
118
125
  _rpc = null;
126
+ _internalRpc = null;
119
127
  processExitPromise = null;
120
128
  // Rejects when CLI process exits
121
129
  negotiatedProtocolVersion = null;
@@ -134,6 +142,19 @@ class CopilotClient {
134
142
  }
135
143
  return this._rpc;
136
144
  }
145
+ /**
146
+ * Internal RPC surface (e.g. handshake helpers). Not part of the public API.
147
+ * @internal
148
+ */
149
+ get internalRpc() {
150
+ if (!this.connection) {
151
+ throw new Error("Client is not connected. Call start() first.");
152
+ }
153
+ if (!this._internalRpc) {
154
+ this._internalRpc = (0, import_rpc.createInternalServerRpc)(this.connection);
155
+ }
156
+ return this._internalRpc;
157
+ }
137
158
  /**
138
159
  * Creates a new CopilotClient instance.
139
160
  *
@@ -169,6 +190,17 @@ class CopilotClient {
169
190
  "gitHubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
170
191
  );
171
192
  }
193
+ if (options.tcpConnectionToken !== void 0) {
194
+ if (typeof options.tcpConnectionToken !== "string" || options.tcpConnectionToken.length === 0) {
195
+ throw new Error("tcpConnectionToken must be a non-empty string");
196
+ }
197
+ if (options.useStdio === true) {
198
+ throw new Error("tcpConnectionToken cannot be used with useStdio: true");
199
+ }
200
+ }
201
+ const willUseStdio = options.cliUrl ? false : options.useStdio ?? true;
202
+ const sdkSpawnsCli = !willUseStdio && !options.cliUrl && !options.isChildProcess;
203
+ this.effectiveConnectionToken = options.tcpConnectionToken ?? (sdkSpawnsCli ? (0, import_node_crypto.randomUUID)() : void 0);
172
204
  if (options.sessionFs) {
173
205
  this.validateSessionFsConfig(options.sessionFs);
174
206
  }
@@ -202,7 +234,9 @@ class CopilotClient {
202
234
  // Default useLoggedInUser to false when gitHubToken is provided, otherwise true
203
235
  useLoggedInUser: options.useLoggedInUser ?? (options.gitHubToken ? false : true),
204
236
  telemetry: options.telemetry,
205
- sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0
237
+ copilotHome: options.copilotHome,
238
+ sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
239
+ remote: options.remote ?? false
206
240
  };
207
241
  }
208
242
  /**
@@ -541,7 +575,7 @@ class CopilotClient {
541
575
  systemMessage: wireSystemMessage,
542
576
  availableTools: config.availableTools,
543
577
  excludedTools: config.excludedTools,
544
- provider: config.provider,
578
+ provider: config.provider ? toWireProviderConfig(config.provider) : void 0,
545
579
  modelCapabilities: config.modelCapabilities,
546
580
  requestPermission: true,
547
581
  requestUserInput: !!config.onUserInputRequest,
@@ -558,6 +592,7 @@ class CopilotClient {
558
592
  configDir: config.configDir,
559
593
  enableConfigDiscovery: config.enableConfigDiscovery,
560
594
  skillDirectories: config.skillDirectories,
595
+ instructionDirectories: config.instructionDirectories,
561
596
  disabledSkills: config.disabledSkills,
562
597
  infiniteSessions: config.infiniteSessions,
563
598
  gitHubToken: config.gitHubToken
@@ -668,7 +703,7 @@ class CopilotClient {
668
703
  name: cmd.name,
669
704
  description: cmd.description
670
705
  })),
671
- provider: config.provider,
706
+ provider: config.provider ? toWireProviderConfig(config.provider) : void 0,
672
707
  modelCapabilities: config.modelCapabilities,
673
708
  requestPermission: config.onPermissionRequest !== import_types.defaultJoinSessionPermissionHandler,
674
709
  requestUserInput: !!config.onUserInputRequest,
@@ -685,9 +720,11 @@ class CopilotClient {
685
720
  defaultAgent: config.defaultAgent,
686
721
  agent: config.agent,
687
722
  skillDirectories: config.skillDirectories,
723
+ instructionDirectories: config.instructionDirectories,
688
724
  disabledSkills: config.disabledSkills,
689
725
  infiniteSessions: config.infiniteSessions,
690
726
  disableResume: config.disableResume,
727
+ continuePendingWork: config.continuePendingWork,
691
728
  gitHubToken: config.gitHubToken
692
729
  });
693
730
  const { workspacePath, capabilities } = response;
@@ -809,18 +846,29 @@ class CopilotClient {
809
846
  }
810
847
  }
811
848
  /**
812
- * Verify that the server's protocol version is within the supported range
813
- * and store the negotiated version.
849
+ * Send the `connect` handshake (carrying the optional token) and verify the
850
+ * server's protocol version. Falls back to `ping` against legacy servers
851
+ * that don't implement `connect`.
814
852
  */
815
853
  async verifyProtocolVersion() {
854
+ if (!this.connection) {
855
+ throw new Error("Client not connected");
856
+ }
816
857
  const maxVersion = (0, import_sdkProtocolVersion.getSdkProtocolVersion)();
817
- let pingResult;
818
- if (this.processExitPromise) {
819
- pingResult = await Promise.race([this.ping(), this.processExitPromise]);
820
- } else {
821
- pingResult = await this.ping();
858
+ const raceAgainstExit = (p) => this.processExitPromise ? Promise.race([p, this.processExitPromise]) : p;
859
+ let serverVersion;
860
+ try {
861
+ const result = await raceAgainstExit(
862
+ this.internalRpc.connect({ token: this.effectiveConnectionToken })
863
+ );
864
+ serverVersion = result.protocolVersion;
865
+ } catch (err) {
866
+ if (err instanceof import_node.ResponseError && (err.code === import_node.ErrorCodes.MethodNotFound || err.message === "Unhandled method connect")) {
867
+ serverVersion = (await raceAgainstExit(this.ping())).protocolVersion;
868
+ } else {
869
+ throw err;
870
+ }
822
871
  }
823
- const serverVersion = pingResult.protocolVersion;
824
872
  if (serverVersion === void 0) {
825
873
  throw new Error(
826
874
  `SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server does not report a protocol version. Please update your server to ensure compatibility.`
@@ -1050,11 +1098,20 @@ class CopilotClient {
1050
1098
  this.options.sessionIdleTimeoutSeconds.toString()
1051
1099
  );
1052
1100
  }
1101
+ if (this.options.remote) {
1102
+ args.push("--remote");
1103
+ }
1053
1104
  const envWithoutNodeDebug = { ...this.options.env };
1054
1105
  delete envWithoutNodeDebug.NODE_DEBUG;
1055
1106
  if (this.options.gitHubToken) {
1056
1107
  envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
1057
1108
  }
1109
+ if (this.effectiveConnectionToken) {
1110
+ envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
1111
+ }
1112
+ if (this.options.copilotHome) {
1113
+ envWithoutNodeDebug.COPILOT_HOME = this.options.copilotHome;
1114
+ }
1058
1115
  if (!this.options.cliPath) {
1059
1116
  throw new Error(
1060
1117
  "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."
@@ -18,6 +18,7 @@ var __copyProps = (to, from, except, desc) => {
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
  var rpc_exports = {};
20
20
  __export(rpc_exports, {
21
+ createInternalServerRpc: () => createInternalServerRpc,
21
22
  createServerRpc: () => createServerRpc,
22
23
  createSessionRpc: () => createSessionRpc,
23
24
  registerClientSessionApiHandlers: () => registerClientSessionApiHandlers
@@ -61,8 +62,14 @@ function createServerRpc(connection) {
61
62
  }
62
63
  };
63
64
  }
65
+ function createInternalServerRpc(connection) {
66
+ return {
67
+ connect: async (params) => connection.sendRequest("connect", params)
68
+ };
69
+ }
64
70
  function createSessionRpc(connection, sessionId) {
65
71
  return {
72
+ suspend: async () => connection.sendRequest("session.suspend", { sessionId }),
66
73
  auth: {
67
74
  getStatus: async () => connection.sendRequest("session.auth.getStatus", { sessionId })
68
75
  },
@@ -105,6 +112,14 @@ function createSessionRpc(connection, sessionId) {
105
112
  reload: async () => connection.sendRequest("session.agent.reload", { sessionId })
106
113
  },
107
114
  /** @experimental */
115
+ tasks: {
116
+ startAgent: async (params) => connection.sendRequest("session.tasks.startAgent", { sessionId, ...params }),
117
+ list: async () => connection.sendRequest("session.tasks.list", { sessionId }),
118
+ promoteToBackground: async (params) => connection.sendRequest("session.tasks.promoteToBackground", { sessionId, ...params }),
119
+ cancel: async (params) => connection.sendRequest("session.tasks.cancel", { sessionId, ...params }),
120
+ remove: async (params) => connection.sendRequest("session.tasks.remove", { sessionId, ...params })
121
+ },
122
+ /** @experimental */
108
123
  skills: {
109
124
  list: async () => connection.sendRequest("session.skills.list", { sessionId }),
110
125
  enable: async (params) => connection.sendRequest("session.skills.enable", { sessionId, ...params }),
@@ -161,6 +176,11 @@ function createSessionRpc(connection, sessionId) {
161
176
  /** @experimental */
162
177
  usage: {
163
178
  getMetrics: async () => connection.sendRequest("session.usage.getMetrics", { sessionId })
179
+ },
180
+ /** @experimental */
181
+ remote: {
182
+ enable: async () => connection.sendRequest("session.remote.enable", { sessionId }),
183
+ disable: async () => connection.sendRequest("session.remote.disable", { sessionId })
164
184
  }
165
185
  };
166
186
  }
@@ -218,6 +238,7 @@ function registerClientSessionApiHandlers(connection, getHandlers) {
218
238
  }
219
239
  // Annotate the CommonJS export names for ESM import in node:
220
240
  0 && (module.exports = {
241
+ createInternalServerRpc,
221
242
  createServerRpc,
222
243
  createSessionRpc,
223
244
  registerClientSessionApiHandlers
package/dist/client.d.ts CHANGED
@@ -47,6 +47,8 @@ export declare class CopilotClient {
47
47
  private options;
48
48
  private isExternalServer;
49
49
  private forceStopping;
50
+ /** Token sent in `connect`; auto-generated when the SDK spawns its own CLI in TCP mode. */
51
+ private effectiveConnectionToken?;
50
52
  private onListModels?;
51
53
  private onGetTraceContext?;
52
54
  private modelsCache;
@@ -54,6 +56,7 @@ export declare class CopilotClient {
54
56
  private sessionLifecycleHandlers;
55
57
  private typedLifecycleHandlers;
56
58
  private _rpc;
59
+ private _internalRpc;
57
60
  private processExitPromise;
58
61
  private negotiatedProtocolVersion;
59
62
  /** Connection-level session filesystem config, set via constructor option. */
@@ -63,6 +66,11 @@ export declare class CopilotClient {
63
66
  * @throws Error if the client is not connected
64
67
  */
65
68
  get rpc(): ReturnType<typeof createServerRpc>;
69
+ /**
70
+ * Internal RPC surface (e.g. handshake helpers). Not part of the public API.
71
+ * @internal
72
+ */
73
+ private get internalRpc();
66
74
  /**
67
75
  * Creates a new CopilotClient instance.
68
76
  *
@@ -267,8 +275,9 @@ export declare class CopilotClient {
267
275
  */
268
276
  listModels(): Promise<ModelInfo[]>;
269
277
  /**
270
- * Verify that the server's protocol version is within the supported range
271
- * and store the negotiated version.
278
+ * Send the `connect` handshake (carrying the optional token) and verify the
279
+ * server's protocol version. Falls back to `ping` against legacy servers
280
+ * that don't implement `connect`.
272
281
  */
273
282
  private verifyProtocolVersion;
274
283
  /**
package/dist/client.js CHANGED
@@ -7,15 +7,26 @@ import { dirname, join } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import {
9
9
  createMessageConnection,
10
+ ErrorCodes,
11
+ ResponseError,
10
12
  StreamMessageReader,
11
13
  StreamMessageWriter
12
14
  } from "vscode-jsonrpc/node.js";
13
- import { createServerRpc, registerClientSessionApiHandlers } from "./generated/rpc.js";
15
+ import {
16
+ createServerRpc,
17
+ createInternalServerRpc,
18
+ registerClientSessionApiHandlers
19
+ } from "./generated/rpc.js";
14
20
  import { getSdkProtocolVersion } from "./sdkProtocolVersion.js";
15
21
  import { CopilotSession, NO_RESULT_PERMISSION_V2_ERROR } from "./session.js";
16
22
  import { createSessionFsAdapter } from "./sessionFsProvider.js";
17
23
  import { getTraceContext } from "./telemetry.js";
18
24
  import { defaultJoinSessionPermissionHandler } from "./types.js";
25
+ function toWireProviderConfig(provider) {
26
+ const { maxInputTokens, ...rest } = provider;
27
+ if (maxInputTokens === void 0) return rest;
28
+ return { ...rest, maxPromptTokens: maxInputTokens };
29
+ }
19
30
  const MIN_PROTOCOL_VERSION = 2;
20
31
  function isZodSchema(value) {
21
32
  return value != null && typeof value === "object" && "toJSONSchema" in value && typeof value.toJSONSchema === "function";
@@ -89,6 +100,8 @@ class CopilotClient {
89
100
  options;
90
101
  isExternalServer = false;
91
102
  forceStopping = false;
103
+ /** Token sent in `connect`; auto-generated when the SDK spawns its own CLI in TCP mode. */
104
+ effectiveConnectionToken;
92
105
  onListModels;
93
106
  onGetTraceContext;
94
107
  modelsCache = null;
@@ -96,6 +109,7 @@ class CopilotClient {
96
109
  sessionLifecycleHandlers = /* @__PURE__ */ new Set();
97
110
  typedLifecycleHandlers = /* @__PURE__ */ new Map();
98
111
  _rpc = null;
112
+ _internalRpc = null;
99
113
  processExitPromise = null;
100
114
  // Rejects when CLI process exits
101
115
  negotiatedProtocolVersion = null;
@@ -114,6 +128,19 @@ class CopilotClient {
114
128
  }
115
129
  return this._rpc;
116
130
  }
131
+ /**
132
+ * Internal RPC surface (e.g. handshake helpers). Not part of the public API.
133
+ * @internal
134
+ */
135
+ get internalRpc() {
136
+ if (!this.connection) {
137
+ throw new Error("Client is not connected. Call start() first.");
138
+ }
139
+ if (!this._internalRpc) {
140
+ this._internalRpc = createInternalServerRpc(this.connection);
141
+ }
142
+ return this._internalRpc;
143
+ }
117
144
  /**
118
145
  * Creates a new CopilotClient instance.
119
146
  *
@@ -149,6 +176,17 @@ class CopilotClient {
149
176
  "gitHubToken and useLoggedInUser cannot be used with cliUrl (external server manages its own auth)"
150
177
  );
151
178
  }
179
+ if (options.tcpConnectionToken !== void 0) {
180
+ if (typeof options.tcpConnectionToken !== "string" || options.tcpConnectionToken.length === 0) {
181
+ throw new Error("tcpConnectionToken must be a non-empty string");
182
+ }
183
+ if (options.useStdio === true) {
184
+ throw new Error("tcpConnectionToken cannot be used with useStdio: true");
185
+ }
186
+ }
187
+ const willUseStdio = options.cliUrl ? false : options.useStdio ?? true;
188
+ const sdkSpawnsCli = !willUseStdio && !options.cliUrl && !options.isChildProcess;
189
+ this.effectiveConnectionToken = options.tcpConnectionToken ?? (sdkSpawnsCli ? randomUUID() : void 0);
152
190
  if (options.sessionFs) {
153
191
  this.validateSessionFsConfig(options.sessionFs);
154
192
  }
@@ -182,7 +220,9 @@ class CopilotClient {
182
220
  // Default useLoggedInUser to false when gitHubToken is provided, otherwise true
183
221
  useLoggedInUser: options.useLoggedInUser ?? (options.gitHubToken ? false : true),
184
222
  telemetry: options.telemetry,
185
- sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0
223
+ copilotHome: options.copilotHome,
224
+ sessionIdleTimeoutSeconds: options.sessionIdleTimeoutSeconds ?? 0,
225
+ remote: options.remote ?? false
186
226
  };
187
227
  }
188
228
  /**
@@ -521,7 +561,7 @@ class CopilotClient {
521
561
  systemMessage: wireSystemMessage,
522
562
  availableTools: config.availableTools,
523
563
  excludedTools: config.excludedTools,
524
- provider: config.provider,
564
+ provider: config.provider ? toWireProviderConfig(config.provider) : void 0,
525
565
  modelCapabilities: config.modelCapabilities,
526
566
  requestPermission: true,
527
567
  requestUserInput: !!config.onUserInputRequest,
@@ -538,6 +578,7 @@ class CopilotClient {
538
578
  configDir: config.configDir,
539
579
  enableConfigDiscovery: config.enableConfigDiscovery,
540
580
  skillDirectories: config.skillDirectories,
581
+ instructionDirectories: config.instructionDirectories,
541
582
  disabledSkills: config.disabledSkills,
542
583
  infiniteSessions: config.infiniteSessions,
543
584
  gitHubToken: config.gitHubToken
@@ -648,7 +689,7 @@ class CopilotClient {
648
689
  name: cmd.name,
649
690
  description: cmd.description
650
691
  })),
651
- provider: config.provider,
692
+ provider: config.provider ? toWireProviderConfig(config.provider) : void 0,
652
693
  modelCapabilities: config.modelCapabilities,
653
694
  requestPermission: config.onPermissionRequest !== defaultJoinSessionPermissionHandler,
654
695
  requestUserInput: !!config.onUserInputRequest,
@@ -665,9 +706,11 @@ class CopilotClient {
665
706
  defaultAgent: config.defaultAgent,
666
707
  agent: config.agent,
667
708
  skillDirectories: config.skillDirectories,
709
+ instructionDirectories: config.instructionDirectories,
668
710
  disabledSkills: config.disabledSkills,
669
711
  infiniteSessions: config.infiniteSessions,
670
712
  disableResume: config.disableResume,
713
+ continuePendingWork: config.continuePendingWork,
671
714
  gitHubToken: config.gitHubToken
672
715
  });
673
716
  const { workspacePath, capabilities } = response;
@@ -789,18 +832,29 @@ class CopilotClient {
789
832
  }
790
833
  }
791
834
  /**
792
- * Verify that the server's protocol version is within the supported range
793
- * and store the negotiated version.
835
+ * Send the `connect` handshake (carrying the optional token) and verify the
836
+ * server's protocol version. Falls back to `ping` against legacy servers
837
+ * that don't implement `connect`.
794
838
  */
795
839
  async verifyProtocolVersion() {
840
+ if (!this.connection) {
841
+ throw new Error("Client not connected");
842
+ }
796
843
  const maxVersion = getSdkProtocolVersion();
797
- let pingResult;
798
- if (this.processExitPromise) {
799
- pingResult = await Promise.race([this.ping(), this.processExitPromise]);
800
- } else {
801
- pingResult = await this.ping();
844
+ const raceAgainstExit = (p) => this.processExitPromise ? Promise.race([p, this.processExitPromise]) : p;
845
+ let serverVersion;
846
+ try {
847
+ const result = await raceAgainstExit(
848
+ this.internalRpc.connect({ token: this.effectiveConnectionToken })
849
+ );
850
+ serverVersion = result.protocolVersion;
851
+ } catch (err) {
852
+ if (err instanceof ResponseError && (err.code === ErrorCodes.MethodNotFound || err.message === "Unhandled method connect")) {
853
+ serverVersion = (await raceAgainstExit(this.ping())).protocolVersion;
854
+ } else {
855
+ throw err;
856
+ }
802
857
  }
803
- const serverVersion = pingResult.protocolVersion;
804
858
  if (serverVersion === void 0) {
805
859
  throw new Error(
806
860
  `SDK protocol version mismatch: SDK supports versions ${MIN_PROTOCOL_VERSION}-${maxVersion}, but server does not report a protocol version. Please update your server to ensure compatibility.`
@@ -1030,11 +1084,20 @@ class CopilotClient {
1030
1084
  this.options.sessionIdleTimeoutSeconds.toString()
1031
1085
  );
1032
1086
  }
1087
+ if (this.options.remote) {
1088
+ args.push("--remote");
1089
+ }
1033
1090
  const envWithoutNodeDebug = { ...this.options.env };
1034
1091
  delete envWithoutNodeDebug.NODE_DEBUG;
1035
1092
  if (this.options.gitHubToken) {
1036
1093
  envWithoutNodeDebug.COPILOT_SDK_AUTH_TOKEN = this.options.gitHubToken;
1037
1094
  }
1095
+ if (this.effectiveConnectionToken) {
1096
+ envWithoutNodeDebug.COPILOT_CONNECTION_TOKEN = this.effectiveConnectionToken;
1097
+ }
1098
+ if (this.options.copilotHome) {
1099
+ envWithoutNodeDebug.COPILOT_HOME = this.options.copilotHome;
1100
+ }
1038
1101
  if (!this.options.cliPath) {
1039
1102
  throw new Error(
1040
1103
  "Path to Copilot CLI is required. Please provide it via the cliPath option, or use cliUrl to rely on a remote CLI."