octocode-cli 1.3.1 → 1.5.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.
Files changed (102) hide show
  1. package/README.md +193 -34
  2. package/out/chunks/chunk-7476PETK.js +309 -0
  3. package/out/chunks/chunk-CVNNNSMQ.js +26 -0
  4. package/out/chunks/chunk-OQBJTZWK.js +60 -0
  5. package/out/chunks/chunk-UCZCF3BQ.js +9 -0
  6. package/out/chunks/command-help-specs-JZXVSLZ5.js +8 -0
  7. package/out/chunks/commands-XBFPLHSQ.js +8 -0
  8. package/out/chunks/help-P7TCOYAJ.js +10 -0
  9. package/out/chunks/main-help-ULF5PAQY.js +10 -0
  10. package/out/chunks/prompts-5E6VKRX5.js +8 -0
  11. package/out/chunks/spinner-URV2OX6O.js +8 -0
  12. package/out/chunks/tool-command-M6VA7P2F.js +8 -0
  13. package/out/octocode-cli.js +1 -1
  14. package/package.json +5 -3
  15. package/skills/README.md +13 -1
  16. package/skills/agentic-flow-best-practices/SKILL.md +280 -0
  17. package/skills/agentic-flow-best-practices/references/agent-collaboration-patterns.md +75 -0
  18. package/skills/agentic-flow-best-practices/references/pr-review-agent-example.md +47 -0
  19. package/skills/agentic-flow-best-practices/references/resources.md +112 -0
  20. package/skills/octocode-chrome-devtools/README.md +541 -0
  21. package/skills/octocode-chrome-devtools/SKILL.md +197 -0
  22. package/skills/octocode-chrome-devtools/agents/openai.yaml +7 -0
  23. package/skills/octocode-chrome-devtools/references/CDP_AGENT_REFERENCE.md +401 -0
  24. package/skills/octocode-chrome-devtools/references/CHROME_FLAGS.md +234 -0
  25. package/skills/octocode-chrome-devtools/references/INTENTS.md +108 -0
  26. package/skills/octocode-chrome-devtools/references/INTENTS_AUTH.md +179 -0
  27. package/skills/octocode-chrome-devtools/references/INTENTS_AUTOMATION.md +214 -0
  28. package/skills/octocode-chrome-devtools/references/INTENTS_DEBUG.md +329 -0
  29. package/skills/octocode-chrome-devtools/references/INTENTS_ENVIRONMENT.md +237 -0
  30. package/skills/octocode-chrome-devtools/references/INTENTS_INSPECT.md +263 -0
  31. package/skills/octocode-chrome-devtools/references/INTENTS_STORAGE_CONSENT.md +214 -0
  32. package/skills/octocode-chrome-devtools/references/RECOVERY.md +39 -0
  33. package/skills/octocode-chrome-devtools/references/SCRIPT_PATTERNS.md +43 -0
  34. package/skills/octocode-chrome-devtools/references/SCRIPT_PATTERNS_ASYNC_WORKERS.md +345 -0
  35. package/skills/octocode-chrome-devtools/references/SCRIPT_PATTERNS_BROWSER.md +403 -0
  36. package/skills/octocode-chrome-devtools/references/SCRIPT_PATTERNS_OBSERVE.md +275 -0
  37. package/skills/octocode-chrome-devtools/references/SCRIPT_PATTERNS_SPECIAL.md +18 -0
  38. package/skills/octocode-chrome-devtools/scripts/cdp-runner.mjs +503 -0
  39. package/skills/octocode-chrome-devtools/scripts/cdp-sandbox.mjs +123 -0
  40. package/skills/octocode-chrome-devtools/scripts/cdp-template.mjs +81 -0
  41. package/skills/octocode-chrome-devtools/scripts/octocode-chrome-devtools.vpn.example.json +8 -0
  42. package/skills/octocode-chrome-devtools/scripts/open-browser.mjs +362 -0
  43. package/skills/octocode-chrome-devtools/scripts/sourcemap-resolver.mjs +193 -0
  44. package/skills/octocode-chrome-devtools/scripts/undercover.mjs +226 -0
  45. package/skills/octocode-design/README.md +2 -2
  46. package/skills/octocode-documentation-writer/README.md +1 -1
  47. package/skills/octocode-engineer/README.md +1 -1
  48. package/skills/octocode-engineer/SKILL.md +4 -2
  49. package/skills/octocode-engineer/references/output-files.md +3 -3
  50. package/skills/octocode-engineer/scripts/run.js +136 -136
  51. package/skills/octocode-engineer/src/reporting/summary-md.test.ts +48 -0
  52. package/skills/octocode-engineer/src/reporting/summary-md.ts +43 -2
  53. package/skills/octocode-install/SKILL.md +1 -1
  54. package/skills/octocode-pull-request-reviewer/README.md +5 -5
  55. package/skills/octocode-pull-request-reviewer/SKILL.md +1 -1
  56. package/skills/octocode-research/AGENTS.md +1 -1
  57. package/skills/octocode-research/README.md +2 -2
  58. package/skills/octocode-research/docs/ARCHITECTURE.md +1 -1
  59. package/skills/octocode-research/scripts/server.js +191 -246
  60. package/skills/octocode-research/src/routes/github.ts +4 -21
  61. package/skills/octocode-research/src/routes/local.ts +4 -21
  62. package/skills/octocode-research/src/utils/fileContentTransform.ts +44 -0
  63. package/skills/octocode-search-skill/SKILL.md +245 -229
  64. package/skills/octocode-search-skill/references/agent-skills-guide.md +177 -0
  65. package/skills/octocode-search-skill/references/discovery-surfaces.md +162 -0
  66. package/skills/octocode-search-skill/references/fetch-and-create-locally.md +57 -0
  67. package/skills/octocode-search-skill/references/install-reference.md +130 -0
  68. package/skills/octocode-search-skill/references/references-template.md +27 -0
  69. package/skills/octocode-search-skill/references/references.md +62 -0
  70. package/skills/octocode-slides/README.md +307 -0
  71. package/skills/octocode-slides/SKILL.md +410 -0
  72. package/skills/octocode-slides/references/01-brief.md +156 -0
  73. package/skills/octocode-slides/references/02-research.md +149 -0
  74. package/skills/octocode-slides/references/03-outline.md +172 -0
  75. package/skills/octocode-slides/references/04-design.md +301 -0
  76. package/skills/octocode-slides/references/05-implementation.md +213 -0
  77. package/skills/octocode-slides/references/06-review.md +258 -0
  78. package/skills/octocode-slides/references/animation.md +281 -0
  79. package/skills/octocode-slides/references/design-system.md +316 -0
  80. package/skills/octocode-slides/references/html-templates.md +673 -0
  81. package/skills/octocode-slides/references/image-generation.md +448 -0
  82. package/skills/octocode-slides/references/resources.md +840 -0
  83. package/skills/octocode-slides/references/slide-rules.md +541 -0
  84. package/skills/octocode-slides/references/wireframes.md +727 -0
  85. package/skills/octocode-slides/scripts/animation.js +182 -0
  86. package/skills/octocode-slides/scripts/base.css +353 -0
  87. package/skills/octocode-slides/scripts/base.html +655 -0
  88. package/skills/octocode-slides/scripts/generate_image.py +221 -0
  89. package/skills/octocode-slides/scripts/navbridge.js +79 -0
  90. package/skills/octocode-slides/scripts/presenter.js +316 -0
  91. package/skills/octocode-slides/scripts/slide.html +248 -0
  92. package/skills/octocode-stats/SKILL.md +73 -0
  93. package/skills/octocode-stats/assets/template.html +1332 -0
  94. package/skills/octocode-stats/scripts/build_dashboard.mjs +407 -0
  95. package/assets/example.png +0 -0
  96. package/out/chunks/chunk-QCY7Q7YW.js +0 -389
  97. package/out/chunks/command-help-specs-CQ3RBLP6.js +0 -8
  98. package/out/chunks/commands-OCTZP2TO.js +0 -51
  99. package/out/chunks/help-XPXP46ZT.js +0 -10
  100. package/out/chunks/main-help-35HX2UDQ.js +0 -10
  101. package/out/chunks/tool-command-HOSMVLNK.js +0 -8
  102. package/skills/octocode-search-skill/INSTALL_REFERENCE.md +0 -112
