@zhangferry-dev/tokendash 1.6.1 → 1.7.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.
Files changed (61) hide show
  1. package/README.md +148 -84
  2. package/dist/client/assets/index-Bw503sNp.css +1 -0
  3. package/dist/client/index.html +2 -2
  4. package/dist/daemon.cjs +3411 -0
  5. package/dist/daemon.cjs.map +7 -0
  6. package/dist/electron-server.cjs +1124 -28
  7. package/dist/electron-server.cjs.map +4 -4
  8. package/dist/server/ccusage.d.ts +7 -0
  9. package/dist/server/ccusage.js +69 -0
  10. package/dist/server/daemon.d.ts +12 -0
  11. package/dist/server/daemon.js +176 -0
  12. package/dist/server/index.js +23 -11
  13. package/dist/server/insightsCalculator.d.ts +15 -0
  14. package/dist/server/insightsCalculator.js +276 -0
  15. package/dist/server/quota/adapter.d.ts +49 -0
  16. package/dist/server/quota/adapter.js +41 -0
  17. package/dist/server/quota/adapters/claude.d.ts +4 -0
  18. package/dist/server/quota/adapters/claude.js +152 -0
  19. package/dist/server/quota/adapters/codex.d.ts +16 -0
  20. package/dist/server/quota/adapters/codex.js +226 -0
  21. package/dist/server/quota/adapters/glm.d.ts +2 -0
  22. package/dist/server/quota/adapters/glm.js +139 -0
  23. package/dist/server/quota/adapters/kimi.d.ts +2 -0
  24. package/dist/server/quota/adapters/kimi.js +186 -0
  25. package/dist/server/quota/adapters/minimax.d.ts +2 -0
  26. package/dist/server/quota/adapters/minimax.js +82 -0
  27. package/dist/server/quota/cache.d.ts +20 -0
  28. package/dist/server/quota/cache.js +44 -0
  29. package/dist/server/quota/credentialsFile.d.ts +13 -0
  30. package/dist/server/quota/credentialsFile.js +23 -0
  31. package/dist/server/quota/helpers.d.ts +39 -0
  32. package/dist/server/quota/helpers.js +93 -0
  33. package/dist/server/quota/index.d.ts +5 -0
  34. package/dist/server/quota/index.js +23 -0
  35. package/dist/server/quota/quotaService.d.ts +43 -0
  36. package/dist/server/quota/quotaService.js +163 -0
  37. package/dist/server/quota/schemas.d.ts +358 -0
  38. package/dist/server/quota/schemas.js +53 -0
  39. package/dist/server/quota/types.d.ts +76 -0
  40. package/dist/server/quota/types.js +10 -0
  41. package/dist/server/routes/api.js +34 -0
  42. package/dist/server/routes/insights.d.ts +2 -0
  43. package/dist/server/routes/insights.js +155 -0
  44. package/package.json +9 -11
  45. package/resources/entitlements.mac.plist +10 -0
  46. package/resources/icon-1024.png +0 -0
  47. package/resources/icon.icns +0 -0
  48. package/resources/icon.png +0 -0
  49. package/resources/product_menu.png +0 -0
  50. package/resources/readme-hero.png +0 -0
  51. package/dist/client/assets/index-_yA9tOzZ.css +0 -1
  52. package/electron/main.cjs +0 -516
  53. package/electron/npmSync.cjs +0 -62
  54. package/electron/preload.cjs +0 -36
  55. package/electron/serverReuse.cjs +0 -59
  56. package/electron/trayBadge.cjs +0 -27
  57. package/electron/trayHelper +0 -0
  58. package/electron/trayHelper.swift +0 -152
  59. package/electron/updateService.cjs +0 -220
  60. package/electron-builder.yml +0 -20
  61. /package/dist/client/assets/{index-CY4G_b0x.js → index-C913wKtU.js} +0 -0
