vibestreak 0.3.0 → 0.3.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,53 @@
1
+ # Changelog
2
+
3
+ Any version runs with `npx vibestreak@<version>`; `npx vibestreak@latest autosync on`
4
+ moves the background sync to the newest one.
5
+
6
+ ## 0.3.2 (2026-10-25)
7
+
8
+ - Linking says up front that your profile is public (and how to hide it), and
9
+ that linking means agreeing to the Terms and Privacy policy.
10
+ - If the server ever stops accepting this version, the background sync shows
11
+ a notification (once a day) with the command that updates it, instead of
12
+ failing quietly while your streak runs out.
13
+
14
+ ## 0.3.1 (2026-10-25)
15
+
16
+ - `vibestreak --version` (or `-v`), and the version in `help` and `status`.
17
+ - Says when a newer version is out: after `vibestreak`, `sync` and in `status`.
18
+ - Sends its version with each sync. Settings shows each machine's version, and
19
+ the server can turn away versions too old to count correctly, with the
20
+ command that updates them.
21
+
22
+ ## 0.3.0 (2026-10-25)
23
+
24
+ - First run: plain `npx vibestreak` signs in through the browser (no token to
25
+ copy), syncs, and offers background sync and the evening nudge.
26
+ - `vibestreak setup [--token <t>]`: all of that with the defaults and no
27
+ questions; commit addresses come from git config.
28
+ - `vibestreak login` signs in through the browser; `--token` still works.
29
+ - `vibestreak pause [1h|3d|tomorrow]` and `resume`: the background syncs and
30
+ nudge skip their runs for up to 7 days, and nothing is lost.
31
+
32
+ ## 0.2.0 (2026-10-25)
33
+
34
+ - `vibestreak` shows a dashboard: streak, level, a 12-week heatmap and your rank.
35
+ - `vibestreak me`, `profile <handle>`, `board`, `today` in the terminal.
36
+ - Syncs after the first one send only the last 7 days: about 3 seconds.
37
+
38
+ ## 0.1.2 (2026-10-25)
39
+
40
+ - `vibestreak nudge at <time>`: when the evening streak check runs (17:00 to
41
+ 23:30; 20:30 by default).
42
+
43
+ ## 0.1.1 (2026-10-25)
44
+
45
+ - `vibestreak status`; plain `vibestreak nudge` shows the nudge's status.
46
+ - The evening job runs `nudge run` (older schedules keep working).
47
+
48
+ ## 0.1.0 (2026-10-25)
49
+
50
+ - First release: reads Claude Code, Codex, Cursor, Copilot (CLI and VS Code),
51
+ Gemini CLI, Antigravity and OpenCode history and git; syncs daily totals.
52
+ - Background sync on macOS (launchd), Linux (systemd or cron) and Windows
53
+ (Task Scheduler), with the evening streak nudge.
@@ -10,12 +10,13 @@ import { CONFIG_PATH, loadConfig, saveConfig } from '../src/config.mjs'
10
10
  import { configuredEmails, DEFAULT_ROOTS, globalEmail, scanEmails } from '../src/git.mjs'
11
11
  import { deviceLogin } from '../src/login.mjs'
12
12
  import { parsePause, pausedUntil, untilText } from '../src/pause.mjs'
13
+ import { updateCommand, updateNotice, VERSION } from '../src/version.mjs'
13
14
  import { boardView, dashboardView, profileView, todayView } from '../src/views.mjs'
14
15
 
15
16
  const BRAND = process.env.VIBESTREAK_BRAND ?? 'vibestreak'
16
17
  const DEFAULT_SERVER = process.env.VIBESTREAK_SERVER ?? 'https://vibestreak.kingsbridge-consultancy.com'
17
18
 
18
- const HELP = `${BRAND} — share your AI coding heatmap: what you shipped, and how steadily.
19
+ const HELP = `${BRAND} ${VERSION} — share your AI coding heatmap: what you shipped, and how steadily.
19
20
 
20
21
  ${BRAND} your streak, heatmap and rank at a glance
21
22
  (the first time: sets this machine up)
@@ -44,6 +45,7 @@ const HELP = `${BRAND} — share your AI coding heatmap: what you shipped, and h
44
45
  days; default until tomorrow 06:00)
45
46
  ${BRAND} resume end a pause now
46
47
  ${BRAND} whoami the profile this machine syncs to
48
+ ${BRAND} --version this CLI's version
47
49
 
48
50
  Reads: Claude Code, Codex, Cursor, Copilot (CLI and VS Code), Gemini CLI,
49
51
  Antigravity and OpenCode history, and git.
@@ -59,10 +61,10 @@ async function api(config, path, init = {}) {
59
61
  if (!config.server || !config.token) throw new Error(`Not linked yet: run \`${BRAND} login\`.`)
