agent-browser-loop 0.2.2 → 0.3.1

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/src/cli.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  string,
14
14
  subcommands,
15
15
  } from "cmd-ts";
16
+ import { VERSION } from "./version";
16
17
  import type { AgentBrowserOptions } from "./browser";
17
18
  import type { StepAction } from "./commands";
18
19
  import { parseBrowserConfig } from "./config";
@@ -24,8 +25,17 @@ import {
24
25
  isDaemonRunning,
25
26
  } from "./daemon";
26
27
  import { log, withLog } from "./log";
28
+ import {
29
+ deleteProfile,
30
+ importProfile,
31
+ listProfiles,
32
+ loadProfile,
33
+ resolveProfilePath,
34
+ resolveStorageStateOption,
35
+ saveProfile,
36
+ } from "./profiles";
27
37
  import { startBrowserServer } from "./server";
28
- import type { BrowserCliConfig } from "./types";
38
+ import type { BrowserCliConfig, StorageState } from "./types";
29
39
 
30
40
  // ============================================================================
31
41
  // Config Loading
@@ -80,6 +90,8 @@ async function loadConfig(configPath: string): Promise<BrowserCliConfig> {
80
90
  return parseBrowserConfig(exported);
81
91
  }
82
92
 
93
+ // ============================================================================
94
+ // Shared CLI Options
83
95
  // ============================================================================
84
96
  // Shared CLI Options
85
97
  // ============================================================================
@@ -119,16 +131,62 @@ const jsonFlag = flag({
119
131
  description: "Output as JSON instead of text",
120
132
  });
121
133
 
134
+ const profileOption = option({
135
+ long: "profile",
136
+ short: "p",
137
+ type: optional(string),
138
+ description:
139
+ "Load profile and save back on close (use --no-save for read-only)",
140
+ });
141
+
142
+ const noSaveFlag = flag({
143
+ long: "no-save",
144
+ description: "Don't save profile changes on close (read-only)",
145
+ });
146
+
147
+ const globalFlag = flag({
148
+ long: "global",
149
+ description: "Save to user-level global profiles (~/.config/agent-browser/)",
150
+ });
151
+
152
+ const privateFlag = flag({
153
+ long: "private",
154
+ description: "Save to project .private/ (gitignored, for secrets)",
155
+ });
156
+
157
+ const widthOption = option({
158
+ long: "width",
159
+ short: "W",
160
+ type: optional(number),
161
+ description: "Viewport width in pixels (default: 1280)",
162
+ });
163
+
164
+ const heightOption = option({
165
+ long: "height",
166
+ short: "H",
167
+ type: optional(number),
168
+ description: "Viewport height in pixels (default: 720)",
169
+ });
170
+
122
171
  // ============================================================================
123
172
  // Browser Options Resolution
124
173
  // ============================================================================
125
174
 
