@vibemastery/zurf 0.2.2 → 0.3.0

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.
@@ -0,0 +1 @@
1
+ export declare function htmlToMarkdown(html: string): Promise<string>;
@@ -0,0 +1,21 @@
1
+ let cached;
2
+ async function getService() {
3
+ if (cached)
4
+ return cached;
5
+ const { default: Turndown } = await import('turndown');
6
+ const service = new Turndown({
7
+ codeBlockStyle: 'fenced',
8
+ headingStyle: 'atx',
9
+ });
10
+ service.remove(['script', 'style', 'iframe', 'noscript']);
11
+ service.addRule('remove-svg', {
12
+ filter: (node) => node.nodeName.toLowerCase() === 'svg',
13
+ replacement: () => '',
14
+ });
15
+ cached = service;
16
+ return service;
17
+ }
18
+ export async function htmlToMarkdown(html) {
19
+ const service = await getService();
20
+ return service.turndown(html);
21
+ }
@@ -0,0 +1,33 @@
1
+ import { resolvePerplexityApiKey } from './config.js';
2
+ export type PerplexityDepth = 'deep' | 'quick';
3
+ export type PerplexityRecency = 'day' | 'hour' | 'month' | 'week' | 'year';
4
+ export interface PerplexityAskOptions {
5
+ depth?: PerplexityDepth;
6
+ domains?: string[];
7
+ question: string;
8
+ recency?: PerplexityRecency;
9
+ }
10
+ export interface PerplexityAskResult {
11
+ answer: string;
12
+ citations: string[];
13
+ model: string;
14
+ }
15
+ export declare class PerplexityClient {
16
+ private readonly apiKey;
17
+ constructor(apiKey: string);
18
+ ask(options: PerplexityAskOptions): Promise<PerplexityAskResult>;
19
+ }
20
+ export declare class MissingPerplexityKeyError extends Error {
21
+ code: string;
22
+ suggestions: string[];
23
+ constructor();
24
+ }
25
+ export declare function createPerplexityClient(options: {
26
+ cwd?: string;
27
+ globalConfigDir: string;
28
+ }): {
29
+ client: PerplexityClient;
30
+ resolution: Extract<ReturnType<typeof resolvePerplexityApiKey>, {
31
+ apiKey: string;
32
+ }>;
33
+ };
@@ -0,0 +1,59 @@
1
+ import { resolvePerplexityApiKey } from './config.js';
2
+ function buildRequestBody(options) {
3
+ const model = options.depth === 'deep' ? 'sonar-pro' : 'sonar';
4
+ const body = {
5
+ messages: [{ content: options.question, role: 'user' }],
6
+ model,
7
+ };
8
+ if (options.recency) {
9
+ body.search_recency_filter = options.recency; // eslint-disable-line camelcase
10
+ }
11
+ if (options.domains && options.domains.length > 0) {
12
+ body.search_domain_filter = options.domains; // eslint-disable-line camelcase
13
+ }
14
+ return body;
15
+ }
16
+ export class PerplexityClient {
17
+ apiKey;
18
+ constructor(apiKey) {
19
+ this.apiKey = apiKey;
20
+ }
21
+ async ask(options) {
22
+ const body = buildRequestBody(options);
23
+ const response = await fetch('https://api.perplexity.ai/chat/completions', {
24
+ body: JSON.stringify(body),
25
+ headers: {
26
+ 'Authorization': `Bearer ${this.apiKey}`,
27
+ 'Content-Type': 'application/json',
28
+ },
29
+ method: 'POST',
30
+ });
31
+ if (!response.ok) {
32
+ const text = await response.text().catch(() => '');
33
+ throw new Error(`Perplexity API error (${response.status}): ${text || response.statusText}`);
34
+ }
35
+ const data = (await response.json());
36
+ const answer = data.choices?.[0]?.message?.content ?? '';
37
+ const citations = data.citations ?? [];
38
+ return { answer, citations, model: data.model };
39
+ }
40
+ }
41
+ export class MissingPerplexityKeyError extends Error {
42
+ code = 'MISSING_PERPLEXITY_KEY';
43
+ suggestions = [
44
+ 'Run `zurf setup` to configure your Perplexity API key',
45
+ 'Or set the PERPLEXITY_API_KEY environment variable',
46
+ ];
47
+ constructor() {
48
+ super('No Perplexity API key found.');
49
+ this.name = 'MissingPerplexityKeyError';
50
+ }
51
+ }
52
+ export function createPerplexityClient(options) {
53
+ const resolution = resolvePerplexityApiKey({ cwd: options.cwd, globalConfigDir: options.globalConfigDir });
54
+ if (resolution.source === 'none') {
55
+ throw new MissingPerplexityKeyError();
56
+ }
57
+ const client = new PerplexityClient(resolution.apiKey);
58
+ return { client, resolution };
59
+ }
@@ -0,0 +1,10 @@
1
+ export type ConfigScope = 'global' | 'local';
2
+ export interface ProviderChoice {
3
+ configured: boolean;
4
+ name: string;
5
+ value: string;
6
+ }
7
+ export declare function selectScope(): Promise<ConfigScope>;
8
+ export declare function selectProviders(providers: ProviderChoice[]): Promise<string[]>;
9
+ export declare function promptApiKey(label: string): Promise<string>;
10
+ export declare function promptProjectId(): Promise<string>;
@@ -0,0 +1,34 @@
1
+ import { checkbox, input, password, select } from '@inquirer/prompts';
2
+ export async function selectScope() {
3
+ return select({
4
+ choices: [
5
+ { name: 'Global (user-wide, recommended)', value: 'global' },
6
+ { name: 'Local (this project only)', value: 'local' },
7
+ ],
8
+ message: 'Where should zurf store your config?',
9
+ });
10
+ }
11
+ export async function selectProviders(providers) {
12
+ const choices = providers.map((p) => ({
13
+ checked: p.configured,
14
+ name: p.configured ? `${p.name} [configured]` : p.name,
15
+ value: p.value,
16
+ }));
17
+ return checkbox({
18
+ choices,
19
+ message: 'Which providers do you want to configure?',
20
+ required: true,
21
+ });
22
+ }
23
+ export async function promptApiKey(label) {
24
+ return password({
25
+ mask: '*',
26
+ message: `${label} API key:`,
27
+ validate: (value) => (value.trim().length > 0 ? true : 'API key cannot be empty'),
28
+ });
29
+ }
30
+ export async function promptProjectId() {
31
+ return input({
32
+ message: 'Browserbase Project ID (optional, press Enter to skip):',
33
+ });
34
+ }
@@ -1,37 +1,142 @@
1
1
  {
2
2
  "commands": {
3
- "config:which": {
3
+ "ask": {
4
4
  "aliases": [],
5
- "args": {},
6
- "description": "Show where the Browserbase API key would be loaded from (no secret printed).\nResolution order: BROWSERBASE_API_KEY, then project .zurf/config.json (walk-up), then global config in the CLI config directory.",
5
+ "args": {
6
+ "question": {
7
+ "description": "The question to ask Perplexity",
8
+ "name": "question",
9
+ "required": true
10
+ }
11
+ },
12
+ "description": "Ask a question and get an AI-powered answer with web citations via Perplexity Sonar.\nReturns an answer with inline citations and a sources list. Use --depth deep for more thorough research.",
7
13
  "examples": [
8
- "<%= config.bin %> config which",
9
- "<%= config.bin %> config which --json"
14
+ "<%= config.bin %> <%= command.id %> \"What is Browserbase?\"",
15
+ "<%= config.bin %> <%= command.id %> \"latest tech news\" --recency day",
16
+ "<%= config.bin %> <%= command.id %> \"search reddit for best CLI tools\" --domains reddit.com",
17
+ "<%= config.bin %> <%= command.id %> \"explain quantum computing\" --depth deep",
18
+ "<%= config.bin %> <%= command.id %> \"What is Node.js?\" --json",
19
+ "<%= config.bin %> <%= command.id %> \"What is oclif?\" --no-citations"
10
20
  ],
11
21
  "flags": {
22
+ "citations": {
23
+ "description": "Show sources list after the answer (use --no-citations to hide)",
24
+ "name": "citations",
25
+ "allowNo": true,
26
+ "type": "boolean"
27
+ },
28
+ "depth": {
29
+ "description": "Search depth: quick (sonar) or deep (sonar-pro)",
30
+ "name": "depth",
31
+ "default": "quick",
32
+ "hasDynamicHelp": false,
33
+ "multiple": false,
34
+ "options": [
35
+ "quick",
36
+ "deep"
37
+ ],
38
+ "type": "option"
39
+ },
40
+ "domains": {
41
+ "description": "Restrict search to these domains (comma-separated)",
42
+ "name": "domains",
43
+ "hasDynamicHelp": false,
44
+ "multiple": false,
45
+ "type": "option"
46
+ },
12
47
  "json": {
13
48
  "description": "Print machine-readable JSON to stdout",
14
49
  "env": "ZURF_JSON",
15
50
  "name": "json",
16
51
  "allowNo": false,
17
52
  "type": "boolean"
53
+ },
54
+ "recency": {
55
+ "description": "Filter sources by recency",
56
+ "name": "recency",
57
+ "hasDynamicHelp": false,
58
+ "multiple": false,
59
+ "options": [
60
+ "hour",
61
+ "day",
62
+ "week",
63
+ "month",
64
+ "year"
65
+ ],
66
+ "type": "option"
18
67
  }
19
68
  },
20
69
  "hasDynamicHelp": false,
21
70
  "hiddenAliases": [],
22
- "id": "config:which",
71
+ "id": "ask",
23
72
  "pluginAlias": "@vibemastery/zurf",
24
73
  "pluginName": "@vibemastery/zurf",
25
74
  "pluginType": "core",
26
75
  "strict": true,
27
- "summary": "Show where the API key is loaded from",
76
+ "summary": "Ask a question via Perplexity Sonar",
28
77
  "enableJsonFlag": false,
29
78
  "isESM": true,
30
79
  "relativePath": [
31
80
  "dist",
32
81
  "commands",
33
- "config",
34
- "which.js"
82
+ "ask",
83
+ "index.js"
84
+ ]
85
+ },
86
+ "browse": {
87
+ "aliases": [],
88
+ "args": {
89
+ "url": {
90
+ "description": "URL to browse",
91
+ "name": "url",
92
+ "required": true
93
+ }
94
+ },
95
+ "description": "Browse a URL in a cloud browser and return the rendered content as markdown (default) or raw HTML.\nUses a real Chromium browser via Browserbase, so JavaScript-heavy pages are fully rendered.\nRequires authentication and a Project ID. Run `zurf setup` before first use.",
96
+ "examples": [
97
+ "<%= config.bin %> <%= command.id %> https://example.com",
98
+ "<%= config.bin %> <%= command.id %> https://example.com --html",
99
+ "<%= config.bin %> <%= command.id %> https://example.com --json",
100
+ "<%= config.bin %> <%= command.id %> https://example.com -o page.md"
101
+ ],
102
+ "flags": {
103
+ "html": {
104
+ "description": "Output raw HTML instead of markdown",
105
+ "env": "ZURF_HTML",
106
+ "name": "html",
107
+ "allowNo": false,
108
+ "type": "boolean"
109
+ },
110
+ "json": {
111
+ "description": "Print machine-readable JSON to stdout",
112
+ "env": "ZURF_JSON",
113
+ "name": "json",
114
+ "allowNo": false,
115
+ "type": "boolean"
116
+ },
117
+ "output": {
118
+ "char": "o",
119
+ "description": "Write rendered content to this file (full content); otherwise human mode prints a truncated preview to stdout",
120
+ "name": "output",
121
+ "hasDynamicHelp": false,
122
+ "multiple": false,
123
+ "type": "option"
124
+ }
125
+ },
126
+ "hasDynamicHelp": false,
127
+ "hiddenAliases": [],
128
+ "id": "browse",
129
+ "pluginAlias": "@vibemastery/zurf",
130
+ "pluginName": "@vibemastery/zurf",
131
+ "pluginType": "core",
132
+ "strict": true,
133
+ "summary": "Browse a URL in a cloud browser and return rendered content as markdown",
134
+ "isESM": true,
135
+ "relativePath": [
136
+ "dist",
137
+ "commands",
138
+ "browse",
139
+ "index.js"
35
140
  ]
36
141
  },
37
142
  "fetch": {
@@ -43,13 +148,21 @@
43
148
  "required": true
44
149
  }
45
150
  },
46
- "description": "Fetch a URL via Browserbase (no browser session; static HTML, 1 MB max).\nRequires authentication. Run `zurf init --global` or use a project key before first use.",
151
+ "description": "Fetch a URL via Browserbase and return content as markdown (default) or raw HTML (no browser session; static HTML, 1 MB max).\nRequires authentication. Run `zurf setup` or use a project key before first use.",
47
152
  "examples": [
48
153
  "<%= config.bin %> <%= command.id %> https://example.com",
154
+ "<%= config.bin %> <%= command.id %> https://example.com --html",
49
155
  "<%= config.bin %> <%= command.id %> https://example.com --json",
50
- "<%= config.bin %> <%= command.id %> https://example.com -o page.html --proxies"
156
+ "<%= config.bin %> <%= command.id %> https://example.com -o page.md --proxies"
51
157
  ],
52
158
  "flags": {
159
+ "html": {
160
+ "description": "Output raw HTML instead of markdown",
161
+ "env": "ZURF_HTML",
162
+ "name": "html",
163
+ "allowNo": false,
164
+ "type": "boolean"
165
+ },
53
166
  "json": {
54
167
  "description": "Print machine-readable JSON to stdout",
55
168
  "env": "ZURF_JSON",
@@ -91,7 +204,7 @@
91
204
  "pluginName": "@vibemastery/zurf",
92
205
  "pluginType": "core",
93
206
  "strict": true,
94
- "summary": "Fetch a URL via Browserbase",
207
+ "summary": "Fetch a URL via Browserbase and return content as markdown",
95
208
  "isESM": true,
96
209
  "relativePath": [
97
210
  "dist",
@@ -100,64 +213,45 @@
100
213
  "index.js"
101
214
  ]
102
215
  },
103
- "init": {
216
+ "config:which": {
104
217
  "aliases": [],
105
218
  "args": {},
106
- "description": "Save your Browserbase API key to global or project config.\nGlobal path follows oclif config (same as `zurf config which`).",
219
+ "description": "Show where API keys would be loaded from (no secrets printed).\nResolution order: env var → project .zurf/config.json (walk-up) global config.",
107
220
  "examples": [
108
- "<%= config.bin %> <%= command.id %> --global",
109
- "<%= config.bin %> <%= command.id %> --local",
110
- "printenv BROWSERBASE_API_KEY | <%= config.bin %> <%= command.id %> --global"
221
+ "<%= config.bin %> config which",
222
+ "<%= config.bin %> config which --json"
111
223
  ],
112
224
  "flags": {
225
+ "html": {
226
+ "description": "Output raw HTML instead of markdown",
227
+ "env": "ZURF_HTML",
228
+ "name": "html",
229
+ "allowNo": false,
230
+ "type": "boolean"
231
+ },
113
232
  "json": {
114
233
  "description": "Print machine-readable JSON to stdout",
115
234
  "env": "ZURF_JSON",
116
235
  "name": "json",
117
236
  "allowNo": false,
118
237
  "type": "boolean"
119
- },
120
- "api-key": {
121
- "description": "API key for non-interactive use. Prefer piping stdin or using a TTY prompt — values on the command line are visible in shell history and process listings.",
122
- "name": "api-key",
123
- "hasDynamicHelp": false,
124
- "multiple": false,
125
- "type": "option"
126
- },
127
- "gitignore": {
128
- "description": "Append .zurf/ to ./.gitignore if that entry is missing",
129
- "name": "gitignore",
130
- "allowNo": false,
131
- "type": "boolean"
132
- },
133
- "global": {
134
- "description": "Store API key in user config (oclif config dir for this CLI)",
135
- "name": "global",
136
- "allowNo": false,
137
- "type": "boolean"
138
- },
139
- "local": {
140
- "description": "Store API key in ./.zurf/config.json for this directory",
141
- "name": "local",
142
- "allowNo": false,
143
- "type": "boolean"
144
238
  }
145
239
  },
146
240
  "hasDynamicHelp": false,
147
241
  "hiddenAliases": [],
148
- "id": "init",
242
+ "id": "config:which",
149
243
  "pluginAlias": "@vibemastery/zurf",
150
244
  "pluginName": "@vibemastery/zurf",
151
245
  "pluginType": "core",
152
246
  "strict": true,
153
- "summary": "Configure Browserbase API key storage",
247
+ "summary": "Show where API keys are loaded from",
154
248
  "enableJsonFlag": false,
155
249
  "isESM": true,
156
250
  "relativePath": [
157
251
  "dist",
158
252
  "commands",
159
- "init",
160
- "index.js"
253
+ "config",
254
+ "which.js"
161
255
  ]
162
256
  },
163
257
  "search": {
@@ -169,12 +263,19 @@
169
263
  "required": true
170
264
  }
171
265
  },
172
- "description": "Search the web via Browserbase (Exa-powered).\nRequires authentication. Run `zurf init --global` or use a project key before first use.",
266
+ "description": "Search the web via Browserbase (Exa-powered).\nRequires authentication. Run `zurf setup` or use a project key before first use.",
173
267
  "examples": [
174
268
  "<%= config.bin %> <%= command.id %> \"browserbase documentation\"",
175
269
  "<%= config.bin %> <%= command.id %> \"laravel inertia\" --num-results 5 --json"
176
270
  ],
177
271
  "flags": {
272
+ "html": {
273
+ "description": "Output raw HTML instead of markdown",
274
+ "env": "ZURF_HTML",
275
+ "name": "html",
276
+ "allowNo": false,
277
+ "type": "boolean"
278
+ },
178
279
  "json": {
179
280
  "description": "Print machine-readable JSON to stdout",
180
281
  "env": "ZURF_JSON",
@@ -207,7 +308,54 @@
207
308
  "search",
208
309
  "index.js"
209
310
  ]
311
+ },
312
+ "setup": {
313
+ "aliases": [],
314
+ "args": {},
315
+ "description": "Interactive setup wizard for configuring API keys for all providers (Browserbase, Perplexity).\nStores keys in global or local config. Re-run to update or add providers.",
316
+ "examples": [
317
+ "<%= config.bin %> <%= command.id %>",
318
+ "<%= config.bin %> <%= command.id %> --global",
319
+ "<%= config.bin %> <%= command.id %> --local"
320
+ ],
321
+ "flags": {
322
+ "global": {
323
+ "description": "Store config in user config directory (skip scope prompt)",
324
+ "name": "global",
325
+ "allowNo": false,
326
+ "type": "boolean"
327
+ },
328
+ "json": {
329
+ "description": "Print machine-readable JSON to stdout",
330
+ "env": "ZURF_JSON",
331
+ "name": "json",
332
+ "allowNo": false,
333
+ "type": "boolean"
334
+ },
335
+ "local": {
336
+ "description": "Store config in .zurf/config.json in the current directory (skip scope prompt)",
337
+ "name": "local",
338
+ "allowNo": false,
339
+ "type": "boolean"
340
+ }
341
+ },
342
+ "hasDynamicHelp": false,
343
+ "hiddenAliases": [],
344
+ "id": "setup",
345
+ "pluginAlias": "@vibemastery/zurf",
346
+ "pluginName": "@vibemastery/zurf",
347
+ "pluginType": "core",
348
+ "strict": true,
349
+ "summary": "Configure API keys for Browserbase and Perplexity",
350
+ "enableJsonFlag": false,
351
+ "isESM": true,
352
+ "relativePath": [
353
+ "dist",
354
+ "commands",
355
+ "setup",
356
+ "index.js"
357
+ ]
210
358
  }
211
359
  },
212
- "version": "0.2.2"
360
+ "version": "0.3.0"
213
361
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vibemastery/zurf",
3
3
  "description": "A lightweight CLI for searching and fetching web pages, powered by Browserbase.",
4
- "version": "0.2.2",
4
+ "version": "0.3.0",
5
5
  "author": "Luis Güette",
6
6
  "bin": {
7
7
  "zurf": "./bin/run.js"
@@ -9,12 +9,15 @@
9
9
  "bugs": "https://github.com/vibemastery/zurf/issues",
10
10
  "dependencies": {
11
11
  "@browserbasehq/sdk": "^2.9.0",
12
+ "@inquirer/prompts": "^8.3.2",
12
13
  "@oclif/core": "^4",
13
14
  "@oclif/plugin-autocomplete": "^3.2.42",
14
15
  "@oclif/plugin-help": "^6",
15
16
  "@oclif/plugin-not-found": "^3.2.77",
16
17
  "@oclif/plugin-plugins": "^5",
17
- "@oclif/plugin-warn-if-update-available": "^3.1.57"
18
+ "@oclif/plugin-warn-if-update-available": "^3.1.57",
19
+ "playwright-core": "^1.58.2",
20
+ "turndown": "^7.2.2"
18
21
  },
19
22
  "devDependencies": {
20
23
  "@eslint/compat": "^1",
@@ -23,6 +26,7 @@
23
26
  "@types/chai": "^4",
24
27
  "@types/mocha": "^10",
25
28
  "@types/node": "^18",
29
+ "@types/turndown": "^5.0.6",
26
30
  "chai": "^4",
27
31
  "eslint": "^9",
28
32
  "eslint-config-oclif": "^6",
@@ -70,8 +74,8 @@
70
74
  "config": {
71
75
  "description": "Inspect zurf configuration"
72
76
  },
73
- "init": {
74
- "description": "Configure Browserbase API key storage"
77
+ "setup": {
78
+ "description": "Configure API keys for Browserbase and Perplexity"
75
79
  }
76
80
  }
77
81
  },
@@ -1,95 +0,0 @@
1
- import { Command, Flags } from '@oclif/core';
2
- import * as fs from 'node:fs/promises';
3
- import { cliError, errorMessage } from '../../lib/cli-errors.js';
4
- import { globalConfigFilePath, localConfigPathForCwd, writeApiKeyConfig } from '../../lib/config.js';
5
- import { zurfBaseFlags } from '../../lib/flags.js';
6
- import { dotGitignoreMentionsZurf, ensureZurfGitignoreEntry, gitignorePathForCwd, } from '../../lib/gitignore-zurf.js';
7
- import { promptLine, readStdinIfPiped } from '../../lib/init-input.js';
8
- import { printJson } from '../../lib/json-output.js';
9
- export default class Init extends Command {
10
- static description = `Save your Browserbase API key to global or project config.
11
- Global path follows oclif config (same as \`zurf config which\`).`;
12
- static examples = [
13
- '<%= config.bin %> <%= command.id %> --global',
14
- '<%= config.bin %> <%= command.id %> --local',
15
- 'printenv BROWSERBASE_API_KEY | <%= config.bin %> <%= command.id %> --global',
16
- ];
17
- static flags = {
18
- ...zurfBaseFlags,
19
- 'api-key': Flags.string({
20
- description: 'API key for non-interactive use. Prefer piping stdin or using a TTY prompt — values on the command line are visible in shell history and process listings.',
21
- }),
22
- gitignore: Flags.boolean({
23
- description: 'Append .zurf/ to ./.gitignore if that entry is missing',
24
- }),
25
- global: Flags.boolean({
26
- description: 'Store API key in user config (oclif config dir for this CLI)',
27
- exactlyOne: ['global', 'local'],
28
- }),
29
- local: Flags.boolean({
30
- description: 'Store API key in ./.zurf/config.json for this directory',
31
- exactlyOne: ['global', 'local'],
32
- }),
33
- };
34
- static summary = 'Configure Browserbase API key storage';
35
- async run() {
36
- const { flags } = await this.parse(Init);
37
- const apiKey = await this.readApiKeyForInit(flags);
38
- const targetPath = flags.global
39
- ? globalConfigFilePath(this.config.configDir)
40
- : localConfigPathForCwd();
41
- try {
42
- await writeApiKeyConfig(targetPath, apiKey);
43
- }
44
- catch (error) {
45
- cliError({ command: this, exitCode: 1, json: flags.json, message: errorMessage(error) });
46
- }
47
- if (flags.gitignore) {
48
- try {
49
- await ensureZurfGitignoreEntry(gitignorePathForCwd());
50
- }
51
- catch (error) {
52
- cliError({ command: this, exitCode: 1, json: flags.json, message: errorMessage(error) });
53
- }
54
- }
55
- if (flags.json) {
56
- printJson({ ok: true, path: targetPath, scope: flags.global ? 'global' : 'local' });
57
- }
58
- else {
59
- this.log(`Saved API key to ${targetPath}`);
60
- if (flags.local) {
61
- let showTip = true;
62
- try {
63
- const gi = await fs.readFile(gitignorePathForCwd(), 'utf8');
64
- if (dotGitignoreMentionsZurf(gi)) {
65
- showTip = false;
66
- }
67
- }
68
- catch {
69
- // no .gitignore yet
70
- }
71
- if (showTip) {
72
- this.log('Tip: add .zurf/ to .gitignore so the key is not committed (or run with --gitignore).');
73
- }
74
- }
75
- }
76
- }
77
- async readApiKeyForInit(flags) {
78
- let apiKey = flags['api-key']?.trim();
79
- if (!apiKey) {
80
- apiKey = await readStdinIfPiped();
81
- }
82
- if (!apiKey && process.stdin.isTTY) {
83
- apiKey = await promptLine('Browserbase API key: ');
84
- }
85
- if (!apiKey) {
86
- cliError({
87
- command: this,
88
- exitCode: 2,
89
- json: flags.json,
90
- message: 'No API key provided. Pipe stdin, use --api-key, or run interactively in a TTY.',
91
- });
92
- }
93
- return apiKey;
94
- }
95
- }