60
62
  const response = await fetch(new URL(path, config.server), {
61
63
  ...init,
62
- headers: { authorization: `Bearer ${config.token}`, 'content-type': 'application/json', ...init.headers },
64
+ headers: { authorization: `Bearer ${config.token}`, 'content-type': 'application/json', 'x-vibestreak-cli': VERSION, 'user-agent': `${BRAND}/${VERSION}`, ...init.headers },
63
65
  })
64
66
  const body = await response.json().catch(() => ({}))
65
- if (!response.ok) throw new Error(body.error ?? `${response.status} ${response.statusText}`)
67
+ if (!response.ok) throw Object.assign(new Error(body.error ?? `${response.status} ${response.statusText}`), { status: response.status })
66
68
  return body
67
69
  }
68
70
 
@@ -83,6 +85,7 @@ async function main() {
83
85
  // Set by the background jobs, so a pause skips them and nothing else.
84
86
  scheduled: { type: 'boolean' },
85
87
  yes: { type: 'boolean', short: 'y' },
88
+ version: { type: 'boolean', short: 'v' },
86
89
  },
87
90
  })
88
91
  const [command = 'dashboard', ...rest] = positionals
@@ -90,6 +93,7 @@ async function main() {
90
93
  const log = (...args) => values.quiet || console.log(...args)
91
94
 
92
95
  if (command === 'help' || values.help) return console.log(HELP)
96
+ if (command === 'version' || values.version) return console.log(VERSION)
93
97
  const columns = process.stdout.columns || 100
94
98
  const server = config.server ?? DEFAULT_SERVER
95
99
 
@@ -101,7 +105,8 @@ async function main() {
101
105
  return setup(config, values, { ask: !values.yes })
102
106
  }
103
107
  const me = await api(config, '/api/v1/me')
104
- return dashboardView({ server, token: config.token, handle: me.handle, columns })
108
+ await dashboardView({ server, token: config.token, handle: me.handle, columns })
109
+ return notice(config, me.cli)
105
110
  }