175
+ type SessionBrowserOptions = AgentBrowserOptions & {
176
+ profile?: string;
177
+ noSave?: boolean;
178
+ };
179
+
126
180
  async function resolveBrowserOptions(args: {
127
181
  configPath?: string;
128
182
  headless?: boolean;
129
183
  headed?: boolean;
130
184
  bundled?: boolean;
131
- }): Promise<AgentBrowserOptions> {
185
+ profile?: string;
186
+ noSave?: boolean;
187
+ width?: number;
188
+ height?: number;
189
+ }): Promise<SessionBrowserOptions> {
132
190
  const configPath = await findConfigPath(args.configPath);
133
191
  const config = configPath ? await loadConfig(configPath) : undefined;
134
192
 
@@ -143,18 +201,30 @@ async function resolveBrowserOptions(args: {
143
201
 
144
202
  const useSystemChrome = args.bundled ? false : config?.useSystemChrome;
145
203
 
204
+ // Resolve storage state from profile or config
205
+ const storageState = resolveStorageStateOption(
206
+ args.profile,
207
+ config?.storageStatePath,
208
+ );
209
+
146
210
  return {
147
211
  headless,
148
212
  executablePath: config?.executablePath,
149
213
  useSystemChrome,
150
214
  allowSystemChromeHeadless: config?.allowSystemChromeHeadless,
151
- viewportWidth: config?.viewportWidth,
152
- viewportHeight: config?.viewportHeight,
215
+ viewportWidth: args.width ?? config?.viewportWidth,
216
+ viewportHeight: args.height ?? config?.viewportHeight,
153
217
  userDataDir: config?.userDataDir,
154
218
  timeout: config?.timeout,
155
219
  captureNetwork: config?.captureNetwork,
156
220
  networkLogLimit: config?.networkLogLimit,
157
- storageStatePath: config?.storageStatePath,
221
+ // Use resolved storage state (object or path)
222
+ storageState: typeof storageState === "object" ? storageState : undefined,
223
+ storageStatePath:
224
+ typeof storageState === "string" ? storageState : undefined,
225
+ // Track profile for save-on-close
226
+ profile: args.profile,
227
+ noSave: args.noSave,
158
228
  };
159
229
  }
160
230
 
@@ -171,6 +241,7 @@ async function resolveBrowserOptions(args: {
171
241
  * press:Enter
172
242
  * scroll:down
173
243
  * scroll:down:500
244
+ * resize:1920:1080
174
245
  */
175
246
  function parseAction(actionStr: string): StepAction {
176
247
  const parts = actionStr.split(":");
@@ -207,6 +278,15 @@ function parseAction(actionStr: string): StepAction {
207
278
  return { type: "select", ref, value };
208
279
  }
209
280
 
281
+ case "resize": {
282
+ const width = Number.parseInt(parts[1], 10);
283
+ const height = Number.parseInt(parts[2], 10);
284
+ if (Number.isNaN(width) || Number.isNaN(height)) {
285
+ throw new Error("resize requires width:height (e.g. resize:1920:1080)");
286
+ }
287
+ return { type: "resize", width, height };
288
+ }
289
+
210
290
  default:
211
291
  throw new Error(`Unknown action type: ${type}`);
212
292
  }
@@ -320,6 +400,10 @@ const openCommand = command({
320
400
  headed: headedFlag,
321
401
  config: configOption,
322
402
  json: jsonFlag,
403
+ profile: profileOption,
404
+ noSave: noSaveFlag,
405
+ width: widthOption,
406
+ height: heightOption,
323
407
  },
324
408
  handler: async (args) => {
325
409
  const browserOptions = await resolveBrowserOptions({
@@ -351,13 +435,16 @@ const openCommand = command({
351
435
  if (args.json) {
352
436
  const jsonData =
353
437
  typeof response.data === "object" && response.data !== null
354
- ? { ...(response.data as object), sessionId }
355
- : { data: response.data, sessionId };
438
+ ? { ...(response.data as object), sessionId, profile: args.profile }
439
+ : { data: response.data, sessionId, profile: args.profile };
356
440
  console.log(JSON.stringify(jsonData, null, 2));
357
441
  } else {
358
442
  if (args.new && sessionId) {
359
443
  console.log(`Session: ${sessionId}`);
360
444
  }
445
+ if (args.profile) {
446
+ console.log(`Profile: ${args.profile}`);
447
+ }
361
448
  console.log(data.text ?? "Navigated successfully");
362
449
  }
363
450
  },
@@ -380,6 +467,7 @@ const actCommand = command({
380
467
  long: "no-state",
381
468
  description: "Don't return state after actions",
382
469
  }),
470
+ profile: profileOption,
383
471
  },
384
472
  handler: async (args) => {
385
473
  if (args.actions.length === 0) {
@@ -494,13 +582,9 @@ const waitCommand = command({
494
582
  process.exit(1);
495
583
  }
496
584
 
497
- const client = new DaemonClient(args.session);
498
- if (!(await client.ping())) {
499
- console.error(
500
- "Daemon not running. Use 'agent-browser open <url>' first.",
501
- );
502
- process.exit(1);
503
- }
585
+ const client = await ensureDaemon(args.session ?? "default", undefined, {
586
+ createIfMissing: false,
587
+ });
504
588
 
505
589
  const response = await client.wait(condition, { timeoutMs: args.timeout });
506
590
 
@@ -527,13 +611,9 @@ const stateCommand = command({
527
611
  json: jsonFlag,
528
612
  },
529
613
  handler: async (args) => {
530
- const client = new DaemonClient(args.session);
531
- if (!(await client.ping())) {
532
- console.error(
533
- "Daemon not running. Use 'agent-browser open <url>' first.",
534
- );
535
- process.exit(1);
536
- }
614
+ const client = await ensureDaemon(args.session ?? "default", undefined, {
615
+ createIfMissing: false,
616
+ });
537
617
 
538
618
  const response = await client.state({
539
619
  format: args.json ? "json" : "text",
@@ -571,13 +651,9 @@ const screenshotCommand = command({
571
651
  }),
572
652
  },
573
653
  handler: async (args) => {
574
- const client = new DaemonClient(args.session);
575
- if (!(await client.ping())) {
576
- console.error(
577
- "Daemon not running. Use 'agent-browser open <url>' first.",
578
- );
579
- process.exit(1);
580
- }
654
+ const client = await ensureDaemon(args.session ?? "default", undefined, {
655
+ createIfMissing: false,
656
+ });
581
657
 
582
658
  const response = await client.screenshot({
583
659
  fullPage: args.fullPage,
@@ -606,6 +682,40 @@ const screenshotCommand = command({
606
682
  },
607
683
  });
608
684
 
685
+ // --- resize ---
686
+ const resizeCommand = command({
687
+ name: "resize",
688
+ description: "Resize browser viewport",
689
+ args: {
690
+ width: positional({ type: number, displayName: "width" }),
691
+ height: positional({ type: number, displayName: "height" }),
692
+ session: sessionOption,
693
+ json: jsonFlag,
694
+ },
695
+ handler: async (args) => {
696
+ const client = await ensureDaemon(args.session ?? "default", undefined, {
697
+ createIfMissing: false,
698
+ });
699
+
700
+ const response = await client.command({
701
+ type: "resize",
702
+ width: args.width,
703
+ height: args.height,
704
+ });
705
+
706
+ if (!response.success) {
707
+ console.error("Error:", response.error);
708
+ process.exit(1);
709
+ }
710
+
711
+ if (args.json) {
712
+ console.log(JSON.stringify({ width: args.width, height: args.height }));
713
+ } else {
714
+ console.log(`Viewport resized to ${args.width}x${args.height}`);
715
+ }
716
+ },
717
+ });
718
+
609
719
  // --- close ---
610
720
  const closeCommand = command({
611
721
  name: "close",
@@ -906,12 +1016,325 @@ const installSkillCommand = command({
906
1016
  },
907
1017
  });
908
1018
 
1019
+ // ============================================================================
1020
+ // Profile Commands
1021
+ // ============================================================================
1022
+
1023
+ const profileListCommand = command({
1024
+ name: "list",
1025
+ description: "List all available profiles",
1026
+ args: {
1027
+ json: jsonFlag,
1028
+ },
1029
+ handler: async (args) => {
1030
+ const profiles = listProfiles();
1031
+
1032
+ if (args.json) {
1033
+ console.log(JSON.stringify(profiles, null, 2));
1034
+ } else {
1035
+ if (profiles.length === 0) {
1036
+ console.log("No profiles found.");
1037
+ console.log("\nCreate a profile with:");
1038
+ console.log(" agent-browser profile login <name> --url <login-url>");
1039
+ console.log(" agent-browser profile save <name>");
1040
+ return;
1041
+ }
1042
+
1043
+ console.log(`Profiles (${profiles.length}):\n`);
1044
+ for (const p of profiles) {
1045
+ const scopeLabel =
1046
+ p.scope === "global"
1047
+ ? "[global]"
1048
+ : p.scope === "local-private"
1049
+ ? "[private]"
1050
+ : "[local]";
1051
+ console.log(` ${p.name} ${scopeLabel}`);
1052
+ if (p.meta?.description) {
1053
+ console.log(` ${p.meta.description}`);
1054
+ }
1055
+ if (p.meta?.origins?.length) {
1056
+ console.log(` Origins: ${p.meta.origins.join(", ")}`);
1057
+ }
1058
+ if (p.meta?.lastUsedAt) {
1059
+ console.log(` Last used: ${p.meta.lastUsedAt}`);
1060
+ }
1061
+ console.log();
1062
+ }
1063
+ }
1064
+ },
1065
+ });
1066
+
1067
+ const profileShowCommand = command({
1068
+ name: "show",
1069
+ description: "Show profile contents",
1070
+ args: {
1071
+ name: positional({ type: string, displayName: "name" }),
1072
+ json: jsonFlag,
1073
+ },
1074
+ handler: async (args) => {
1075
+ const profile = loadProfile(args.name);
1076
+ if (!profile) {
1077
+ console.error(`Profile not found: ${args.name}`);
1078
+ process.exit(1);
1079
+ }
1080
+
1081
+ if (args.json) {
1082
+ console.log(JSON.stringify(profile, null, 2));
1083
+ } else {
1084
+ const resolved = resolveProfilePath(args.name);
1085
+ console.log(`Profile: ${args.name}`);
1086
+ console.log(`Path: ${resolved?.path}`);
1087
+ console.log(`Scope: ${resolved?.scope}`);
1088
+ console.log();
1089
+ if (profile._meta?.description) {
1090
+ console.log(`Description: ${profile._meta.description}`);
1091
+ }
1092
+ if (profile._meta?.origins?.length) {
1093
+ console.log(`Origins: ${profile._meta.origins.join(", ")}`);
1094
+ }
1095
+ if (profile._meta?.createdAt) {
1096
+ console.log(`Created: ${profile._meta.createdAt}`);
1097
+ }
1098
+ if (profile._meta?.lastUsedAt) {
1099
+ console.log(`Last used: ${profile._meta.lastUsedAt}`);
1100
+ }
1101
+ console.log();
1102
+ console.log(`Cookies: ${profile.cookies.length}`);
1103
+ for (const cookie of profile.cookies) {
1104
+ console.log(` - ${cookie.name} (${cookie.domain})`);
1105
+ }
1106
+ console.log();
1107
+ console.log(`LocalStorage origins: ${profile.origins.length}`);
1108
+ for (const origin of profile.origins) {
1109
+ console.log(
1110
+ ` - ${origin.origin}: ${origin.localStorage.length} items`,
1111
+ );
1112
+ }
1113
+ }
1114
+ },
1115
+ });
1116
+
1117
+ const profileSaveCommand = command({
1118
+ name: "save",
1119
+ description: "Save current session storage to a profile",
1120
+ args: {
1121
+ name: positional({ type: string, displayName: "name" }),
1122
+ session: sessionOption,
1123
+ global: globalFlag,
1124
+ private: privateFlag,
1125
+ description: option({
1126
+ long: "description",
1127
+ short: "d",
1128
+ type: optional(string),
1129
+ description: "Profile description",
1130
+ }),
1131
+ },
1132
+ handler: async (args) => {
1133
+ const client = await ensureDaemon(args.session ?? "default", undefined, {
1134
+ createIfMissing: false,
1135
+ });
1136
+
1137
+ // Get storage state from session via command
1138
+ const response = await client.command({
1139
+ type: "saveStorageState",
1140
+ });
1141
+
1142
+ if (!response.success) {
1143
+ console.error("Error:", response.error);
1144
+ process.exit(1);
1145
+ }
1146
+
1147
+ const storageState = response.data as StorageState;
1148
+
1149
+ // Extract origins from storage state
1150
+ const origins = [
1151
+ ...new Set([
1152
+ ...storageState.cookies.map((c) => c.domain),
1153
+ ...storageState.origins.map((o) => o.origin),
1154
+ ]),
1155
+ ].filter(Boolean);
1156
+
1157
+ const savedPath = saveProfile(args.name, storageState, {
1158
+ global: args.global,
1159
+ private: args.private,
1160
+ description: args.description,
1161
+ origins,
1162
+ });
1163
+
1164
+ console.log(`Profile saved: ${args.name}`);
1165
+ console.log(`Path: ${savedPath}`);
1166
+ console.log(`Cookies: ${storageState.cookies.length}`);
1167
+ console.log(`LocalStorage origins: ${storageState.origins.length}`);
1168
+ },
1169
+ });
1170
+
1171
+ const profileDeleteCommand = command({
1172
+ name: "delete",
1173
+ description: "Delete a profile",
1174
+ args: {
1175
+ name: positional({ type: string, displayName: "name" }),
1176
+ },
1177
+ handler: async (args) => {
1178
+ const resolved = resolveProfilePath(args.name);
1179
+ if (!resolved) {
1180
+ console.error(`Profile not found: ${args.name}`);
1181
+ process.exit(1);
1182
+ }
1183
+
1184
+ const deleted = deleteProfile(args.name);
1185
+ if (deleted) {
1186
+ console.log(`Deleted profile: ${args.name}`);
1187
+ console.log(`Path: ${resolved.path}`);
1188
+ } else {
1189
+ console.error(`Failed to delete profile: ${args.name}`);
1190
+ process.exit(1);
1191
+ }
1192
+ },
1193
+ });
1194
+
1195
+ const profileImportCommand = command({
1196
+ name: "import",
1197
+ description: "Import a profile from a storage state JSON file",
1198
+ args: {
1199
+ name: positional({ type: string, displayName: "name" }),
1200
+ path: positional({ type: string, displayName: "path" }),
1201
+ global: globalFlag,
1202
+ private: privateFlag,
1203
+ },
1204
+ handler: async (args) => {
1205
+ try {
1206
+ const savedPath = importProfile(args.name, args.path, {
1207
+ global: args.global,
1208
+ private: args.private,
1209
+ });
1210
+ console.log(`Imported profile: ${args.name}`);
1211
+ console.log(`Path: ${savedPath}`);
1212
+ } catch (err) {
1213
+ console.error("Error:", err instanceof Error ? err.message : String(err));
1214
+ process.exit(1);
1215
+ }
1216
+ },
1217
+ });
1218
+
1219
+ const profileCaptureCommand = command({
1220
+ name: "capture",
1221
+ description: "Open browser, interact manually, then save session to profile",
1222
+ args: {
1223
+ name: positional({ type: string, displayName: "name" }),
1224
+ url: option({
1225
+ long: "url",
1226
+ type: string,
1227
+ description: "URL to navigate to",
1228
+ }),
1229
+ headed: headedFlag,
1230
+ headless: headlessFlag,
1231
+ config: configOption,
1232
+ global: globalFlag,
1233
+ private: privateFlag,
1234
+ description: option({
1235
+ long: "description",
1236
+ short: "d",
1237
+ type: optional(string),
1238
+ description: "Profile description",
1239
+ }),
1240
+ },
1241
+ handler: async (args) => {
1242
+ console.log(`Capturing session for profile: ${args.name}`);
1243
+ console.log(`URL: ${args.url}`);
1244
+ console.log();
1245
+ console.log("Browser will open. Log in or do whatever you need.");
1246
+ console.log("Press Enter here when done to save and close.");
1247
+ console.log();
1248
+
1249
+ // Force headed mode for interactive login
1250
+ const browserOptions = await resolveBrowserOptions({
1251
+ ...args,
1252
+ configPath: args.config,
1253
+ headed: true,
1254
+ headless: false,
1255
+ });
1256
+
1257
+ // Create a new session for login
1258
+ const client = await ensureDaemonNewSession(browserOptions);
1259
+ const sessionId = client.getSessionId();
1260
+
1261
+ // Navigate to login URL
1262
+ const navResponse = await client.act([{ type: "navigate", url: args.url }]);
1263
+ if (!navResponse.success) {
1264
+ console.error("Error navigating:", navResponse.error);
1265
+ await client.closeSession(sessionId!);
1266
+ process.exit(1);
1267
+ }
1268
+
1269
+ // Wait for user to press Enter
1270
+ process.stdout.write("Press Enter when login is complete...");
1271
+ await new Promise<void>((resolve) => {
1272
+ process.stdin.setRawMode?.(false);
1273
+ process.stdin.resume();
1274
+ process.stdin.once("data", () => {
1275
+ resolve();
1276
+ });
1277
+ });
1278
+ console.log();
1279
+
1280
+ // Save storage state
1281
+ const saveResponse = await client.command({
1282
+ type: "saveStorageState",
1283
+ });
1284
+
1285
+ if (!saveResponse.success) {
1286
+ console.error("Error saving storage state:", saveResponse.error);
1287
+ await client.closeSession(sessionId!);
1288
+ process.exit(1);
1289
+ }
1290
+
1291
+ const storageState = saveResponse.data as StorageState;
1292
+
1293
+ // Extract origins from storage state
1294
+ const origins = [
1295
+ ...new Set([
1296
+ ...storageState.cookies.map((c) => c.domain),
1297
+ ...storageState.origins.map((o) => o.origin),
1298
+ ]),
1299
+ ].filter(Boolean);
1300
+
1301
+ const savedPath = saveProfile(args.name, storageState, {
1302
+ global: args.global,
1303
+ private: args.private,
1304
+ description: args.description,
1305
+ origins,
1306
+ });
1307
+
1308
+ // Close the session
1309
+ await client.closeSession(sessionId!);
1310
+
1311
+ console.log();
1312
+ console.log(`Profile saved: ${args.name}`);
1313
+ console.log(`Path: ${savedPath}`);
1314
+ console.log(`Cookies: ${storageState.cookies.length}`);
1315
+ console.log(`LocalStorage origins: ${storageState.origins.length}`);
1316
+ },
1317
+ });
1318
+
1319
+ const profileCommand = subcommands({
1320
+ name: "profile",
1321
+ cmds: {
1322
+ list: profileListCommand,
1323
+ show: profileShowCommand,
1324
+ save: profileSaveCommand,
1325
+ delete: profileDeleteCommand,
1326
+ import: profileImportCommand,
1327
+ capture: profileCaptureCommand,
1328
+ },
1329
+ });
1330
+
909
1331
  // ============================================================================
910
1332
  // Main CLI
911
1333
  // ============================================================================
912
1334
 
913
1335
  const cli = subcommands({
914
1336
  name: "agent-browser",
1337
+ version: VERSION,
915
1338
  cmds: {
916
1339
  // Primary CLI commands (daemon-based)
917
1340
  open: openCommand,
@@ -919,10 +1342,14 @@ const cli = subcommands({
919
1342
  wait: waitCommand,
920
1343
  state: stateCommand,
921
1344
  screenshot: screenshotCommand,
1345
+ resize: resizeCommand,
922
1346
  close: closeCommand,
923
1347
  sessions: sessionsCommand,
924
1348
  status: statusCommand,
925
1349
 
1350
+ // Profile management
1351
+ profile: profileCommand,
1352
+
926
1353
  // Setup & configuration
927
1354
  setup: setupCommand,
928
1355
  "install-skill": installSkillCommand,
@@ -934,10 +1361,11 @@ const cli = subcommands({
934
1361
  });
935
1362
 
936
1363
  run(cli, process.argv.slice(2)).catch((error) => {
937
- log
938
- .withError(error)
939
- .withMetadata({ argv: process.argv.slice(2) })
940
- .error("CLI failed");
941
- console.error(error);
1364
+ // Print clean error message for user-facing errors
1365
+ if (error instanceof Error) {
1366
+ console.error(`Error: ${error.message}`);
1367
+ } else {
1368
+ console.error(error);
1369
+ }
942
1370
  process.exit(1);
943
1371
  });
package/src/commands.ts CHANGED
@@ -121,6 +121,12 @@ const screenshotCommandSchema = z.object({
121
121
  path: z.string().optional(),
122
122
  });
123
123
 
124
+ const resizeCommandSchema = z.object({
125
+ type: z.literal("resize"),
126
+ width: z.number().int().positive(),
127
+ height: z.number().int().positive(),
128
+ });
129
+
124
130
  const saveStorageStateCommandSchema = z.object({
125
131
  type: z.literal("saveStorageState"),
126
132
  path: z.string().optional(),
@@ -178,6 +184,7 @@ export const stepActionSchema = z.discriminatedUnion("type", [
178
184
  waitForElementCommandSchema,
179
185
  screenshotCommandSchema,
180
186
  saveStorageStateCommandSchema,
187
+ resizeCommandSchema,
181
188
  ]);
182
189
 
183
190
  // All commands
@@ -202,6 +209,7 @@ export const commandSchema = z.discriminatedUnion("type", [
202
209
  clearNetworkLogsCommandSchema,
203
210
  enableNetworkCaptureCommandSchema,
204
211
  saveStorageStateCommandSchema,
212
+ resizeCommandSchema,
205
213
  closeCommandSchema,
206
214
  ]);
207
215
 
@@ -280,6 +288,9 @@ export async function executeCommand(
280
288
  case "close":
281
289
  await browser.stop();
282
290
  return;
291
+ case "resize":
292
+ await browser.resize(command.width, command.height);
293
+ return;
283
294
  // Commands that return data
284
295
  case "getState":
285
296
  return browser.getState(command.options);