@vibescore/tracker 0.2.5 → 0.2.7

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
@@ -9,7 +9,7 @@ _Real-time AI Analytics for Codex CLI_
9
9
 
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
11
11
  [![Node.js Support](https://img.shields.io/badge/Node.js-%3E%3D18-brightgreen.svg)](https://nodejs.org/)
12
- [![Platform](https://img.shields.io/badge/Platform-macOS-lightgrey.svg)](https://www.apple.com/macos/)
12
+ [![Platform](https://img.shields.io/badge/Platform-macOS%20%7C%20Linux-lightgrey.svg)](https://www.kernel.org/)
13
13
 
14
14
  [**English**](README.md) • [**中文说明**](README.zh-CN.md)
15
15
 
@@ -25,7 +25,7 @@ _Real-time AI Analytics for Codex CLI_
25
25
 
26
26
  ## 🌌 Overview
27
27
 
28
- **VibeScore** is an intelligent token usage tracking system designed specifically for macOS developers. Through the all-new **Matrix-A Design System**, it provides a high-fidelity cyberpunk-style dashboard that transforms your **AI Output** into quantifiable metrics, supported by the **Neural Divergence Map** for real-time monitoring of multi-model compute distribution.
28
+ **VibeScore** is an intelligent token usage tracking system designed for macOS-first workflows, with CLI support on mainstream Linux (Ubuntu/Fedora/Arch). Through the all-new **Matrix-A Design System**, it provides a high-fidelity cyberpunk-style dashboard that transforms your **AI Output** into quantifiable metrics, supported by the **Neural Divergence Map** for real-time monitoring of multi-model compute distribution.
29
29
 
30
30
  > [!TIP] > **Core Index**: Our signature metric that reflects your flow state by analyzing token consumption rates and patterns.
31
31
 
@@ -67,6 +67,7 @@ Note: `init` shows a consent prompt in interactive shells. Use `--yes` to skip p
67
67
  Optional: `--dry-run` previews planned changes without writing files.
68
68
  Note: If `~/.code/config.toml` exists (or `CODE_HOME`), `init` also configures Every Code `notify` automatically. No further user intervention is required for data sync.
69
69
  Note: If Gemini CLI home exists, `init` installs a `SessionEnd` hook in `~/.gemini/settings.json` and sets `tools.enableHooks = true` so hooks execute. This enables all Gemini hooks; disable by setting `tools.enableHooks = false` (or disabling the `vibescore-tracker` hook).
70
+ Note: Linux support is CLI-only and officially covers Codex CLI + Claude Code sources.
70
71
 
71
72
  ### Sync & Status
72
73
 
@@ -84,6 +85,7 @@ npx --yes @vibescore/tracker status
84
85
  ### Sources
85
86
 
86
87
  - Codex CLI logs: `~/.codex/sessions/**/rollout-*.jsonl` (override with `CODEX_HOME`)
88
+ - Claude Code logs: `~/.claude/projects/**/*.jsonl` (override with `CLAUDE_HOME`)
87
89
  - Every Code logs: `~/.code/sessions/**/rollout-*.jsonl` (override with `CODE_HOME`)
88
90
  - Gemini CLI logs: `~/.gemini/tmp/**/chats/session-*.json` (override with `GEMINI_HOME`)
89
91
 
@@ -91,6 +93,7 @@ npx --yes @vibescore/tracker status
91
93
 
92
94
  - `VIBESCORE_HTTP_TIMEOUT_MS`: CLI HTTP timeout in ms (default `20000`, `0` disables, clamped to `1000..120000`).
93
95
  - `VITE_VIBESCORE_HTTP_TIMEOUT_MS`: Dashboard request timeout in ms (default `15000`, `0` disables, clamped to `1000..30000`).
96
+ - `CLAUDE_HOME`: Override Claude Code home (defaults to `~/.claude`).
94
97
  - `GEMINI_HOME`: Override Gemini CLI home (defaults to `~/.gemini`).
95
98
 
96
99
  ## 🧰 Troubleshooting
package/README.zh-CN.md CHANGED
@@ -9,7 +9,7 @@ _Codex CLI 实时 AI 分析工具_
9
9
 
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
11
11
  [![Node.js Support](https://img.shields.io/badge/Node.js-%3E%3D18-brightgreen.svg)](https://nodejs.org/)
12
- [![Platform](https://img.shields.io/badge/Platform-macOS-lightgrey.svg)](https://www.apple.com/macos/)
12
+ [![Platform](https://img.shields.io/badge/Platform-macOS%20%7C%20Linux-lightgrey.svg)](https://www.kernel.org/)
13
13
 
14
14
  [**English**](README.md) • [**中文说明**](README.zh-CN.md)
15
15
 
@@ -25,7 +25,7 @@ _Codex CLI 实时 AI 分析工具_
25
25
 
26
26
  ## 🌌 项目概述
27
27
 
28
- **VibeScore** 是一个专为 macOS 开发者设计的智能令牌(Token)使用追踪系统。它通过全新的 **Matrix-A Design System**,提供高度可视化的赛博朋克风格仪表盘,将你的 **AI 产出 (AI Output)** 转化为可量化的指标,并支持通过 **Neural Divergence Map** 实时监控多模型的算力分布。
28
+ **VibeScore** 是一个 macOS 优先的智能令牌(Token)使用追踪系统,CLI 支持主流 Linux 发行版(Ubuntu/Fedora/Arch)。它通过全新的 **Matrix-A Design System**,提供高度可视化的赛博朋克风格仪表盘,将你的 **AI 产出 (AI Output)** 转化为可量化的指标,并支持通过 **Neural Divergence Map** 实时监控多模型的算力分布。
29
29
 
30
30
  > [!TIP] > **Core Index (核心指数)**: 我们的标志性指标,通过分析 Token 消耗速率与模式,反映你的开发心流状态。
31
31
 
@@ -67,6 +67,7 @@ npx --yes @vibescore/tracker init
67
67
  可选:`--dry-run` 仅预览将发生的变更,不写入任何文件。
68
68
  说明:若存在 `~/.code/config.toml`(或 `CODE_HOME`),`init` 会自动配置 Every Code 的 `notify`。配置完成后,数据同步完全自动化,无需后续人工干预。
69
69
  说明:若检测到 Gemini CLI home,`init` 会在 `~/.gemini/settings.json` 安装 `SessionEnd` hook,并将 `tools.enableHooks = true` 以确保 hook 生效。这会启用所有 Gemini hooks;如需关闭,可将 `tools.enableHooks = false`(或禁用 `vibescore-tracker` hook)。
70
+ 说明:Linux 仅支持 CLI,官方支持的数据源范围为 Codex CLI + Claude Code。
70
71
 
71
72
  ### 同步与状态查看
72
73
 
@@ -84,6 +85,7 @@ npx --yes @vibescore/tracker status
84
85
  ### 日志来源
85
86
 
86
87
  - Codex CLI 日志:`~/.codex/sessions/**/rollout-*.jsonl`(可用 `CODEX_HOME` 覆盖)
88
+ - Claude Code 日志:`~/.claude/projects/**/*.jsonl`(可用 `CLAUDE_HOME` 覆盖)
87
89
  - Every Code 日志:`~/.code/sessions/**/rollout-*.jsonl`(可用 `CODE_HOME` 覆盖)
88
90
  - Gemini CLI 日志:`~/.gemini/tmp/**/chats/session-*.json`(可用 `GEMINI_HOME` 覆盖)
89
91
 
@@ -91,6 +93,7 @@ npx --yes @vibescore/tracker status
91
93
 
92
94
  - `VIBESCORE_HTTP_TIMEOUT_MS`:CLI 请求超时(毫秒,默认 `20000`,`0` 表示关闭,范围 `1000..120000`)。
93
95
  - `VITE_VIBESCORE_HTTP_TIMEOUT_MS`:Dashboard 请求超时(毫秒,默认 `15000`,`0` 表示关闭,范围 `1000..30000`)。
96
+ - `CLAUDE_HOME`:覆盖 Claude Code 的 home(默认 `~/.claude`)。
94
97
  - `GEMINI_HOME`:覆盖 Gemini CLI 的 home(默认 `~/.gemini`)。
95
98
 
96
99
  ## 🧰 常见问题
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibescore/tracker",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Codex CLI token usage tracker (macOS-first, notify-driven).",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
package/src/cli.js CHANGED
@@ -54,7 +54,8 @@ function printHelp() {
54
54
  ' - Every Code notify installs when ~/.code/config.toml exists.',
55
55
  ' - auto sync waits for a device token.',
56
56
  ' - optional: VIBESCORE_DASHBOARD_URL or --dashboard-url for hosted landing.',
57
- ' - sync parses ~/.codex/sessions/**/rollout-*.jsonl and ~/.code/sessions/**/rollout-*.jsonl, then uploads token deltas.',
57
+ ' - sync parses Codex (~/.codex/sessions/**/rollout-*.jsonl), Claude (~/.claude/projects/**/*.jsonl), and other supported sources.',
58
+ ' - Linux support: Codex + Claude only (set CLAUDE_HOME to override Claude paths).',
58
59
  ' - --debug shows original backend errors.',
59
60
  ''
60
61
  ].join('\n')
@@ -13,7 +13,13 @@ const {
13
13
  readCodexNotify,
14
14
  readEveryCodeNotify
15
15
  } = require('../lib/codex-config');
16
- const { upsertClaudeHook, buildClaudeHookCommand, isClaudeHookConfigured } = require('../lib/claude-config');
16
+ const {
17
+ upsertClaudeHook,
18
+ buildClaudeHookCommand,
19
+ isClaudeHookConfigured,
20
+ resolveClaudeHome,
21
+ resolveClaudeSettingsPath
22
+ } = require('../lib/claude-config');
17
23
  const {
18
24
  resolveGeminiConfigDir,
19
25
  resolveGeminiSettingsPath,
@@ -34,14 +40,11 @@ const {
34
40
  CYAN,
35
41
  RESET,
36
42
  color,
37
- underline,
38
- renderBox,
39
43
  isInteractive,
40
44
  promptMenu,
41
- promptEnter,
42
- createSpinner,
43
- formatSummaryLine
45
+ createSpinner
44
46
  } = require('../lib/cli-ui');
47
+ const { renderLocalReport, renderAuthTransition, renderSuccessBox } = require('../lib/init-flow');
45
48
 
46
49
  const ASCII_LOGO = [
47
50
  '██╗ ██╗██╗██████╗ ███████╗███████╗ ██████╗ ██████╗ ██████╗ ███████╗',
@@ -80,11 +83,12 @@ async function cmdInit(argv) {
80
83
 
81
84
  if (isInteractive() && !opts.yes && !opts.dryRun) {
82
85
  const choice = await promptMenu({
83
- message: '? How would you like to proceed?',
84
- options: ['Start Setup (Recommended)', 'Exit'],
86
+ message: '? Proceed with installation?',
87
+ options: ['Yes, configure my environment', 'No, exit'],
85
88
  defaultIndex: 0
86
89
  });
87
- if (choice.toLowerCase().startsWith('exit')) {
90
+ const normalizedChoice = String(choice || '').trim().toLowerCase();
91
+ if (normalizedChoice.startsWith('no') || normalizedChoice.includes('exit')) {
88
92
  process.stdout.write('Setup cancelled.\n');
89
93
  return;
90
94
  }
@@ -98,7 +102,7 @@ async function cmdInit(argv) {
98
102
  configPath,
99
103
  notifyPath
100
104
  });
101
- renderTransparencyReport({ summary: preview.summary, isDryRun: true });
105
+ renderLocalReport({ summary: preview.summary, isDryRun: true });
102
106
  if (preview.pendingBrowserAuth) {
103
107
  process.stdout.write('Account linking would be required for full setup.\n');
104
108
  } else if (!preview.deviceToken) {
@@ -130,11 +134,7 @@ async function cmdInit(argv) {
130
134
  }
131
135
  spinner.stop();
132
136
 
133
- renderTransparencyReport({
134
- summary: setup.summary,
135
- isDryRun: false,
136
- includeDivider: setup.pendingBrowserAuth
137
- });
137
+ renderLocalReport({ summary: setup.summary, isDryRun: false });
138
138
 
139
139
  let deviceToken = setup.deviceToken;
140
140
  let deviceId = setup.deviceId;
@@ -144,10 +144,7 @@ async function cmdInit(argv) {
144
144
  if (!dashboardUrl) dashboardUrl = await detectLocalDashboardUrl();
145
145
  const flow = await beginBrowserAuth({ baseUrl, dashboardUrl, timeoutMs: 10 * 60_000, open: false });
146
146
  const canAutoOpen = !opts.noOpen;
147
- renderFinalStep({ authUrl: flow.authUrl, canAutoOpen });
148
- if (canAutoOpen && isInteractive()) {
149
- await promptEnter('');
150
- }
147
+ renderAuthTransition({ authUrl: flow.authUrl, canAutoOpen });
151
148
  if (canAutoOpen) {
152
149
  if (isInteractive()) await sleep(250);
153
150
  openInBrowser(flow.authUrl);
@@ -158,9 +155,12 @@ async function cmdInit(argv) {
158
155
  deviceId = issued.deviceId;
159
156
  await writeJson(configPath, { baseUrl, deviceToken, deviceId, installedAt: setup.installedAt });
160
157
  await chmod600IfPossible(configPath);
161
- renderSuccessBox({ deviceId, configPath });
158
+ const resolvedDashboardUrl = dashboardUrl || null;
159
+ renderSuccessBox({ configPath, dashboardUrl: resolvedDashboardUrl });
162
160
  } else if (deviceToken) {
163
- renderSuccessBox({ deviceId, configPath });
161
+ if (!dashboardUrl) dashboardUrl = await detectLocalDashboardUrl();
162
+ const resolvedDashboardUrl = dashboardUrl || null;
163
+ renderSuccessBox({ configPath, dashboardUrl: resolvedDashboardUrl });
164
164
  } else {
165
165
  renderAccountNotLinked();
166
166
  }
@@ -194,39 +194,6 @@ function renderWelcome() {
194
194
  );
195
195
  }
196
196
 
197
- function renderTransparencyReport({ summary, isDryRun, includeDivider = false }) {
198
- const header = isDryRun ? 'Dry run complete. Preview only; no changes were applied.' : 'Local setup complete.';
199
- const lines = [header, '', "We've integrated VibeScore with:"];
200
- for (const item of summary) lines.push(formatSummaryLine(item));
201
- if (includeDivider) lines.push('', DIVIDER, '');
202
- process.stdout.write(`${lines.join('\n')}\n`);
203
- }
204
-
205
- function renderFinalStep({ authUrl, canAutoOpen }) {
206
- const lines = [
207
- 'Final Step: Link your account',
208
- '',
209
- canAutoOpen ? 'Press [Enter] to open your browser and sign in.' : 'Open the link below to sign in.'
210
- ];
211
- if (authUrl) lines.push(`(Or visit: ${underline(authUrl)})`);
212
- lines.push('');
213
- process.stdout.write(lines.join('\n'));
214
- }
215
-
216
- function renderSuccessBox({ deviceId, configPath }) {
217
- const identityLine = deviceId ? `Device ID: ${deviceId}` : 'Account linked.';
218
- const lines = [
219
- 'You are all set!',
220
- '',
221
- identityLine,
222
- `Token saved to: ${configPath}`,
223
- '',
224
- 'VibeScore is now running in the background.',
225
- 'You can close this terminal window.'
226
- ];
227
- process.stdout.write(`${renderBox(lines)}\n`);
228
- }
229
-
230
197
  function renderAccountNotLinked({ context } = {}) {
231
198
  if (context === 'dry-run') {
232
199
  process.stdout.write(['', 'Account not linked (dry run).', 'Run init without --dry-run to link your account.', ''].join('\n'));
@@ -363,8 +330,8 @@ function buildIntegrationTargets({ home, trackerDir, notifyPath }) {
363
330
  const codeNotifyOriginalPath = path.join(trackerDir, 'code_notify_original.json');
364
331
  const notifyCmd = ['/usr/bin/env', 'node', notifyPath];
365
332
  const codeNotifyCmd = ['/usr/bin/env', 'node', notifyPath, '--source=every-code'];
366
- const claudeDir = path.join(home, '.claude');
367
- const claudeSettingsPath = path.join(claudeDir, 'settings.json');
333
+ const claudeDir = resolveClaudeHome({ home, env: process.env });
334
+ const claudeSettingsPath = resolveClaudeSettingsPath({ claudeHome: claudeDir });
368
335
  const claudeHookCommand = buildClaudeHookCommand(notifyPath);
369
336
  const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
370
337
  const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
@@ -4,7 +4,12 @@ const fs = require('node:fs/promises');
4
4
 
5
5
  const { readJson } = require('../lib/fs');
6
6
  const { readCodexNotify, readEveryCodeNotify } = require('../lib/codex-config');
7
- const { isClaudeHookConfigured, buildClaudeHookCommand } = require('../lib/claude-config');
7
+ const {
8
+ isClaudeHookConfigured,
9
+ buildClaudeHookCommand,
10
+ resolveClaudeHome,
11
+ resolveClaudeSettingsPath
12
+ } = require('../lib/claude-config');
8
13
  const {
9
14
  resolveGeminiConfigDir,
10
15
  resolveGeminiSettingsPath,
@@ -37,7 +42,8 @@ async function cmdStatus(argv = []) {
37
42
  const codexConfigPath = path.join(codexHome, 'config.toml');
38
43
  const codeHome = process.env.CODE_HOME || path.join(home, '.code');
39
44
  const codeConfigPath = path.join(codeHome, 'config.toml');
40
- const claudeSettingsPath = path.join(home, '.claude', 'settings.json');
45
+ const claudeHome = resolveClaudeHome({ home, env: process.env });
46
+ const claudeSettingsPath = resolveClaudeSettingsPath({ claudeHome });
41
47
  const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
42
48
  const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
43
49
  const opencodeConfigDir = resolveOpencodeConfigDir({ home, env: process.env });
@@ -14,6 +14,7 @@ const {
14
14
  parseGeminiIncremental,
15
15
  parseOpencodeIncremental
16
16
  } = require('../lib/rollout');
17
+ const { resolveClaudeHome, resolveClaudeProjectsDir } = require('../lib/claude-config');
17
18
  const { drainQueueToCloud } = require('../lib/uploader');
18
19
  const { createProgress, renderBar, formatNumber, formatBytes } = require('../lib/progress');
19
20
  const { syncHeartbeat } = require('../lib/vibescore-api');
@@ -53,7 +54,8 @@ async function cmdSync(argv) {
53
54
 
54
55
  const codexHome = process.env.CODEX_HOME || path.join(home, '.codex');
55
56
  const codeHome = process.env.CODE_HOME || path.join(home, '.code');
56
- const claudeProjectsDir = path.join(home, '.claude', 'projects');
57
+ const claudeHome = resolveClaudeHome({ home, env: process.env });
58
+ const claudeProjectsDir = resolveClaudeProjectsDir({ claudeHome });
57
59
  const geminiHome = process.env.GEMINI_HOME || path.join(home, '.gemini');
58
60
  const geminiTmpDir = path.join(geminiHome, 'tmp');
59
61
  const xdgDataHome = process.env.XDG_DATA_HOME || path.join(home, '.local', 'share');
@@ -3,7 +3,12 @@ const path = require('node:path');
3
3
  const fs = require('node:fs/promises');
4
4
 
5
5
  const { restoreCodexNotify, restoreEveryCodeNotify } = require('../lib/codex-config');
6
- const { removeClaudeHook, buildClaudeHookCommand } = require('../lib/claude-config');
6
+ const {
7
+ removeClaudeHook,
8
+ buildClaudeHookCommand,
9
+ resolveClaudeHome,
10
+ resolveClaudeSettingsPath
11
+ } = require('../lib/claude-config');
7
12
  const {
8
13
  resolveGeminiConfigDir,
9
14
  resolveGeminiSettingsPath,
@@ -21,7 +26,8 @@ async function cmdUninstall(argv) {
21
26
  const codexConfigPath = path.join(codexHome, 'config.toml');
22
27
  const codeHome = process.env.CODE_HOME || path.join(home, '.code');
23
28
  const codeConfigPath = path.join(codeHome, 'config.toml');
24
- const claudeSettingsPath = path.join(home, '.claude', 'settings.json');
29
+ const claudeHome = resolveClaudeHome({ home, env: process.env });
30
+ const claudeSettingsPath = resolveClaudeSettingsPath({ claudeHome });
25
31
  const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
26
32
  const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
27
33
  const opencodeConfigDir = resolveOpencodeConfigDir({ home, env: process.env });
@@ -1,10 +1,25 @@
1
1
  const fs = require('node:fs/promises');
2
+ const os = require('node:os');
2
3
  const path = require('node:path');
3
4
 
4
5
  const { ensureDir, readJson, writeJson } = require('./fs');
5
6
 
6
7
  const DEFAULT_EVENT = 'SessionEnd';
7
8
 
9
+ function resolveClaudeHome({ home = os.homedir(), env = process.env } = {}) {
10
+ const explicit = typeof env.CLAUDE_HOME === 'string' ? env.CLAUDE_HOME.trim() : '';
11
+ if (explicit) return path.resolve(explicit);
12
+ return path.join(home, '.claude');
13
+ }
14
+
15
+ function resolveClaudeSettingsPath({ claudeHome }) {
16
+ return path.join(claudeHome, 'settings.json');
17
+ }
18
+
19
+ function resolveClaudeProjectsDir({ claudeHome }) {
20
+ return path.join(claudeHome, 'projects');
21
+ }
22
+
8
23
  async function upsertClaudeHook({ settingsPath, hookCommand, event = DEFAULT_EVENT }) {
9
24
  const existing = await readJson(settingsPath);
10
25
  const settings = normalizeSettings(existing);
@@ -183,6 +198,9 @@ async function writeClaudeSettings({ settingsPath, settings }) {
183
198
  }
184
199
 
185
200
  module.exports = {
201
+ resolveClaudeHome,
202
+ resolveClaudeSettingsPath,
203
+ resolveClaudeProjectsDir,
186
204
  upsertClaudeHook,
187
205
  removeClaudeHook,
188
206
  isClaudeHookConfigured,
@@ -4,7 +4,12 @@ const fs = require('node:fs/promises');
4
4
 
5
5
  const { readJson } = require('./fs');
6
6
  const { readCodexNotify, readEveryCodeNotify } = require('./codex-config');
7
- const { isClaudeHookConfigured, buildClaudeHookCommand } = require('./claude-config');
7
+ const {
8
+ isClaudeHookConfigured,
9
+ buildClaudeHookCommand,
10
+ resolveClaudeHome,
11
+ resolveClaudeSettingsPath
12
+ } = require('./claude-config');
8
13
  const {
9
14
  resolveGeminiConfigDir,
10
15
  resolveGeminiSettingsPath,
@@ -30,7 +35,8 @@ async function collectTrackerDiagnostics({
30
35
  const autoRetryPath = path.join(trackerDir, 'auto.retry.json');
31
36
  const codexConfigPath = path.join(codexHome, 'config.toml');
32
37
  const codeConfigPath = path.join(codeHome, 'config.toml');
33
- const claudeConfigPath = path.join(home, '.claude', 'settings.json');
38
+ const claudeHome = resolveClaudeHome({ home, env: process.env });
39
+ const claudeConfigPath = resolveClaudeSettingsPath({ claudeHome });
34
40
  const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
35
41
  const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
36
42
  const opencodeConfigDir = resolveOpencodeConfigDir({ home, env: process.env });
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ const { formatSummaryLine, renderBox, underline } = require('./cli-ui');
4
+
5
+ const DIVIDER = '----------------------------------------------';
6
+
7
+ function renderLocalReport({ summary, isDryRun }) {
8
+ const header = isDryRun
9
+ ? 'Dry run complete. Preview only; no changes were applied.'
10
+ : 'Local configuration complete.';
11
+ const lines = [header, '', 'Integration Status:'];
12
+ for (const item of summary || []) lines.push(formatSummaryLine(item));
13
+ process.stdout.write(`${lines.join('\n')}\n`);
14
+ }
15
+
16
+ function renderAuthTransition({ authUrl, canAutoOpen }) {
17
+ const lines = ['', DIVIDER, '', 'Next: Registering device...'];
18
+ if (canAutoOpen) {
19
+ lines.push('Opening your browser to link account...');
20
+ if (authUrl) lines.push(`If it does not open, visit: ${underline(authUrl)}`);
21
+ } else {
22
+ lines.push('Open the link below to sign in.');
23
+ if (authUrl) lines.push(`Visit: ${underline(authUrl)}`);
24
+ }
25
+ lines.push('');
26
+ process.stdout.write(`${lines.join('\n')}\n`);
27
+ }
28
+
29
+ function renderSuccessBox({ configPath, dashboardUrl }) {
30
+ const identityLine = 'Account linked.';
31
+ const lines = [
32
+ 'You are all set!',
33
+ '',
34
+ identityLine,
35
+ `Token saved to: ${configPath}`,
36
+ ''
37
+ ];
38
+ if (dashboardUrl) lines.push(`View your stats at: ${dashboardUrl}`);
39
+ lines.push('You can close this terminal window.');
40
+ process.stdout.write(`${renderBox(lines)}\n`);
41
+ }
42
+
43
+ module.exports = {
44
+ DIVIDER,
45
+ renderLocalReport,
46
+ renderAuthTransition,
47
+ renderSuccessBox
48
+ };