@@ -0,0 +1,407 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * build_dashboard.mjs
4
+ *
5
+ * Read Octocode MCP `stats.json`, pre-compute every dashboard metric, embed the
6
+ * resulting JSON into the HTML template, write the result to disk, and
7
+ * optionally open it in the default browser.
8
+ *
9
+ * Schema source of truth:
10
+ * packages/octocode-shared/src/session/schemas.ts
11
+ *
12
+ * Usage:
13
+ * node build_dashboard.mjs [--stats <path>] [--output <path>]
14
+ * [--template <path>] [--no-open] [--allow-empty]
15
+ */
16
+
17
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
18
+ import { resolve, dirname, join, isAbsolute } from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { homedir, platform } from 'node:os';
21
+ import { spawn } from 'node:child_process';
22
+
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = dirname(__filename);
25
+
26
+ // Universal heuristic used by OpenAI / Anthropic tokenizers (~4 chars per token
27
+ // for English text and code). Actual token usage varies by model and content —
28
+ // surfaced as an explicit disclaimer on the dashboard.
29
+ const TOKEN_CHAR_RATIO = 4;
30
+ const TOKEN_ESTIMATE_NOTE =
31
+ `Token counts are estimated at ~${TOKEN_CHAR_RATIO} chars per token. ` +
32
+ `Actual usage varies by model and content.`;
33
+
34
+ const SENTINEL = '__OCTOCODE_STATS_DATA__';
35
+
36
+ const DEFAULTS = {
37
+ stats:
38
+ process.env.OCTOCODE_HOME
39
+ ? join(process.env.OCTOCODE_HOME, 'stats.json')
40
+ : join(homedir(), '.octocode', 'stats.json'),
41
+ output: join(process.cwd(), '.octocode', 'stats', 'dashboard.html'),
42
+ template: resolve(__dirname, '..', 'assets', 'template.html'),
43
+ open: true,
44
+ allowEmpty: false,
45
+ };
46
+
47
+ function parseArgs(argv) {
48
+ const opts = { ...DEFAULTS };
49
+ const args = argv.slice(2);
50
+ for (let i = 0; i < args.length; i++) {
51
+ const a = args[i];
52
+ switch (a) {
53
+ case '--stats':
54
+ opts.stats = absolutize(args[++i]);
55
+ break;
56
+ case '--output':
57
+ opts.output = absolutize(args[++i]);
58
+ break;
59
+ case '--template':
60
+ opts.template = absolutize(args[++i]);
61
+ break;
62
+ case '--no-open':
63
+ opts.open = false;
64
+ break;
65
+ case '--allow-empty':
66
+ opts.allowEmpty = true;
67
+ break;
68
+ case '-h':
69
+ case '--help':
70
+ printHelp();
71
+ process.exit(0);
72
+ break;
73
+ default:
74
+ if (a.startsWith('--')) {
75
+ die(`Unknown flag: ${a}`);
76
+ }
77
+ die(`Unexpected argument: ${a}`);
78
+ }
79
+ }
80
+ return opts;
81
+ }
82
+
83
+ function absolutize(p) {
84
+ if (!p) die('Missing value for flag');
85
+ return isAbsolute(p) ? p : resolve(process.cwd(), p);
86
+ }
87
+
88
+ function printHelp() {
89
+ process.stdout.write(`build_dashboard.mjs — render Octocode MCP stats as an HTML dashboard
90
+
91
+ Options:
92
+ --stats <path> Path to stats.json
93
+ (default: $OCTOCODE_HOME/stats.json or ~/.octocode/stats.json)
94
+ --output <path> Output HTML path
95
+ (default: ./.octocode/stats/dashboard.html)
96
+ --template <path> HTML template
97
+ (default: <skill>/assets/template.html)
98
+ --no-open Do not auto-open the dashboard in the default browser
99
+ --allow-empty Render an empty-state dashboard if stats.json is missing
100
+ -h, --help Show this help
101
+ `);
102
+ }
103
+
104
+ function die(msg) {
105
+ process.stderr.write(`error: ${msg}\n`);
106
+ process.exit(1);
107
+ }
108
+
109
+ function readStats(statsPath, allowEmpty) {
110
+ if (!existsSync(statsPath)) {
111
+ if (!allowEmpty) {
112
+ die(
113
+ `stats.json not found at ${statsPath}\n` +
114
+ `Run any Octocode MCP tool first, or pass --allow-empty to render an empty dashboard.`
115
+ );
116
+ }
117
+ return { present: false, stats: emptyStats() };
118
+ }
119
+ let raw;
120
+ try {
121
+ raw = readFileSync(statsPath, 'utf8');
122
+ } catch (e) {
123
+ die(`failed to read ${statsPath}: ${e.message}`);
124
+ }
125
+ let parsed;
126
+ try {
127
+ parsed = JSON.parse(raw);
128
+ } catch (e) {
129
+ die(`failed to parse JSON in ${statsPath}: ${e.message}`);
130
+ }
131
+ const stats = parsed?.stats ?? parsed;
132
+ if (!stats || typeof stats !== 'object') {
133
+ die(`unexpected stats.json shape — missing "stats" object`);
134
+ }
135
+ return { present: true, stats, version: parsed?.version };
136
+ }
137
+
138
+ function readSessionMeta(statsPath) {
139
+ // session.json lives next to stats.json in OCTOCODE_HOME
140
+ const sessionPath = join(dirname(statsPath), 'session.json');
141
+ if (!existsSync(sessionPath)) return null;
142
+ try {
143
+ const parsed = JSON.parse(readFileSync(sessionPath, 'utf8'));
144
+ if (!parsed || typeof parsed !== 'object') return null;
145
+ return {
146
+ sessionId: typeof parsed.sessionId === 'string' ? parsed.sessionId : null,
147
+ createdAt: typeof parsed.createdAt === 'string' ? parsed.createdAt : null,
148
+ lastActiveAt:
149
+ typeof parsed.lastActiveAt === 'string' ? parsed.lastActiveAt : null,
150
+ sessionPath,
151
+ };
152
+ } catch {
153
+ return null;
154
+ }
155
+ }
156
+
157
+ function emptyStats() {
158
+ return {
159
+ toolCalls: 0,
160
+ promptCalls: 0,
161
+ errors: 0,
162
+ rateLimits: 0,
163
+ rateLimitsByProvider: {},
164
+ charsSavedByTool: {},
165
+ githubCacheHits: { hits: {}, rateLimits: 0 },
166
+ packageRegistryFailures: {},
167
+ };
168
+ }
169
+
170
+ function safeNumber(n) {
171
+ return typeof n === 'number' && Number.isFinite(n) ? n : 0;
172
+ }
173
+
174
+ function safeRecord(rec) {
175
+ return rec && typeof rec === 'object' ? rec : {};
176
+ }
177
+
178
+ function computeDashboard(stats, ctx) {
179
+ const charsSavedByTool = safeRecord(stats.charsSavedByTool);
180
+ const githubCacheHits = stats.githubCacheHits ?? { hits: {}, rateLimits: 0 };
181
+ const cacheHitsMap = safeRecord(githubCacheHits.hits);
182
+ const rateLimitsByProvider = safeRecord(stats.rateLimitsByProvider);
183
+ const packageRegistryFailures = safeRecord(stats.packageRegistryFailures);
184
+ const totalUsage = stats.totalUsage;
185
+
186
+ const toolEntries = Object.entries(charsSavedByTool).map(([name, t]) => {
187
+ const rawChars = safeNumber(t?.rawChars);
188
+ const responseChars = safeNumber(t?.responseChars);
189
+ const savedChars = safeNumber(t?.savedChars);
190
+ const calls = safeNumber(t?.calls);
191
+ return {
192
+ name,
193
+ calls,
194
+ rawChars,
195
+ responseChars,
196
+ savedChars,
197
+ savingsPct: rawChars > 0 ? (savedChars / rawChars) * 100 : 0,
198
+ estimatedTokensSaved: Math.round(savedChars / TOKEN_CHAR_RATIO),
199
+ };
200
+ });
201
+
202
+ const aggregated = toolEntries.reduce(
203
+ (acc, t) => {
204
+ acc.rawChars += t.rawChars;
205
+ acc.responseChars += t.responseChars;
206
+ acc.savedChars += t.savedChars;
207
+ acc.charSavingsCalls += t.calls;
208
+ return acc;
209
+ },
210
+ { rawChars: 0, responseChars: 0, savedChars: 0, charSavingsCalls: 0 }
211
+ );
212
+
213
+ const rawChars = safeNumber(totalUsage?.rawChars) || aggregated.rawChars;
214
+ const responseChars =
215
+ safeNumber(totalUsage?.responseChars) || aggregated.responseChars;
216
+ const savedChars = safeNumber(totalUsage?.savedChars) || aggregated.savedChars;
217
+ const charSavingsCalls =
218
+ safeNumber(totalUsage?.charSavingsCalls) || aggregated.charSavingsCalls;
219
+
220
+ const cacheHitsArr = Object.entries(cacheHitsMap)
221
+ .map(([endpoint, count]) => ({ endpoint, count: safeNumber(count) }))
222
+ .filter((e) => e.count > 0)
223
+ .sort((a, b) => b.count - a.count);
224
+
225
+ const cacheHitsTotal =
226
+ safeNumber(totalUsage?.githubCacheHits) ||
227
+ cacheHitsArr.reduce((s, e) => s + e.count, 0);
228
+
229
+ const cacheRateLimits =
230
+ safeNumber(totalUsage?.githubCacheRateLimits) ||
231
+ safeNumber(githubCacheHits.rateLimits);
232
+
233
+ const rateLimitsByProviderArr = Object.entries(rateLimitsByProvider)
234
+ .map(([provider, count]) => ({ provider, count: safeNumber(count) }))
235
+ .filter((e) => e.count > 0)
236
+ .sort((a, b) => b.count - a.count);
237
+
238
+ const packageRegistryFailuresArr = Object.entries(packageRegistryFailures)
239
+ .map(([registry, count]) => ({ registry, count: safeNumber(count) }))
240
+ .filter((e) => e.count > 0)
241
+ .sort((a, b) => b.count - a.count);
242
+
243
+ toolEntries.sort((a, b) => b.calls - a.calls || b.savedChars - a.savedChars);
244
+ const savingToolEntries = toolEntries.filter((t) => t.savedChars > 0);
245
+ const remoteToolEntries = toolEntries
246
+ .filter((t) => t.name.startsWith('github') || t.name === 'packageSearch')
247
+ .map((t) => ({
248
+ ...t,
249
+ source:
250
+ t.name === 'packageSearch'
251
+ ? 'package registry'
252
+ : t.name === 'githubCloneRepo'
253
+ ? 'clone payload'
254
+ : 'github provider',
255
+ deltaChars: t.rawChars - t.responseChars,
256
+ sentPctOfRaw: t.rawChars > 0 ? (t.responseChars / t.rawChars) * 100 : 0,
257
+ outcome:
258
+ t.responseChars < t.rawChars
259
+ ? 'reduced'
260
+ : t.responseChars > t.rawChars
261
+ ? 'expanded'
262
+ : 'same',
263
+ }))
264
+ .sort((a, b) => b.calls - a.calls || b.rawChars - a.rawChars);
265
+
266
+ const session = ctx.session;
267
+ const sessionAgeMs =
268
+ session?.createdAt && !Number.isNaN(Date.parse(session.createdAt))
269
+ ? Date.now() - Date.parse(session.createdAt)
270
+ : null;
271
+
272
+ const totalPackageFailures = packageRegistryFailuresArr.reduce(
273
+ (s, e) => s + e.count,
274
+ 0
275
+ );
276
+
277
+ return {
278
+ generatedAt: new Date().toISOString(),
279
+ statsPath: ctx.statsPath,
280
+ hasStats: ctx.present,
281
+ schemaVersion: ctx.version ?? null,
282
+ session: session
283
+ ? {
284
+ sessionId: session.sessionId,
285
+ createdAt: session.createdAt,
286
+ lastActiveAt: session.lastActiveAt,
287
+ sessionAgeMs,
288
+ sessionPath: session.sessionPath,
289
+ }
290
+ : null,
291
+ tokenCharRatio: TOKEN_CHAR_RATIO,
292
+ tokenEstimateNote: TOKEN_ESTIMATE_NOTE,
293
+ totals: {
294
+ toolCalls: safeNumber(stats.toolCalls),
295
+ promptCalls: safeNumber(stats.promptCalls),
296
+ errors: safeNumber(stats.errors),
297
+ rateLimits: safeNumber(stats.rateLimits),
298
+ rawChars,
299
+ responseChars,
300
+ savedChars,
301
+ savingsPct: rawChars > 0 ? (savedChars / rawChars) * 100 : 0,
302
+ estimatedTokensSaved: Math.round(savedChars / TOKEN_CHAR_RATIO),
303
+ charSavingsCalls,
304
+ githubCacheHits: cacheHitsTotal,
305
+ githubCacheRateLimits: cacheRateLimits,
306
+ packageRegistryFailures: totalPackageFailures,
307
+ uniqueTools: savingToolEntries.length,
308
+ uniqueCacheEndpoints: cacheHitsArr.length,
309
+ avgCharsSavedPerCall:
310
+ charSavingsCalls > 0 ? Math.round(savedChars / charSavingsCalls) : 0,
311
+ },
312
+ tools: savingToolEntries,
313
+ remoteTools: remoteToolEntries,
314
+ rateLimitsByProvider: rateLimitsByProviderArr,
315
+ githubCacheHits: cacheHitsArr,
316
+ packageRegistryFailures: packageRegistryFailuresArr,
317
+ };
318
+ }
319
+
320
+ function injectData(template, data) {
321
+ if (!template.includes(SENTINEL)) {
322
+ die(
323
+ `template is missing the "${SENTINEL}" sentinel — cannot inject data. ` +
324
+ `Confirm the template file is the one shipped with this skill.`
325
+ );
326
+ }
327
+ // JSON.stringify never produces </script>, but we still defend against it.
328
+ const json = JSON.stringify(data).replace(/</g, '\\u003c');
329
+ return template.replace(SENTINEL, json);
330
+ }
331
+
332
+ function ensureDir(filePath) {
333
+ const dir = dirname(filePath);
334
+ if (!existsSync(dir)) {
335
+ mkdirSync(dir, { recursive: true });
336
+ }
337
+ }
338
+
339
+ function openInBrowser(filePath) {
340
+ const url = `file://${filePath}`;
341
+ const plat = platform();
342
+ let cmd, args;
343
+ if (plat === 'darwin') {
344
+ cmd = 'open';
345
+ args = [url];
346
+ } else if (plat === 'win32') {
347
+ cmd = 'cmd';
348
+ args = ['/c', 'start', '""', url];
349
+ } else {
350
+ cmd = 'xdg-open';
351
+ args = [url];
352
+ }
353
+ try {
354
+ const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
355
+ child.unref();
356
+ return true;
357
+ } catch {
358
+ return false;
359
+ }
360
+ }
361
+
362
+ function main() {
363
+ const opts = parseArgs(process.argv);
364
+ const { stats, present, version } = readStats(opts.stats, opts.allowEmpty);
365
+
366
+ if (!existsSync(opts.template)) {
367
+ die(`template not found at ${opts.template}`);
368
+ }
369
+ const template = readFileSync(opts.template, 'utf8');
370
+
371
+ const sessionMeta = readSessionMeta(opts.stats);
372
+ const data = computeDashboard(stats, {
373
+ statsPath: opts.stats,
374
+ present,
375
+ version,
376
+ session: sessionMeta,
377
+ });
378
+ const html = injectData(template, data);
379
+
380
+ ensureDir(opts.output);
381
+ writeFileSync(opts.output, html, 'utf8');
382
+
383
+ const t = data.totals;
384
+ process.stdout.write(
385
+ [
386
+ `dashboard: ${opts.output}`,
387
+ `stats source: ${opts.stats}${present ? '' : ' (missing — empty state)'}`,
388
+ `tool calls: ${t.toolCalls}`,
389
+ `tokens saved: ${t.estimatedTokensSaved.toLocaleString()} (~${t.savingsPct.toFixed(1)}%)`,
390
+ `cache hits: ${t.githubCacheHits} (avoided ${t.githubCacheRateLimits} rate limits)`,
391
+ `errors: ${t.errors}`,
392
+ '',
393
+ ].join('\n')
394
+ );
395
+
396
+ if (opts.open) {
397
+ if (openInBrowser(opts.output)) {
398
+ process.stdout.write(`opened in default browser.\n`);
399
+ } else {
400
+ process.stdout.write(
401
+ `open the file manually: file://${opts.output}\n`
402
+ );
403
+ }
404
+ }
405
+ }
406
+
407
+ main();
Binary file