@ipv9/tokentracker-cli 0.39.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.
- package/LICENSE +21 -0
- package/README.ja.md +464 -0
- package/README.ko.md +464 -0
- package/README.md +476 -0
- package/README.zh-CN.md +469 -0
- package/bin/tracker.js +52 -0
- package/dashboard/dist/app-icon.png +0 -0
- package/dashboard/dist/apple-touch-icon.png +0 -0
- package/dashboard/dist/assets/ActivityHeatmap-BxBGHY1z.js +42 -0
- package/dashboard/dist/assets/Card-GROxUc_6.js +1 -0
- package/dashboard/dist/assets/DashboardPage-BZ5VFoBk.js +19 -0
- package/dashboard/dist/assets/DevicePage-CQts7ivB.js +1 -0
- package/dashboard/dist/assets/DialogTitle-DqFsk26U.js +1 -0
- package/dashboard/dist/assets/FadeIn-HBBXW_3q.js +1 -0
- package/dashboard/dist/assets/HeaderGithubStar-COD2HfaF.js +1 -0
- package/dashboard/dist/assets/IpCheckPage-Cn0l8-BQ.js +15 -0
- package/dashboard/dist/assets/LandingPage-CmV1BMU4.js +4356 -0
- package/dashboard/dist/assets/LeaderboardAvatar-CiIpWZwb.js +1 -0
- package/dashboard/dist/assets/LeaderboardPage-BU0mlR3g.js +6 -0
- package/dashboard/dist/assets/LeaderboardProfileModal-C49_3Xm9.js +72 -0
- package/dashboard/dist/assets/LeaderboardProfilePage-Bh1n0BAM.js +1 -0
- package/dashboard/dist/assets/LimitsPage-fZAmwLWT.js +2 -0
- package/dashboard/dist/assets/LocalOnlyNotice-D3rQdA-P.js +1 -0
- package/dashboard/dist/assets/LoginPage-enO4XP-c.js +1 -0
- package/dashboard/dist/assets/PopoverPopup-pitJEpN1.js +1 -0
- package/dashboard/dist/assets/Select-tT9x4ntD.js +1 -0
- package/dashboard/dist/assets/SelectItemText-C7F7eIm0.js +1 -0
- package/dashboard/dist/assets/SettingsPage-pDasZNU2.js +1 -0
- package/dashboard/dist/assets/SkillsPage-DEu7FY-2.js +1 -0
- package/dashboard/dist/assets/WidgetsPage-DGcO7rbp.js +1 -0
- package/dashboard/dist/assets/WrappedPage-D_1ID213.js +1 -0
- package/dashboard/dist/assets/agent-logos-DXQVg1xy.js +1 -0
- package/dashboard/dist/assets/arrow-up-right-DNLo92Ba.js +1 -0
- package/dashboard/dist/assets/download-DbBBBpJF.js +1 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-400-normal-BPBWmzPh.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-400-normal-Ce5q_31Z.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-500-normal-CJBLNVQT.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-500-normal-mNhfPmgl.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-700-normal-DH5Q319x.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-700-normal-VCNRadI3.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-900-normal-BKC3PZkM.woff +0 -0
- package/dashboard/dist/assets/geist-mono-cyrillic-900-normal-DdrQ8KBw.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-400-normal-CoULgQGM.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-400-normal-LC9RFr9I.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-500-normal-D3o2eNa9.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-500-normal-DOxI7kZ4.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-700-normal-D6izGJRP.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-700-normal-QGw08Lff.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-900-normal-CmoKXrdK.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-900-normal-Cu5MFKsu.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-400-normal-Cgks_Qgx.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-400-normal-CxNRRMGd.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-500-normal-CQcGuCNt.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-500-normal-diTenJ8L.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-700-normal-BX9f1BHp.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-700-normal-YOllDaLV.woff2 +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-900-normal-Br6WNUGb.woff +0 -0
- package/dashboard/dist/assets/geist-mono-latin-ext-900-normal-CrmBTzU2.woff2 +0 -0
- package/dashboard/dist/assets/index-BeoRn2gJ.js +2 -0
- package/dashboard/dist/assets/info-Bh5qTz6k.js +1 -0
- package/dashboard/dist/assets/main-BIY3Nj6I.js +969 -0
- package/dashboard/dist/assets/main-G_F9Iu_x.css +1 -0
- package/dashboard/dist/assets/use-limits-display-prefs-B4iJ-bBc.js +1 -0
- package/dashboard/dist/assets/use-native-settings-DHjPFDv8.js +1 -0
- package/dashboard/dist/assets/use-usage-limits-odqe9OY7.js +1 -0
- package/dashboard/dist/assets/useCurrency-BsSZiZMP.js +1 -0
- package/dashboard/dist/assets/useOpenInteractionType-BQhgq4rv.js +12 -0
- package/dashboard/dist/brand-logos/antigravity.svg +1 -0
- package/dashboard/dist/brand-logos/claude-code.svg +1 -0
- package/dashboard/dist/brand-logos/codebuddy.svg +1 -0
- package/dashboard/dist/brand-logos/codex.svg +15 -0
- package/dashboard/dist/brand-logos/copilot.svg +1 -0
- package/dashboard/dist/brand-logos/cursor.svg +1 -0
- package/dashboard/dist/brand-logos/gemini.svg +1 -0
- package/dashboard/dist/brand-logos/grok.svg +1 -0
- package/dashboard/dist/brand-logos/hermes.svg +1 -0
- package/dashboard/dist/brand-logos/kilo.svg +4 -0
- package/dashboard/dist/brand-logos/kimi.svg +1 -0
- package/dashboard/dist/brand-logos/kiro.svg +1 -0
- package/dashboard/dist/brand-logos/openclaw.svg +22 -0
- package/dashboard/dist/brand-logos/opencode.svg +3 -0
- package/dashboard/dist/clawd/happy.svg +161 -0
- package/dashboard/dist/clawd/idle/collapse.svg +101 -0
- package/dashboard/dist/clawd/idle/doze.svg +72 -0
- package/dashboard/dist/clawd/idle/follow.svg +64 -0
- package/dashboard/dist/clawd/idle/living.svg +196 -0
- package/dashboard/dist/clawd/idle/look.svg +115 -0
- package/dashboard/dist/clawd/idle/yawn.svg +158 -0
- package/dashboard/dist/clawd/mini/alert.svg +129 -0
- package/dashboard/dist/clawd/mini/crabwalk.svg +76 -0
- package/dashboard/dist/clawd/mini/enter-sleep.svg +65 -0
- package/dashboard/dist/clawd/mini/enter.svg +115 -0
- package/dashboard/dist/clawd/mini/happy.svg +124 -0
- package/dashboard/dist/clawd/mini/idle-tight.svg +15 -0
- package/dashboard/dist/clawd/mini/idle.svg +83 -0
- package/dashboard/dist/clawd/mini/peek.svg +82 -0
- package/dashboard/dist/clawd/mini/sleep.svg +112 -0
- package/dashboard/dist/clawd/react/double.svg +108 -0
- package/dashboard/dist/clawd/react/drag.svg +102 -0
- package/dashboard/dist/clawd/react/left.svg +102 -0
- package/dashboard/dist/clawd/react/right.svg +102 -0
- package/dashboard/dist/clawd/sleep/collapse-sleep.svg +247 -0
- package/dashboard/dist/clawd/sleep/sleeping.svg +118 -0
- package/dashboard/dist/clawd/sleep/wake.svg +277 -0
- package/dashboard/dist/clawd/static-base.svg +21 -0
- package/dashboard/dist/clawd/status/disconnected.svg +136 -0
- package/dashboard/dist/clawd/status/error.svg +94 -0
- package/dashboard/dist/clawd/status/notification.svg +134 -0
- package/dashboard/dist/clawd/working/building.svg +231 -0
- package/dashboard/dist/clawd/working/carrying.svg +107 -0
- package/dashboard/dist/clawd/working/conducting.svg +118 -0
- package/dashboard/dist/clawd/working/confused.svg +113 -0
- package/dashboard/dist/clawd/working/debugger.svg +91 -0
- package/dashboard/dist/clawd/working/juggling.svg +82 -0
- package/dashboard/dist/clawd/working/overheated.svg +75 -0
- package/dashboard/dist/clawd/working/pushing.svg +125 -0
- package/dashboard/dist/clawd/working/sweeping.svg +79 -0
- package/dashboard/dist/clawd/working/thinking.svg +119 -0
- package/dashboard/dist/clawd/working/typing.svg +153 -0
- package/dashboard/dist/clawd/working/ultrathink.svg +166 -0
- package/dashboard/dist/clawd/working/wizard.svg +98 -0
- package/dashboard/dist/dashboard-dark.png +0 -0
- package/dashboard/dist/favicon-16.png +0 -0
- package/dashboard/dist/favicon-32.png +0 -0
- package/dashboard/dist/favicon.ico +0 -0
- package/dashboard/dist/feed.xml +34 -0
- package/dashboard/dist/icon-192.png +0 -0
- package/dashboard/dist/icon-512.png +0 -0
- package/dashboard/dist/index.html +355 -0
- package/dashboard/dist/llms.txt +53 -0
- package/dashboard/dist/neon_bg.png +0 -0
- package/dashboard/dist/robots.txt +7 -0
- package/dashboard/dist/share.html +140 -0
- package/dashboard/dist/sitemap.xml +27 -0
- package/dashboard/dist/widgets-overview.png +0 -0
- package/package.json +78 -0
- package/scripts/install-local-service.sh +140 -0
- package/scripts/uninstall-local-service.sh +21 -0
- package/src/cli.js +95 -0
- package/src/commands/device-login.js +161 -0
- package/src/commands/diagnostics.js +38 -0
- package/src/commands/doctor.js +96 -0
- package/src/commands/init.js +1195 -0
- package/src/commands/serve.js +351 -0
- package/src/commands/status.js +609 -0
- package/src/commands/sync.js +2285 -0
- package/src/commands/uninstall.js +189 -0
- package/src/commands/wrapped.js +150 -0
- package/src/lib/antigravity-paths.js +48 -0
- package/src/lib/browser-auth.js +281 -0
- package/src/lib/categorizer-utils.js +232 -0
- package/src/lib/claude-categorizer.js +1319 -0
- package/src/lib/claude-config.js +204 -0
- package/src/lib/cli-ui.js +179 -0
- package/src/lib/codex-config.js +350 -0
- package/src/lib/codex-context-breakdown.js +400 -0
- package/src/lib/codex-rollout-parser.js +623 -0
- package/src/lib/codex-token-refresh.js +112 -0
- package/src/lib/cursor-config.js +425 -0
- package/src/lib/debug-flags.js +7 -0
- package/src/lib/diagnostics.js +272 -0
- package/src/lib/doctor.js +338 -0
- package/src/lib/fs.js +91 -0
- package/src/lib/gemini-config.js +288 -0
- package/src/lib/grok-hook.js +351 -0
- package/src/lib/init-flow.js +42 -0
- package/src/lib/local-api.js +1861 -0
- package/src/lib/openclaw-hook.js +420 -0
- package/src/lib/openclaw-session-plugin.js +520 -0
- package/src/lib/opencode-config.js +99 -0
- package/src/lib/passive-mode.js +185 -0
- package/src/lib/pricing/curated-overrides.json +79 -0
- package/src/lib/pricing/index.js +143 -0
- package/src/lib/pricing/litellm-fetcher.js +172 -0
- package/src/lib/pricing/matcher.js +255 -0
- package/src/lib/pricing/seed-snapshot.json +1 -0
- package/src/lib/progress.js +76 -0
- package/src/lib/project-usage-purge.js +106 -0
- package/src/lib/prompt.js +24 -0
- package/src/lib/proxy-env.js +131 -0
- package/src/lib/rollout.js +8442 -0
- package/src/lib/runtime-config.js +122 -0
- package/src/lib/skill-usage.js +267 -0
- package/src/lib/skills-manager.js +1127 -0
- package/src/lib/source-metadata.js +48 -0
- package/src/lib/sqlite-reader.js +136 -0
- package/src/lib/static-server.js +59 -0
- package/src/lib/subscriptions.js +453 -0
- package/src/lib/tracker-paths.js +16 -0
- package/src/lib/upload-throttle.js +148 -0
- package/src/lib/usage-limits.js +1806 -0
- package/src/lib/wrapped-aggregator.js +225 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const os = require("node:os");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
const fs = require("node:fs/promises");
|
|
4
|
+
|
|
5
|
+
const { restoreCodexNotify, restoreEveryCodeNotify } = require("../lib/codex-config");
|
|
6
|
+
const { removeClaudeHook, buildClaudeHookCommand, buildHookCommand } = require("../lib/claude-config");
|
|
7
|
+
const {
|
|
8
|
+
resolveGeminiConfigDir,
|
|
9
|
+
resolveGeminiSettingsPath,
|
|
10
|
+
buildGeminiHookCommand,
|
|
11
|
+
removeGeminiHook,
|
|
12
|
+
} = require("../lib/gemini-config");
|
|
13
|
+
const { resolveOpencodeConfigDir, removeOpencodePlugin } = require("../lib/opencode-config");
|
|
14
|
+
const { removeOpenclawHookConfig } = require("../lib/openclaw-hook");
|
|
15
|
+
const { removeOpenclawSessionPluginConfig } = require("../lib/openclaw-session-plugin");
|
|
16
|
+
const { removeGrokHook } = require("../lib/grok-hook");
|
|
17
|
+
const { resolveTrackerPaths } = require("../lib/tracker-paths");
|
|
18
|
+
|
|
19
|
+
async function cmdUninstall(argv) {
|
|
20
|
+
const opts = parseArgs(argv);
|
|
21
|
+
const home = os.homedir();
|
|
22
|
+
const { trackerDir, binDir } = await resolveTrackerPaths({ home });
|
|
23
|
+
const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
|
|
24
|
+
const codexConfigPath = path.join(codexHome, "config.toml");
|
|
25
|
+
const codeHome = process.env.CODE_HOME || path.join(home, ".code");
|
|
26
|
+
const codeConfigPath = path.join(codeHome, "config.toml");
|
|
27
|
+
const claudeSettingsPath = path.join(home, ".claude", "settings.json");
|
|
28
|
+
const codebuddyDir = process.env.CODEBUDDY_HOME || path.join(home, ".codebuddy");
|
|
29
|
+
const codebuddySettingsPath = path.join(codebuddyDir, "settings.json");
|
|
30
|
+
const geminiConfigDir = resolveGeminiConfigDir({ home, env: process.env });
|
|
31
|
+
const geminiSettingsPath = resolveGeminiSettingsPath({ configDir: geminiConfigDir });
|
|
32
|
+
const opencodeConfigDir = resolveOpencodeConfigDir({ home, env: process.env });
|
|
33
|
+
const notifyPath = path.join(binDir, "notify.cjs");
|
|
34
|
+
const notifyOriginalPath = path.join(trackerDir, "codex_notify_original.json");
|
|
35
|
+
const codeNotifyOriginalPath = path.join(trackerDir, "code_notify_original.json");
|
|
36
|
+
const codexNotifyCmd = ["/usr/bin/env", "node", notifyPath];
|
|
37
|
+
const codeNotifyCmd = ["/usr/bin/env", "node", notifyPath, "--source=every-code"];
|
|
38
|
+
const claudeHookCommand = buildClaudeHookCommand(notifyPath);
|
|
39
|
+
const codebuddyHookCommand = buildHookCommand(notifyPath, "codebuddy");
|
|
40
|
+
const geminiHookCommand = buildGeminiHookCommand(notifyPath);
|
|
41
|
+
|
|
42
|
+
const codexConfigExists = await isFile(codexConfigPath);
|
|
43
|
+
const codeConfigExists = await isFile(codeConfigPath);
|
|
44
|
+
const claudeConfigExists = await isFile(claudeSettingsPath);
|
|
45
|
+
const codebuddyConfigExists = await isFile(codebuddySettingsPath);
|
|
46
|
+
const geminiConfigExists = await isDir(geminiConfigDir);
|
|
47
|
+
const opencodeConfigExists = await isDir(opencodeConfigDir);
|
|
48
|
+
const codexRestore = codexConfigExists
|
|
49
|
+
? await restoreCodexNotify({
|
|
50
|
+
codexConfigPath,
|
|
51
|
+
notifyOriginalPath,
|
|
52
|
+
notifyCmd: codexNotifyCmd,
|
|
53
|
+
})
|
|
54
|
+
: { restored: false, skippedReason: "config-missing" };
|
|
55
|
+
const codeRestore = codeConfigExists
|
|
56
|
+
? await restoreEveryCodeNotify({
|
|
57
|
+
codeConfigPath,
|
|
58
|
+
notifyOriginalPath: codeNotifyOriginalPath,
|
|
59
|
+
notifyCmd: codeNotifyCmd,
|
|
60
|
+
})
|
|
61
|
+
: { restored: false, skippedReason: "config-missing" };
|
|
62
|
+
const claudeRemove = claudeConfigExists
|
|
63
|
+
? await removeClaudeHook({ settingsPath: claudeSettingsPath, hookCommand: claudeHookCommand })
|
|
64
|
+
: { removed: false, skippedReason: "config-missing" };
|
|
65
|
+
const codebuddyRemove = codebuddyConfigExists
|
|
66
|
+
? await removeClaudeHook({
|
|
67
|
+
settingsPath: codebuddySettingsPath,
|
|
68
|
+
hookCommand: codebuddyHookCommand,
|
|
69
|
+
})
|
|
70
|
+
: { removed: false, skippedReason: "config-missing" };
|
|
71
|
+
const geminiRemove = geminiConfigExists
|
|
72
|
+
? await removeGeminiHook({ settingsPath: geminiSettingsPath, hookCommand: geminiHookCommand })
|
|
73
|
+
: { removed: false, skippedReason: "config-missing" };
|
|
74
|
+
const opencodeRemove = opencodeConfigExists
|
|
75
|
+
? await removeOpencodePlugin({ configDir: opencodeConfigDir })
|
|
76
|
+
: { removed: false, skippedReason: "config-missing" };
|
|
77
|
+
const openclawSessionPluginRemove = await removeOpenclawSessionPluginConfig({
|
|
78
|
+
home,
|
|
79
|
+
trackerDir,
|
|
80
|
+
env: process.env,
|
|
81
|
+
});
|
|
82
|
+
const openclawHookRemove = await removeOpenclawHookConfig({ home, trackerDir, env: process.env });
|
|
83
|
+
const grokHookRemove = await removeGrokHook({ home, trackerDir, env: process.env });
|
|
84
|
+
|
|
85
|
+
// Remove installed notify handler.
|
|
86
|
+
await fs.unlink(notifyPath).catch(() => {});
|
|
87
|
+
|
|
88
|
+
// Remove local app runtime (installed by init for notify-driven sync).
|
|
89
|
+
await fs.rm(path.join(trackerDir, "app"), { recursive: true, force: true }).catch(() => {});
|
|
90
|
+
|
|
91
|
+
if (opts.purge) {
|
|
92
|
+
await fs.rm(path.join(home, ".tokentracker"), { recursive: true, force: true }).catch(() => {});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
process.stdout.write(
|
|
96
|
+
[
|
|
97
|
+
"Uninstalled:",
|
|
98
|
+
codexConfigExists
|
|
99
|
+
? codexRestore?.restored
|
|
100
|
+
? `- Codex notify restored: ${codexConfigPath}`
|
|
101
|
+
: codexRestore?.skippedReason === "no-backup-not-installed"
|
|
102
|
+
? "- Codex notify: skipped (no backup; not installed)"
|
|
103
|
+
: "- Codex notify: no change"
|
|
104
|
+
: "- Codex notify: skipped (config.toml not found)",
|
|
105
|
+
codeConfigExists
|
|
106
|
+
? codeRestore?.restored
|
|
107
|
+
? `- Every Code notify restored: ${codeConfigPath}`
|
|
108
|
+
: codeRestore?.skippedReason === "no-backup-not-installed"
|
|
109
|
+
? "- Every Code notify: skipped (no backup; not installed)"
|
|
110
|
+
: "- Every Code notify: no change"
|
|
111
|
+
: "- Every Code notify: skipped (config.toml not found)",
|
|
112
|
+
claudeConfigExists
|
|
113
|
+
? claudeRemove?.removed
|
|
114
|
+
? `- Claude hooks removed: ${claudeSettingsPath}`
|
|
115
|
+
: claudeRemove?.skippedReason === "hook-missing"
|
|
116
|
+
? "- Claude hooks: no change"
|
|
117
|
+
: "- Claude hooks: skipped"
|
|
118
|
+
: "- Claude hooks: skipped (settings.json not found)",
|
|
119
|
+
codebuddyConfigExists
|
|
120
|
+
? codebuddyRemove?.removed
|
|
121
|
+
? `- CodeBuddy hooks removed: ${codebuddySettingsPath}`
|
|
122
|
+
: codebuddyRemove?.skippedReason === "hook-missing"
|
|
123
|
+
? "- CodeBuddy hooks: no change"
|
|
124
|
+
: "- CodeBuddy hooks: skipped"
|
|
125
|
+
: "- CodeBuddy hooks: skipped (settings.json not found)",
|
|
126
|
+
geminiConfigExists
|
|
127
|
+
? geminiRemove?.removed
|
|
128
|
+
? `- Gemini hooks removed: ${geminiSettingsPath}`
|
|
129
|
+
: geminiRemove?.skippedReason === "hook-missing"
|
|
130
|
+
? "- Gemini hooks: no change"
|
|
131
|
+
: "- Gemini hooks: skipped"
|
|
132
|
+
: `- Gemini hooks: skipped (${geminiConfigDir} not found)`,
|
|
133
|
+
opencodeConfigExists
|
|
134
|
+
? opencodeRemove?.removed
|
|
135
|
+
? `- Opencode plugin removed: ${opencodeConfigDir}`
|
|
136
|
+
: opencodeRemove?.skippedReason === "plugin-missing"
|
|
137
|
+
? "- Opencode plugin: no change"
|
|
138
|
+
: opencodeRemove?.skippedReason === "unexpected-content"
|
|
139
|
+
? "- Opencode plugin: skipped (unexpected content)"
|
|
140
|
+
: "- Opencode plugin: skipped"
|
|
141
|
+
: `- Opencode plugin: skipped (${opencodeConfigDir} not found)`,
|
|
142
|
+
openclawSessionPluginRemove?.removed
|
|
143
|
+
? `- OpenClaw session plugin removed: ${openclawSessionPluginRemove.openclawConfigPath}`
|
|
144
|
+
: openclawSessionPluginRemove?.skippedReason === "openclaw-config-missing"
|
|
145
|
+
? "- OpenClaw session plugin: skipped (openclaw config not found)"
|
|
146
|
+
: "- OpenClaw session plugin: no change",
|
|
147
|
+
openclawHookRemove?.removed
|
|
148
|
+
? `- OpenClaw hook (legacy) removed: ${openclawHookRemove.openclawConfigPath}`
|
|
149
|
+
: openclawHookRemove?.skippedReason === "openclaw-config-missing"
|
|
150
|
+
? "- OpenClaw hook (legacy): skipped (openclaw config not found)"
|
|
151
|
+
: "- OpenClaw hook (legacy): no change",
|
|
152
|
+
grokHookRemove?.removed
|
|
153
|
+
? `- Grok Build hook removed: ${grokHookRemove.hookPath}`
|
|
154
|
+
: "- Grok Build hook: no change",
|
|
155
|
+
opts.purge ? `- Purged: ${path.join(home, ".tokentracker")}` : "- Purge: skipped (use --purge)",
|
|
156
|
+
"",
|
|
157
|
+
].join("\n"),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function parseArgs(argv) {
|
|
162
|
+
const out = { purge: false };
|
|
163
|
+
for (let i = 0; i < argv.length; i++) {
|
|
164
|
+
const a = argv[i];
|
|
165
|
+
if (a === "--purge") out.purge = true;
|
|
166
|
+
else throw new Error(`Unknown option: ${a}`);
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = { cmdUninstall };
|
|
172
|
+
|
|
173
|
+
async function isFile(p) {
|
|
174
|
+
try {
|
|
175
|
+
const st = await fs.stat(p);
|
|
176
|
+
return st.isFile();
|
|
177
|
+
} catch (_e) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function isDir(p) {
|
|
183
|
+
try {
|
|
184
|
+
const st = await fs.stat(p);
|
|
185
|
+
return st.isDirectory();
|
|
186
|
+
} catch (_e) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const os = require("node:os");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
const fs = require("node:fs");
|
|
6
|
+
|
|
7
|
+
const { resolveTrackerPaths } = require("../lib/tracker-paths");
|
|
8
|
+
const { aggregateWrapped, formatCompact } = require("../lib/wrapped-aggregator");
|
|
9
|
+
|
|
10
|
+
async function readQueueRows(queuePath) {
|
|
11
|
+
if (!fs.existsSync(queuePath)) return [];
|
|
12
|
+
const raw = await fs.promises.readFile(queuePath, "utf8");
|
|
13
|
+
const rows = [];
|
|
14
|
+
for (const line of raw.split("\n")) {
|
|
15
|
+
if (!line.trim()) continue;
|
|
16
|
+
try {
|
|
17
|
+
rows.push(JSON.parse(line));
|
|
18
|
+
} catch (_e) {
|
|
19
|
+
// skip malformed line — production queues sometimes hold a partial
|
|
20
|
+
// tail row mid-write that becomes valid on the next read.
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return rows;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseArgs(argv) {
|
|
27
|
+
const out = { year: null, json: false };
|
|
28
|
+
for (let i = 0; i < argv.length; i++) {
|
|
29
|
+
const a = argv[i];
|
|
30
|
+
if (a === "--json") out.json = true;
|
|
31
|
+
else if (a === "--year") {
|
|
32
|
+
const v = argv[++i];
|
|
33
|
+
const y = Number(v);
|
|
34
|
+
if (!Number.isInteger(y) || y < 2000 || y > 2100) {
|
|
35
|
+
throw new Error(`--year expects a 4-digit year, got: ${v}`);
|
|
36
|
+
}
|
|
37
|
+
out.year = y;
|
|
38
|
+
} else throw new Error(`Unknown option: ${a}`);
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function renderAscii(wrapped) {
|
|
44
|
+
const lines = [];
|
|
45
|
+
const w = 60;
|
|
46
|
+
const border = "═".repeat(w - 2);
|
|
47
|
+
const center = (s) => {
|
|
48
|
+
const text = String(s);
|
|
49
|
+
const padTotal = Math.max(0, w - 2 - text.length);
|
|
50
|
+
const left = Math.floor(padTotal / 2);
|
|
51
|
+
const right = padTotal - left;
|
|
52
|
+
return `║${" ".repeat(left)}${text}${" ".repeat(right)}║`;
|
|
53
|
+
};
|
|
54
|
+
const row = (label, value) => {
|
|
55
|
+
const text = ` ${label}: ${value}`;
|
|
56
|
+
const right = Math.max(0, w - 2 - text.length);
|
|
57
|
+
return `║${text}${" ".repeat(right)}║`;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
lines.push(`╔${border}╗`);
|
|
61
|
+
lines.push(center(""));
|
|
62
|
+
lines.push(center(`TokenTracker Wrapped · ${wrapped.year}`));
|
|
63
|
+
lines.push(center(""));
|
|
64
|
+
lines.push(`╠${border}╣`);
|
|
65
|
+
lines.push(row("Total tokens", formatCompact(wrapped.totals.tokens)));
|
|
66
|
+
lines.push(row("Conversations", wrapped.totals.conversations.toLocaleString("en-US")));
|
|
67
|
+
lines.push(row("Active days", `${wrapped.totals.active_days} / 365`));
|
|
68
|
+
lines.push(row("Tools used", String(wrapped.totals.sources)));
|
|
69
|
+
lines.push(row("Models used", String(wrapped.totals.models)));
|
|
70
|
+
if (wrapped.peak_hour) {
|
|
71
|
+
lines.push(
|
|
72
|
+
row(
|
|
73
|
+
"Peak hour",
|
|
74
|
+
`${String(wrapped.peak_hour.hour).padStart(2, "0")}:00 UTC (${formatCompact(wrapped.peak_hour.tokens)})`,
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (wrapped.longest_streak.days > 0) {
|
|
79
|
+
lines.push(
|
|
80
|
+
row("Longest streak", `${wrapped.longest_streak.days} days (${wrapped.longest_streak.from} → ${wrapped.longest_streak.to})`),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
lines.push(`╠${border}╣`);
|
|
84
|
+
|
|
85
|
+
if (wrapped.top.sources.length > 0) {
|
|
86
|
+
lines.push(row("Top tools", ""));
|
|
87
|
+
for (const s of wrapped.top.sources.slice(0, 3)) {
|
|
88
|
+
lines.push(row(` ${s.source}`, `${formatCompact(s.tokens)} (${(s.share * 100).toFixed(0)}%)`));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (wrapped.top.models.length > 0) {
|
|
92
|
+
lines.push(`╠${border}╣`);
|
|
93
|
+
lines.push(row("Top models", ""));
|
|
94
|
+
for (const m of wrapped.top.models.slice(0, 3)) {
|
|
95
|
+
lines.push(row(` ${m.model}`, `${formatCompact(m.tokens)} (${(m.share * 100).toFixed(0)}%)`));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (wrapped.top.days.length > 0) {
|
|
99
|
+
lines.push(`╠${border}╣`);
|
|
100
|
+
lines.push(row("Top days", ""));
|
|
101
|
+
for (const d of wrapped.top.days.slice(0, 3)) {
|
|
102
|
+
lines.push(row(` ${d.day}`, formatCompact(d.tokens)));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (wrapped.highlights.length > 0) {
|
|
106
|
+
lines.push(`╠${border}╣`);
|
|
107
|
+
const max = w - 4; // 2 borders + 2 padding
|
|
108
|
+
for (const h of wrapped.highlights) {
|
|
109
|
+
// Wrap long highlights into multiple rows.
|
|
110
|
+
for (let start = 0; start < h.length; start += max) {
|
|
111
|
+
const segment = h.slice(start, start + max);
|
|
112
|
+
const padded = ` ${segment}`;
|
|
113
|
+
const right = Math.max(0, w - 2 - padded.length);
|
|
114
|
+
lines.push(`║${padded}${" ".repeat(right)}║`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
lines.push(`╚${border}╝`);
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function cmdWrapped(argv = []) {
|
|
123
|
+
const opts = parseArgs(argv);
|
|
124
|
+
const home = os.homedir();
|
|
125
|
+
const { trackerDir } = await resolveTrackerPaths({ home });
|
|
126
|
+
const queuePath = path.join(trackerDir, "queue.jsonl");
|
|
127
|
+
|
|
128
|
+
const rows = await readQueueRows(queuePath);
|
|
129
|
+
if (rows.length === 0) {
|
|
130
|
+
if (opts.json) {
|
|
131
|
+
process.stdout.write(JSON.stringify({ error: "no data" }, null, 2) + "\n");
|
|
132
|
+
} else {
|
|
133
|
+
process.stdout.write(
|
|
134
|
+
"No queue data found yet. Run `tracker sync` first to ingest some history.\n",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const wrapped = aggregateWrapped(rows, opts.year ? { year: opts.year } : {});
|
|
141
|
+
|
|
142
|
+
if (opts.json) {
|
|
143
|
+
process.stdout.write(JSON.stringify(wrapped, null, 2) + "\n");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
process.stdout.write(renderAscii(wrapped) + "\n");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { cmdWrapped };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const os = require("node:os");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const MAIN_APP_SUBDIR = "antigravity";
|
|
6
|
+
const IDE_SUBDIR = "antigravity-ide";
|
|
7
|
+
|
|
8
|
+
function geminiHome(env) {
|
|
9
|
+
return path.join(env.HOME || env.USERPROFILE || os.homedir(), ".gemini");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function trimmed(value) {
|
|
13
|
+
return typeof value === "string" ? value.trim() : "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function existsDir(dirPath) {
|
|
17
|
+
try {
|
|
18
|
+
return fs.statSync(dirPath).isDirectory();
|
|
19
|
+
} catch (_e) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveAntigravitySkillDirs(env = process.env) {
|
|
25
|
+
const override = trimmed(env.TOKENTRACKER_ANTIGRAVITY_HOME);
|
|
26
|
+
if (override) return [path.join(override, "skills")];
|
|
27
|
+
|
|
28
|
+
const home = geminiHome(env);
|
|
29
|
+
const mainSkills = path.join(home, MAIN_APP_SUBDIR, "skills");
|
|
30
|
+
const ideSkills = path.join(home, IDE_SUBDIR, "skills");
|
|
31
|
+
const mainParent = path.join(home, MAIN_APP_SUBDIR);
|
|
32
|
+
const ideParent = path.join(home, IDE_SUBDIR);
|
|
33
|
+
|
|
34
|
+
const dirs = [];
|
|
35
|
+
if (existsDir(mainParent)) dirs.push(mainSkills);
|
|
36
|
+
if (existsDir(ideParent)) dirs.push(ideSkills);
|
|
37
|
+
|
|
38
|
+
// Neither Antigravity install present: fall back to the main-app path so
|
|
39
|
+
// targetList still surfaces a stable path string and the user can install
|
|
40
|
+
// skills now and run Antigravity later. The empty parent will be created on
|
|
41
|
+
// demand by ensureDir; cleanup happens naturally via uninstallSkill.
|
|
42
|
+
if (dirs.length > 0) return dirs;
|
|
43
|
+
return [mainSkills];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = {
|
|
47
|
+
resolveAntigravitySkillDirs,
|
|
48
|
+
};
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
const http = require("node:http");
|
|
2
|
+
const crypto = require("node:crypto");
|
|
3
|
+
const cp = require("node:child_process");
|
|
4
|
+
|
|
5
|
+
const { DEFAULT_BASE_URL } = require("./runtime-config");
|
|
6
|
+
|
|
7
|
+
async function beginBrowserAuth({ baseUrl, dashboardUrl, timeoutMs, open }) {
|
|
8
|
+
const nonce = crypto.randomBytes(16).toString("hex");
|
|
9
|
+
const callbackPath = `/tokentracker/callback/${nonce}`;
|
|
10
|
+
const authUrl = dashboardUrl ? new URL("/", dashboardUrl) : new URL("/auth/sign-up", baseUrl);
|
|
11
|
+
const postAuthRedirect = resolvePostAuthRedirect({ dashboardUrl, authUrl });
|
|
12
|
+
const { callbackUrl, waitForCallback } = await startLocalCallbackServer({
|
|
13
|
+
callbackPath,
|
|
14
|
+
timeoutMs,
|
|
15
|
+
redirectUrl: postAuthRedirect,
|
|
16
|
+
});
|
|
17
|
+
authUrl.searchParams.set("redirect", callbackUrl);
|
|
18
|
+
if (dashboardUrl && baseUrl && baseUrl !== DEFAULT_BASE_URL) {
|
|
19
|
+
authUrl.searchParams.set("base_url", baseUrl);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (open !== false) openInBrowser(authUrl.toString());
|
|
23
|
+
|
|
24
|
+
return { authUrl: authUrl.toString(), waitForCallback };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function startLocalCallbackServer({ callbackPath, timeoutMs, redirectUrl }) {
|
|
28
|
+
let resolved = false;
|
|
29
|
+
let resolveResult;
|
|
30
|
+
let rejectResult;
|
|
31
|
+
|
|
32
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
33
|
+
resolveResult = resolve;
|
|
34
|
+
rejectResult = reject;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const server = http.createServer((req, res) => {
|
|
38
|
+
if (resolved) {
|
|
39
|
+
res.writeHead(409, { "Content-Type": "text/plain; charset=utf-8" });
|
|
40
|
+
res.end("Already authenticated.\n");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const method = req.method || "GET";
|
|
45
|
+
if (method !== "GET") {
|
|
46
|
+
res.writeHead(405, { "Content-Type": "text/plain; charset=utf-8" });
|
|
47
|
+
res.end("Method not allowed.\n");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const url = new URL(req.url || "/", "http://127.0.0.1");
|
|
52
|
+
if (url.pathname !== callbackPath) {
|
|
53
|
+
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
54
|
+
res.end("Not found.\n");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const accessToken = url.searchParams.get("access_token") || "";
|
|
59
|
+
if (!accessToken) {
|
|
60
|
+
res.writeHead(400, { "Content-Type": "text/plain; charset=utf-8" });
|
|
61
|
+
res.end("Missing access_token.\n");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
resolved = true;
|
|
66
|
+
if (redirectUrl) {
|
|
67
|
+
res.writeHead(302, {
|
|
68
|
+
Location: redirectUrl,
|
|
69
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
70
|
+
});
|
|
71
|
+
res.end(
|
|
72
|
+
[
|
|
73
|
+
"<!doctype html>",
|
|
74
|
+
'<html><head><meta charset="utf-8"><title>TokenTracker</title></head>',
|
|
75
|
+
"<body>",
|
|
76
|
+
"<h2>Login succeeded</h2>",
|
|
77
|
+
`<p>Redirecting to <a href="${redirectUrl}">dashboard</a>...</p>`,
|
|
78
|
+
"</body></html>",
|
|
79
|
+
].join(""),
|
|
80
|
+
);
|
|
81
|
+
} else {
|
|
82
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
83
|
+
res.end(
|
|
84
|
+
[
|
|
85
|
+
"<!doctype html>",
|
|
86
|
+
'<html><head><meta charset="utf-8"><title>TokenTracker</title></head>',
|
|
87
|
+
"<body>",
|
|
88
|
+
"<h2>Login succeeded</h2>",
|
|
89
|
+
"<p>You can close this tab and return to the CLI.</p>",
|
|
90
|
+
"</body></html>",
|
|
91
|
+
].join(""),
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
resolveResult({
|
|
96
|
+
accessToken,
|
|
97
|
+
userId: url.searchParams.get("user_id") || null,
|
|
98
|
+
email: url.searchParams.get("email") || null,
|
|
99
|
+
name: url.searchParams.get("name") || null,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
await new Promise((resolve, reject) => {
|
|
104
|
+
server.once("error", reject);
|
|
105
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const addr = server.address();
|
|
109
|
+
const port = typeof addr === "object" && addr ? addr.port : null;
|
|
110
|
+
if (!port) {
|
|
111
|
+
server.close();
|
|
112
|
+
throw new Error("Failed to bind local callback server");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const callbackUrl = `http://127.0.0.1:${port}${callbackPath}`;
|
|
116
|
+
|
|
117
|
+
const timer = setTimeout(() => {
|
|
118
|
+
if (resolved) return;
|
|
119
|
+
resolved = true;
|
|
120
|
+
rejectResult(new Error("Authentication timed out"));
|
|
121
|
+
server.close();
|
|
122
|
+
}, timeoutMs);
|
|
123
|
+
|
|
124
|
+
async function waitForCallback() {
|
|
125
|
+
try {
|
|
126
|
+
return await resultPromise;
|
|
127
|
+
} finally {
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
server.close();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return { callbackUrl, waitForCallback };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function detectDefaultBrowser() {
|
|
137
|
+
try {
|
|
138
|
+
const raw = cp.execFileSync("defaults", [
|
|
139
|
+
"read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers",
|
|
140
|
+
], { encoding: "utf8", timeout: 3000 });
|
|
141
|
+
const match = raw.match(/https[\s\S]*?LSHandlerRoleAll\s*=\s*"([^"]+)"/);
|
|
142
|
+
if (!match) return null;
|
|
143
|
+
const bundleId = match[1].toLowerCase();
|
|
144
|
+
if (bundleId.includes("chrome")) return "Google Chrome";
|
|
145
|
+
if (bundleId.includes("safari")) return "Safari";
|
|
146
|
+
if (bundleId.includes("edgemac")) return "Microsoft Edge";
|
|
147
|
+
if (bundleId.includes("thebrowser") || bundleId.includes("arc")) return "Arc";
|
|
148
|
+
return null;
|
|
149
|
+
} catch (_e) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function buildBrowserList() {
|
|
155
|
+
const all = ["Google Chrome", "Safari", "Microsoft Edge", "Arc"];
|
|
156
|
+
const def = detectDefaultBrowser();
|
|
157
|
+
if (!def) return all;
|
|
158
|
+
return [def, ...all.filter((b) => b !== def)];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function openInBrowser(url) {
|
|
162
|
+
const platform = process.platform;
|
|
163
|
+
|
|
164
|
+
if (platform === "darwin") {
|
|
165
|
+
// On macOS, prefer reusing a matching tab in a supported running browser.
|
|
166
|
+
// Supported browsers are checked with the user's default browser first.
|
|
167
|
+
const browsers = buildBrowserList();
|
|
168
|
+
const listLiteral = browsers.map((b) => `"${b}"`).join(", ");
|
|
169
|
+
const script = `
|
|
170
|
+
tell application "System Events"
|
|
171
|
+
set browserList to {${listLiteral}}
|
|
172
|
+
set runningBrowser to ""
|
|
173
|
+
repeat with b in browserList
|
|
174
|
+
if (exists process (b as text)) then
|
|
175
|
+
set runningBrowser to (b as text)
|
|
176
|
+
exit repeat
|
|
177
|
+
end if
|
|
178
|
+
end repeat
|
|
179
|
+
end tell
|
|
180
|
+
|
|
181
|
+
if runningBrowser is "" then
|
|
182
|
+
open location "${url}"
|
|
183
|
+
else if runningBrowser is "Google Chrome" then
|
|
184
|
+
tell application "Google Chrome"
|
|
185
|
+
set found to false
|
|
186
|
+
repeat with w in windows
|
|
187
|
+
set tabIndex to 0
|
|
188
|
+
repeat with t in tabs of w
|
|
189
|
+
set tabIndex to tabIndex + 1
|
|
190
|
+
if URL of t starts with "${url}" then
|
|
191
|
+
set active tab index of w to tabIndex
|
|
192
|
+
set index of w to 1
|
|
193
|
+
reload t
|
|
194
|
+
activate
|
|
195
|
+
set found to true
|
|
196
|
+
exit repeat
|
|
197
|
+
end if
|
|
198
|
+
end repeat
|
|
199
|
+
if found then exit repeat
|
|
200
|
+
end repeat
|
|
201
|
+
if not found then
|
|
202
|
+
open location "${url}"
|
|
203
|
+
activate
|
|
204
|
+
end if
|
|
205
|
+
end tell
|
|
206
|
+
else if runningBrowser is "Safari" then
|
|
207
|
+
tell application "Safari"
|
|
208
|
+
set found to false
|
|
209
|
+
repeat with w in windows
|
|
210
|
+
set tabIndex to 0
|
|
211
|
+
repeat with t in tabs of w
|
|
212
|
+
set tabIndex to tabIndex + 1
|
|
213
|
+
if URL of t starts with "${url}" then
|
|
214
|
+
set current tab of w to t
|
|
215
|
+
set index of w to 1
|
|
216
|
+
do JavaScript "location.reload()" in t
|
|
217
|
+
activate
|
|
218
|
+
set found to true
|
|
219
|
+
exit repeat
|
|
220
|
+
end if
|
|
221
|
+
end repeat
|
|
222
|
+
if found then exit repeat
|
|
223
|
+
end repeat
|
|
224
|
+
if not found then
|
|
225
|
+
open location "${url}"
|
|
226
|
+
activate
|
|
227
|
+
end if
|
|
228
|
+
end tell
|
|
229
|
+
else
|
|
230
|
+
open location "${url}"
|
|
231
|
+
end if
|
|
232
|
+
`;
|
|
233
|
+
try {
|
|
234
|
+
const child = cp.spawn("osascript", ["-e", script], { stdio: "ignore", detached: true });
|
|
235
|
+
child.unref();
|
|
236
|
+
} catch (_e) {
|
|
237
|
+
// Fallback to plain open
|
|
238
|
+
try {
|
|
239
|
+
const child = cp.spawn("open", [url], { stdio: "ignore", detached: true });
|
|
240
|
+
child.unref();
|
|
241
|
+
} catch (_e2) {}
|
|
242
|
+
}
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let cmd = null;
|
|
247
|
+
let args = [];
|
|
248
|
+
|
|
249
|
+
if (platform === "win32") {
|
|
250
|
+
cmd = "cmd";
|
|
251
|
+
args = ["/c", "start", "", url];
|
|
252
|
+
} else {
|
|
253
|
+
cmd = "xdg-open";
|
|
254
|
+
args = [url];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
const child = cp.spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
259
|
+
child.unref();
|
|
260
|
+
} catch (_e) {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function resolvePostAuthRedirect({ dashboardUrl, authUrl }) {
|
|
264
|
+
try {
|
|
265
|
+
if (dashboardUrl) {
|
|
266
|
+
const target = new URL("/", dashboardUrl);
|
|
267
|
+
if (target.protocol === "http:" || target.protocol === "https:") {
|
|
268
|
+
return target.toString();
|
|
269
|
+
}
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
} catch (_e) {
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
module.exports = {
|
|
279
|
+
beginBrowserAuth,
|
|
280
|
+
openInBrowser,
|
|
281
|
+
};
|