@@ -1,152 +0,0 @@
1
- import Cocoa
2
-
3
- // TokenDash Native Tray Helper for macOS 26+
4
- // Communicates with Electron main process via stdin/stdout
5
- // Protocol:
6
- // stdin commands: "title:<text>\n" "tooltip:<text>\n" "quit\n"
7
- // stdout events: "click:<screenX>,<screenY>\n"
8
-
9
- class AppDelegate: NSObject, NSApplicationDelegate {
10
- var statusItem: NSStatusItem!
11
- var readHandle: FileHandle?
12
- var currentTitle = "0"
13
-
14
- func applicationDidFinishLaunching(_ notification: Notification) {
15
- // Create status bar item
16
- statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
17
-
18
- statusItem.button?.image = renderCombinedImage(title: currentTitle)
19
- statusItem.button?.imagePosition = .imageOnly
20
- statusItem.button?.imageScaling = .scaleProportionallyDown
21
- statusItem.button?.isBordered = false
22
- statusItem.button?.title = ""
23
- statusItem.button?.toolTip = "TokenDash"
24
-
25
- // Set up click actions — both left and right click
26
- statusItem.button?.target = self
27
- statusItem.button?.action = #selector(handleClick(_:))
28
- statusItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
29
-
30
- // Read commands from stdin
31
- readHandle = FileHandle.standardInput
32
- NotificationCenter.default.addObserver(
33
- self,
34
- selector: #selector(handleStdin),
35
- name: .NSFileHandleDataAvailable,
36
- object: readHandle
37
- )
38
- readHandle?.waitForDataInBackgroundAndNotify()
39
-
40
- // Signal ready
41
- sendEvent("ready")
42
- }
43
-
44
- /// Render icon + title text into a single template image for the status bar.
45
- func renderCombinedImage(title: String) -> NSImage {
46
- let iconW: CGFloat = 18
47
- let iconH: CGFloat = 18
48
- let fontSize: CGFloat = 13
49
- let font = NSFont.monospacedSystemFont(ofSize: fontSize, weight: .medium)
50
- let textAttrs: [NSAttributedString.Key: Any] = [.font: font]
51
- let textWidth = (title as NSString).size(withAttributes: textAttrs).width
52
- let padding: CGFloat = 4 // gap between icon and text
53
-
54
- let totalWidth = iconW + padding + textWidth
55
- // Status bar height is ~22pt; center vertically
56
- let totalHeight: CGFloat = 20
57
-
58
- let image = NSImage(size: NSSize(width: totalWidth, height: totalHeight))
59
- image.lockFocus()
60
-
61
- // Draw icon centered vertically
62
- let icon = createTemplateIcon(size: NSSize(width: iconW, height: iconH))
63
- let iconY = (totalHeight - iconH) / 2.0
64
- icon.draw(in: NSRect(x: 0, y: iconY, width: iconW, height: iconH))
65
-
66
- // Draw text centered vertically (baseline-adjusted)
67
- let textY = (totalHeight - fontSize) / 2.0 - 1
68
- (title as NSString).draw(at: NSPoint(x: iconW + padding, y: textY), withAttributes: textAttrs)
69
-
70
- image.unlockFocus()
71
- image.isTemplate = true
72
- return image
73
- }
74
-
75
- @objc func handleClick(_ sender: Any?) {
76
- guard let _ = NSApp.currentEvent else { return }
77
- let loc = NSEvent.mouseLocation
78
- sendEvent("click:\(Int(loc.x)),\(Int(loc.y))")
79
- }
80
-
81
- @objc func handleStdin() {
82
- guard let data = readHandle?.availableData, data.count > 0 else {
83
- readHandle?.waitForDataInBackgroundAndNotify()
84
- return
85
- }
86
-
87
- if let line = String(data: data, encoding: .utf8) {
88
- for command in line.split(separator: "\n") {
89
- let cmd = command.trimmingCharacters(in: .whitespacesAndNewlines)
90
- if cmd.hasPrefix("title:") {
91
- let title = String(cmd.dropFirst(6))
92
- currentTitle = title
93
- statusItem.button?.image = renderCombinedImage(title: title)
94
- } else if cmd.hasPrefix("tooltip:") {
95
- let tooltip = String(cmd.dropFirst(8))
96
- statusItem.button?.toolTip = tooltip
97
- } else if cmd == "quit" {
98
- NSApp.terminate(nil)
99
- return
100
- }
101
- }
102
- }
103
-
104
- readHandle?.waitForDataInBackgroundAndNotify()
105
- }
106
-
107
- func sendEvent(_ event: String) {
108
- print(event)
109
- fflush(stdout)
110
- }
111
-
112
- func createTemplateIcon(size: NSSize) -> NSImage {
113
- let image = NSImage(size: size)
114
- image.lockFocus()
115
-
116
- let sx = size.width / 64.0
117
- let sy = size.height / 64.0
118
-
119
- let path = NSBezierPath()
120
- path.move(to: NSPoint(x: 6 * sx, y: (64 - 32) * sy))
121
- path.line(to: NSPoint(x: 18 * sx, y: (64 - 32) * sy))
122
- path.curve(to: NSPoint(x: 24.5 * sx, y: (64 - 39) * sy),
123
- controlPoint1: NSPoint(x: 21 * sx, y: (64 - 32) * sy),
124
- controlPoint2: NSPoint(x: 22.5 * sx, y: (64 - 34) * sy))
125
- path.curve(to: NSPoint(x: 34 * sx, y: (64 - 50) * sy),
126
- controlPoint1: NSPoint(x: 27 * sx, y: (64 - 45.5) * sy),
127
- controlPoint2: NSPoint(x: 30 * sx, y: (64 - 50) * sy))
128
- path.curve(to: NSPoint(x: 44 * sx, y: (64 - 22) * sy),
129
- controlPoint1: NSPoint(x: 38 * sx, y: (64 - 50) * sy),
130
- controlPoint2: NSPoint(x: 40.5 * sx, y: (64 - 42) * sy))
131
- path.curve(to: NSPoint(x: 52 * sx, y: (64 - 8) * sy),
132
- controlPoint1: NSPoint(x: 46 * sx, y: (64 - 11) * sy),
133
- controlPoint2: NSPoint(x: 49 * sx, y: (64 - 8) * sy))
134
- path.curve(to: NSPoint(x: 60 * sx, y: (64 - 22) * sy),
135
- controlPoint1: NSPoint(x: 55 * sx, y: (64 - 8) * sy),
136
- controlPoint2: NSPoint(x: 57.5 * sx, y: (64 - 13) * sy))
137
-
138
- path.lineWidth = 5 * sx
139
- path.lineCapStyle = .round
140
- path.lineJoinStyle = .round
141
- NSColor.black.setStroke()
142
- path.stroke()
143
-
144
- image.unlockFocus()
145
- image.isTemplate = true
146
- return image
147
- }
148
- }
149
-
150
- let delegate = AppDelegate()
151
- NSApplication.shared.delegate = delegate
152
- NSApp.run()
@@ -1,220 +0,0 @@
1
- const fs = require('node:fs');
2
- const https = require('node:https');
3
- const path = require('node:path');
4
-
5
- function fetchHttpsJson(url) {
6
- return new Promise((resolve, reject) => {
7
- const opts = new URL(url);
8
- const reqOpts = {
9
- hostname: opts.hostname,
10
- path: opts.pathname + opts.search,
11
- method: 'GET',
12
- headers: {
13
- Accept: 'application/vnd.github+json',
14
- 'User-Agent': 'TokenDash',
15
- },
16
- };
17
-
18
- https.get(reqOpts, (res) => {
19
- let data = '';
20
- res.on('data', (chunk) => { data += chunk; });
21
- res.on('end', () => {
22
- if (res.statusCode && res.statusCode >= 400) {
23
- reject(new Error(`HTTP ${res.statusCode}`));
24
- return;
25
- }
26
- try { resolve(JSON.parse(data)); }
27
- catch (e) { reject(e); }
28
- });
29
- }).on('error', reject);
30
- });
31
- }
32
-
33
- function fetchLatestReleaseUrl(repo) {
34
- const url = `https://github.com/${repo}/releases/latest`;
35
- return new Promise((resolve, reject) => {
36
- const opts = new URL(url);
37
- const reqOpts = {
38
- hostname: opts.hostname,
39
- path: opts.pathname + opts.search,
40
- method: 'HEAD',
41
- headers: {
42
- 'User-Agent': 'TokenDash',
43
- },
44
- };
45
-
46
- https.request(reqOpts, (res) => {
47
- if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
48
- resolve(new URL(res.headers.location, url).toString());
49
- return;
50
- }
51
-
52
- if (res.statusCode && res.statusCode >= 400) {
53
- reject(new Error(`HTTP ${res.statusCode}`));
54
- return;
55
- }
56
-
57
- resolve(url);
58
- }).on('error', reject).end();
59
- });
60
- }
61
-
62
- function compareVersions(a, b) {
63
- const aParts = String(a).replace(/^v/, '').split(/[.-]/).map((part) => parseInt(part, 10) || 0);
64
- const bParts = String(b).replace(/^v/, '').split(/[.-]/).map((part) => parseInt(part, 10) || 0);
65
- const maxLen = Math.max(aParts.length, bParts.length);
66
- for (let i = 0; i < maxLen; i++) {
67
- const delta = (aParts[i] || 0) - (bParts[i] || 0);
68
- if (delta !== 0) return delta;
69
- }
70
- return 0;
71
- }
72
-
73
- function isDmgAsset(asset) {
74
- return Boolean(asset && typeof asset.name === 'string' && /\.dmg$/i.test(asset.name));
75
- }
76
-
77
- function selectMacDmgAsset(assets, arch = process.arch) {
78
- const dmgAssets = (Array.isArray(assets) ? assets : []).filter(isDmgAsset);
79
- if (dmgAssets.length === 0) return null;
80
-
81
- const archNeedle = arch === 'arm64' ? 'arm64' : arch === 'x64' ? 'x64' : '';
82
- if (archNeedle) {
83
- const archMatch = dmgAssets.find((asset) => asset.name.toLowerCase().includes(archNeedle));
84
- if (archMatch) return archMatch;
85
- }
86
-
87
- const universal = dmgAssets.find((asset) => /universal/i.test(asset.name));
88
- return universal || dmgAssets[0];
89
- }
90
-
91
- function getReleaseUpdateInfo(release, currentVersion, arch = process.arch) {
92
- const tag = String((release && release.tag_name) || '').replace(/^v/, '');
93
- const latestVersion = tag || currentVersion;
94
- const asset = selectMacDmgAsset(release && release.assets, arch);
95
- const upToDate = compareVersions(currentVersion, latestVersion) >= 0;
96
-
97
- return {
98
- currentVersion,
99
- latestVersion,
100
- upToDate,
101
- releaseUrl: (release && release.html_url) || null,
102
- asset: asset ? {
103
- name: asset.name,
104
- size: Number(asset.size) || 0,
105
- url: asset.browser_download_url,
106
- } : null,
107
- };
108
- }
109
-
110
- function buildDmgAssetFromVersion(repo, tagName, arch = process.arch) {
111
- const version = String(tagName || '').replace(/^v/, '');
112
- const archSuffix = arch === 'arm64' ? 'arm64' : arch === 'x64' ? 'x64' : 'universal';
113
- const name = `TokenDash-${version}-${archSuffix}.dmg`;
114
-
115
- return {
116
- name,
117
- size: 0,
118
- url: `https://github.com/${repo}/releases/download/${tagName}/${name}`,
119
- };
120
- }
121
-
122
- function getRedirectReleaseUpdateInfo(repo, releaseUrl, currentVersion, arch = process.arch) {
123
- const parsedUrl = new URL(releaseUrl);
124
- const tagMatch = parsedUrl.pathname.match(/\/releases\/tag\/([^/]+)\/?$/);
125
- if (!tagMatch) throw new Error('Unable to determine the latest release tag.');
126
-
127
- const tagName = decodeURIComponent(tagMatch[1]);
128
- const latestVersion = tagName.replace(/^v/, '');
129
- const upToDate = compareVersions(currentVersion, latestVersion) >= 0;
130
-
131
- return {
132
- currentVersion,
133
- latestVersion,
134
- upToDate,
135
- releaseUrl: parsedUrl.toString(),
136
- asset: upToDate ? null : buildDmgAssetFromVersion(repo, tagName, arch),
137
- };
138
- }
139
-
140
- async function checkForUpdates({
141
- repo,
142
- currentVersion,
143
- arch = process.arch,
144
- fetchReleaseJson = fetchHttpsJson,
145
- fetchLatestReleaseUrl: fetchLatestReleaseUrlOverride = fetchLatestReleaseUrl,
146
- }) {
147
- try {
148
- const release = await fetchReleaseJson(`https://api.github.com/repos/${repo}/releases/latest`);
149
- return getReleaseUpdateInfo(release, currentVersion, arch);
150
- } catch (error) {
151
- const releaseUrl = await fetchLatestReleaseUrlOverride(repo);
152
- return getRedirectReleaseUpdateInfo(repo, releaseUrl, currentVersion, arch);
153
- }
154
- }
155
-
156
- function safeDownloadName(name) {
157
- return path.basename(String(name || 'TokenDash-update.dmg')).replace(/[^\w .()+@-]/g, '-');
158
- }
159
-
160
- function downloadFile(url, destination, onProgress) {
161
- return new Promise((resolve, reject) => {
162
- const file = fs.createWriteStream(destination);
163
- let received = 0;
164
-
165
- const request = https.get(url, { headers: { 'User-Agent': 'TokenDash' } }, (res) => {
166
- if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
167
- file.close(() => fs.rm(destination, { force: true }, () => {}));
168
- downloadFile(res.headers.location, destination, onProgress).then(resolve, reject);
169
- return;
170
- }
171
-
172
- if (res.statusCode && res.statusCode >= 400) {
173
- file.close(() => fs.rm(destination, { force: true }, () => {}));
174
- reject(new Error(`HTTP ${res.statusCode}`));
175
- return;
176
- }
177
-
178
- const total = Number(res.headers['content-length']) || 0;
179
- res.on('data', (chunk) => {
180
- received += chunk.length;
181
- if (typeof onProgress === 'function') {
182
- onProgress({ received, total, percent: total > 0 ? Math.round((received / total) * 100) : null });
183
- }
184
- });
185
- res.pipe(file);
186
- });
187
-
188
- request.on('error', (error) => {
189
- file.close(() => fs.rm(destination, { force: true }, () => {}));
190
- reject(error);
191
- });
192
-
193
- file.on('finish', () => {
194
- file.close(() => resolve(destination));
195
- });
196
-
197
- file.on('error', (error) => {
198
- file.close(() => fs.rm(destination, { force: true }, () => {}));
199
- reject(error);
200
- });
201
- });
202
- }
203
-
204
- async function downloadUpdateAsset(asset, downloadsDir, onProgress) {
205
- if (!asset || !asset.url) throw new Error('No downloadable macOS update asset was found.');
206
- fs.mkdirSync(downloadsDir, { recursive: true });
207
- const destination = path.join(downloadsDir, safeDownloadName(asset.name));
208
- await downloadFile(asset.url, destination, onProgress);
209
- return destination;
210
- }
211
-
212
- module.exports = {
213
- checkForUpdates,
214
- compareVersions,
215
- downloadUpdateAsset,
216
- fetchLatestReleaseUrl,
217
- getRedirectReleaseUpdateInfo,
218
- getReleaseUpdateInfo,
219
- selectMacDmgAsset,
220
- };
@@ -1,20 +0,0 @@
1
- appId: com.zhangferry-dev.tokendash
2
- productName: TokenDash
3
- directories:
4
- output: release
5
- files:
6
- - dist/**/*
7
- - bin/**/*
8
- - electron/**/*
9
- - package.json
10
- mac:
11
- target: dmg
12
- category: public.app-category.developer-tools
13
- icon: resources/icon.icns
14
- identity: null
15
- darkModeSupport: true
16
- minimumSystemVersion: '14.0'
17
- extendInfo:
18
- LSUIElement: true
19
- extraMetadata:
20
- main: electron/main.cjs