tokalytics 2.0.0 → 2.0.6

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
@@ -41,6 +41,18 @@ npm run start # compila e em seguida executa ./tokalytics
41
41
 
42
42
  Na primeira execução o app sobe o **servidor HTTP na porta `3456`** e o ícone na barra de menus. Use **«Abrir Dashboard»** no menu ou acesse [http://127.0.0.1:3456](http://127.0.0.1:3456).
43
43
 
44
+ ### Instalação global pelo npm
45
+
46
+ ```bash
47
+ npm install -g tokalytics
48
+ ```
49
+
50
+ Instalação a partir do repositório (sempre o `postinstall` da branch atual):
51
+
52
+ ```bash
53
+ npm install -g "github:kaicmurilo/Tokalytics"
54
+ ```
55
+
44
56
  ## Dashboard
45
57
 
46
58
  Interface web em abas:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokalytics",
3
- "version": "2.0.0",
3
+ "version": "2.0.6",
4
4
  "description": "Veja para onde vão seus tokens do Claude Code, Gemini CLI, Cursor e Codex. Reescrito em Go como Tokalytics.",
5
5
  "bin": {
6
6
  "tokalytics": "bin/run.js"
@@ -13,7 +13,8 @@
13
13
  "postinstall": "node scripts/postinstall.js",
14
14
  "start": "go build -o tokalytics main.go && ./tokalytics",
15
15
  "dev": "go run main.go",
16
- "build": "go build -o tokalytics main.go"
16
+ "build": "go build -o tokalytics main.go",
17
+ "release:npm": "npm publish --access public"
17
18
  },
18
19
  "keywords": [
19
20
  "claude",
@@ -31,6 +32,6 @@
31
32
  "license": "MIT",
32
33
  "repository": {
33
34
  "type": "git",
34
- "url": "git+https://github.com/kaicmurilo/tokalytics.git"
35
+ "url": "git+https://github.com/kaicmurilo/Tokalytics.git"
35
36
  }
36
37
  }
@@ -4,9 +4,18 @@
4
4
  const https = require('https');
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
- const { execSync } = require('child_process');
8
7
 
9
- const REPO = 'kaicmurilo/tokalytics';
8
+ const pkgPath = path.join(__dirname, '..', 'package.json');
9
+ const pkgVersion = (() => {
10
+ try {
11
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version || '?';
12
+ } catch {
13
+ return '?';
14
+ }
15
+ })();
16
+
17
+ // Repositório público no GitHub (nome real: Tokalytics)
18
+ const REPO = 'kaicmurilo/Tokalytics';
10
19
  const BIN_DIR = path.join(__dirname, '..', 'bin');
11
20
  const BIN_PATH = path.join(BIN_DIR, process.platform === 'win32' ? 'tokalytics.exe' : 'tokalytics');
12
21
 
@@ -30,9 +39,17 @@ function getPlatformBinary() {
30
39
  return name;
31
40
  }
32
41
 
33
- function fetchJson(url) {
42
+ function fetchJson(url, extraHeaders = {}) {
34
43
  return new Promise((resolve, reject) => {
35
- const options = { headers: { 'User-Agent': 'tokalytics-installer' } };
44
+ const headers = {
45
+ 'User-Agent': 'tokalytics-installer',
46
+ Accept: 'application/vnd.github+json',
47
+ ...extraHeaders,
48
+ };
49
+ if (process.env.GITHUB_TOKEN) {
50
+ headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
51
+ }
52
+ const options = { headers };
36
53
  https.get(url, options, (res) => {
37
54
  if (res.statusCode === 302 || res.statusCode === 301) {
38
55
  return fetchJson(res.headers.location).then(resolve).catch(reject);
@@ -40,8 +57,24 @@ function fetchJson(url) {
40
57
  let data = '';
41
58
  res.on('data', (chunk) => (data += chunk));
42
59
  res.on('end', () => {
43
- try { resolve(JSON.parse(data)); }
44
- catch (e) { reject(new Error('Resposta inválida da API do GitHub')); }
60
+ if (res.statusCode && res.statusCode >= 400) {
61
+ try {
62
+ const j = JSON.parse(data);
63
+ if (j && j.message) {
64
+ return reject(
65
+ new Error(`GitHub HTTP ${res.statusCode}: ${j.message}`)
66
+ );
67
+ }
68
+ } catch (_) {
69
+ /* fallthrough */
70
+ }
71
+ return reject(new Error(`GitHub HTTP ${res.statusCode}`));
72
+ }
73
+ try {
74
+ resolve(JSON.parse(data));
75
+ } catch {
76
+ reject(new Error('Resposta inválida da API do GitHub'));
77
+ }
45
78
  });
46
79
  }).on('error', reject);
47
80
  });
@@ -70,14 +103,40 @@ function downloadFile(url, dest) {
70
103
  });
71
104
  }
72
105
 
106
+ function assertReleasePayload(release) {
107
+ if (!release || typeof release !== 'object') {
108
+ throw new Error('Resposta inválida da API do GitHub (corpo vazio).');
109
+ }
110
+ if (release.message && !release.tag_name) {
111
+ let hint = '';
112
+ if (/rate limit/i.test(release.message) && !process.env.GITHUB_TOKEN) {
113
+ hint = ' Defina GITHUB_TOKEN no ambiente para aumentar o limite.';
114
+ } else if (/not found/i.test(release.message)) {
115
+ hint =
116
+ ` Confirme que https://github.com/${REPO} existe, é público e tem pelo menos uma release com binários anexados.`;
117
+ }
118
+ throw new Error(`GitHub: ${release.message}${hint}`);
119
+ }
120
+ if (!release.tag_name) {
121
+ throw new Error('Release sem tag_name; verifique se existe release no repositório.');
122
+ }
123
+ if (!Array.isArray(release.assets)) {
124
+ throw new Error(
125
+ 'Resposta da API sem lista de assets. Possível rate limit ou repositório/release inexistente.'
126
+ );
127
+ }
128
+ }
129
+
73
130
  async function install() {
74
- console.log('Tokalytics: buscando última versão...');
131
+ console.log(`Tokalytics installer v${pkgVersion}: buscando última versão...`);
75
132
 
76
133
  const release = await fetchJson(`https://api.github.com/repos/${REPO}/releases/latest`);
134
+ assertReleasePayload(release);
77
135
  const version = release.tag_name;
78
136
  const binaryName = getPlatformBinary();
79
137
 
80
- const asset = release.assets.find((a) => a.name === binaryName);
138
+ const assets = Array.isArray(release.assets) ? release.assets : [];
139
+ const asset = assets.find((a) => a && a.name === binaryName);
81
140
  if (!asset) {
82
141
  throw new Error(`Binário "${binaryName}" não encontrado na release ${version}`);
83
142
  }