106
111
  if (command === 'me') {
107
112
  const me = await api(config, '/api/v1/me')
@@ -175,7 +180,7 @@ async function main() {
175
180
  if (command === 'collect' || command === 'sync') {
176
181
  const until = values.scheduled && pausedUntil(config)
177
182
  if (until) return console.log(`${new Date().toISOString()} paused until ${until.toISOString()}; skipped`)
178
- return sync(config, values, log, command === 'sync')
183
+ return sync(config, values, log, command === 'sync').catch(error => (values.scheduled ? tooOldNotice(config, error) : Promise.reject(error)))
179
184
  }
180
185
 
181
186
  if (command === 'status') return status(config)
@@ -208,7 +213,7 @@ async function main() {
208
213
  // `nudge run`, the evening job: sync, then nudge if the streak would end at midnight.
209
214
  const until = pausedUntil(config)
210
215
  if (until) return console.log(`${new Date().toISOString()} paused until ${until.toISOString()}; skipped`)
211
- const result = await sync(config, values, log, true)
216
+ const result = await sync(config, values, log, true).catch(error => tooOldNotice(config, error))
212
217
  if (shouldNudge(result.streak, config)) {
213
218
  await notify(nudgeText(result.streak))
214
219
  config.lastNudge = result.streak.today
@@ -242,6 +247,9 @@ async function main() {
242
247
  // settings page (for machines without one).
243
248
  async function login(config, values, { addresses = true } = {}) {
244
249
  config.server = values.server ?? config.server ?? DEFAULT_SERVER
250
+ const site = config.server.replace(/\/$/, '')
251
+ console.log(`Your profile (GitHub username, avatar, heatmap, streak) will be public on ${BRAND} and its leaderboard; you can hide it in Settings.`)
252
+ console.log(`Linking means you agree to the Terms (${site}/terms) and Privacy policy (${site}/privacy).\n`)
245
253
  if (values.token) config.token = values.token
246
254
  else config.token = (await deviceLogin({ server: config.server, machine: config.machineName })).token
247
255
  const me = await api(config, '/api/v1/me')
@@ -323,9 +331,28 @@ async function sync(config, values, log, isSync) {
323
331
  // Always one line, quiet or not: the background sync's log is how a person sees it ran.
324
332
  const streak = result.streak ? ` · streak ${result.streak.current}d${result.streak.activeToday ? '' : ' (not yet today)'}` : ''
325
333
  console.log(`${config.lastSync} uploaded ${result.days} days, ${result.commits} commits from ${s.repos} repos${streak} → ${result.profileUrl}`)
334
+ if (!values.quiet) notice(config, result.cli)
326
335
  return result
327
336
  }
328
337
 
338
+ // A background run the server turned away as too old: nobody reads the log,
339
+ // and the streak would end unnoticed, so say so on screen, once a day.
340
+ async function tooOldNotice(config, error) {
341
+ const today = new Date().toISOString().slice(0, 10)
342
+ if (error.status === 426 && config.lastUpdateNotice !== today) {
343
+ await notify({ title: 'Update to keep your streak', message: `This version can no longer sync. Run: npx ${BRAND}@latest autosync on` })
344
+ config.lastUpdateNotice = today
345
+ await saveConfig(config)
346
+ }
347
+ throw error
348
+ }
349
+
350
+ // Says so when a newer CLI is out (the server checks npm).
351
+ function notice(config, cli) {
352
+ const line = updateNotice(cli, { brand: BRAND, autosync: config.autosync })
353
+ if (line) console.log(`\n${line}`)
354
+ }
355
+
329
356
  const ago = iso => {
330
357
  if (!iso) return 'never'
331
358
  const minutes = Math.round((Date.now() - Date.parse(iso)) / 60000)
@@ -382,6 +409,8 @@ async function status(config) {
382
409
  row('Nudge', nudgeOn ? `on · checks at ${clock(nudgeTimeOf(config))} · last nudged ${config.lastNudge ?? 'never'}` : config.nudge === false ? 'off' : 'off (needs autosync)')
383
410
  const github = me.error ? 0 : (me.emails?.length ?? 0)
384
411
  row('Addresses', `${config.emails.join(', ') || 'none'}${github ? ` · +${github} from GitHub` : ''}`)
412
+ const update = me.error ? undefined : updateNotice(me.cli, { brand: BRAND, autosync: config.autosync })
413
+ row('Version', update ? `${VERSION} · ${me.cli.latest} is out: ${updateCommand(BRAND, config.autosync)}` : `${VERSION}${me.cli?.latest ? ' (latest)' : ''}`)
385
414
  row('Config', CONFIG_PATH)
386
415
  }
387
416
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibestreak",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Your AI coding streak: a heatmap of the days you coded with Claude Code, Codex, Cursor, Copilot, Gemini and OpenCode, and the commits you shipped with them. Sends daily totals only.",
5
5
  "keywords": ["claude-code", "codex", "cursor", "copilot", "gemini", "opencode", "ai-coding", "heatmap", "streak", "leaderboard", "cli"],
6
6
  "homepage": "https://vibestreak.kingsbridge-consultancy.com/cli",
@@ -8,7 +8,7 @@
8
8
  "author": "Kingsbridge Consultancy LLC <support@kingsbridge-consultancy.com>",
9
9
  "type": "module",
10
10
  "bin": { "vibestreak": "bin/vibestreak.mjs" },
11
- "files": ["bin", "src", "README.md", "LICENSE"],
11
+ "files": ["bin", "src", "README.md", "CHANGELOG.md", "LICENSE"],
12
12
  "engines": { "node": ">=22.13" },
13
13
  "os": ["darwin", "linux", "win32"],
14
14
  "scripts": { "test": "node --test test/*.test.mjs" }
@@ -0,0 +1,21 @@
1
+ import { readFileSync } from 'node:fs'
2
+
3
+ export const VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version
4
+
5
+ // Release versions only (x.y.z): negative when a is older than b.
6
+ export function compareVersions(a, b) {
7
+ const pa = String(a).split('.').map(Number)
8
+ const pb = String(b).split('.').map(Number)
9
+ for (let i = 0; i < 3; i++) if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0)
10
+ return 0
11
+ }
12
+
13
+ // One line when the server says a newer CLI is out; nothing otherwise. With
14
+ // autosync on, the update command also moves the background jobs to it.
15
+ export function updateNotice(cli, { brand = 'vibestreak', autosync = false, current = VERSION } = {}) {
16
+ if (!cli?.latest || compareVersions(current, cli.latest) >= 0) return undefined
17
+ return `${brand} ${cli.latest} is out (this is ${current}): ${updateCommand(brand, autosync)}.`
18
+ }
19
+
20
+ export const updateCommand = (brand, autosync) =>
21
+ autosync ? `\`npx ${brand}@latest autosync on\` updates the background sync to it` : `\`npx ${brand}@latest\` runs it`