clocktopus 1.4.0 → 1.5.1

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
@@ -72,7 +72,13 @@ After installing, remove the quarantine flag (app is not code-signed):
72
72
  xattr -cr /Applications/Clocktopus.app
73
73
  ```
74
74
 
75
- The dashboard server must be running (`clocktopus serve`). See [desktop/README.md](desktop/README.md) for details.
75
+ The app manages the dashboard server for you:
76
+
77
+ - **Install Clocktopus** — if the CLI is not installed, the popup offers a one-click installer that runs `bun install -g clocktopus` for you.
78
+ - **Start Server** — when the dashboard is not running, the popup shows a "Start Server" button. Click it and the app spawns `clocktopus dash` in the background, then loads the dashboard once it's up.
79
+ - **Stop Server** / **Restart Server** — available from the tray menu when the server is reachable. Stop also kills any pre-existing process on port 4001 (terminal, PM2, prior session).
80
+
81
+ See [desktop/README.md](desktop/README.md) for details.
76
82
 
77
83
  ---
78
84
 
Binary file
package/dist/clockify.js CHANGED
@@ -1,20 +1,39 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { fileURLToPath } from 'url';
1
4
  import { HttpClient } from './lib/http-client.js';
2
5
  import { logSessionStart } from './lib/db.js';
3
6
  import { v4 as uuidv4 } from 'uuid';
4
7
  import { NotificationCenter } from 'node-notifier';
5
8
  import { getJiraTicket } from './lib/jira.js';
9
+ /**
10
+ * Resolve `assets/logo.png` from the package root regardless of install layout.
11
+ * Walks up from this file until the assets dir is found.
12
+ */
13
+ function resolveLogoPath() {
14
+ const here = path.dirname(fileURLToPath(import.meta.url));
15
+ for (let dir = here, prev = ''; dir !== prev; prev = dir, dir = path.dirname(dir)) {
16
+ const candidate = path.join(dir, 'assets', 'logo.png');
17
+ if (fs.existsSync(candidate))
18
+ return candidate;
19
+ }
20
+ return undefined;
21
+ }
22
+ const LOGO_PATH = resolveLogoPath();
6
23
  export class Clockify {
7
24
  constructor() {
8
25
  this.httpClient = new HttpClient().getClient();
9
26
  this.notifier = new NotificationCenter();
10
27
  }
11
- sendNotification(title, message, actions, callback) {
28
+ sendNotification(subtitle, message, actions, callback) {
12
29
  this.notifier.notify({
13
- title,
30
+ title: 'Clocktopus',
31
+ subtitle,
14
32
  message,
15
33
  sound: true,
16
34
  wait: true,
17
35
  actions,
36
+ contentImage: LOGO_PATH,
18
37
  }, callback ??
19
38
  ((err) => {
20
39
  if (err)
@@ -3,6 +3,8 @@ import { google } from 'googleapis';
3
3
  import { getAuthenticatedClient, getRefreshedToken } from '../../lib/google.js';
4
4
  import { getLatestToken, storeToken, getEventProject, setEventProject, getActiveProjects } from '../../lib/db.js';
5
5
  import { Clockify } from '../../clockify.js';
6
+ // Hardcoded — registered with Google OAuth; cannot vary with CLOCKTOPUS_PORT
7
+ // without re-registering the redirect URI in the Google Cloud console.
6
8
  const DASHBOARD_REDIRECT_URI = 'http://localhost:4001/api/google/callback';
7
9
  const calendarRoutes = new Hono();
8
10
  calendarRoutes.get('/calendar/events', async (c) => {
@@ -3,6 +3,8 @@ import { google } from 'googleapis';
3
3
  import { getAuthenticatedClient, getAuthUrl, exchangeGoogleCode } from '../../lib/google.js';
4
4
  import { storeToken } from '../../lib/db.js';
5
5
  import { saveCredential } from '../../lib/credentials.js';
6
+ // Hardcoded — registered with Google OAuth; cannot vary with CLOCKTOPUS_PORT
7
+ // without re-registering the redirect URI in the Google Cloud console.
6
8
  const DASHBOARD_REDIRECT_URI = 'http://localhost:4001/api/google/callback';
7
9
  const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/userinfo.email'];
8
10
  const googleRoutes = new Hono();
@@ -2,6 +2,7 @@ import { Hono } from 'hono';
2
2
  import { cors } from 'hono/cors';
3
3
  import { serve } from '@hono/node-server';
4
4
  import { indexPage } from './views.js';
5
+ import { DASHBOARD_PORT } from '../lib/constants.js';
5
6
  import statusRoutes from './routes/status.js';
6
7
  import clockifyRoutes from './routes/clockify.js';
7
8
  import jiraRoutes from './routes/jira.js';
@@ -22,7 +23,6 @@ app.route('/api', dataRoutes);
22
23
  app.route('/api', monitorRoutes);
23
24
  app.route('/api', calendarRoutes);
24
25
  export function startDashboard() {
25
- const port = 4001;
26
- console.log(`Clocktopus dashboard running at http://localhost:${port}`);
27
- serve({ fetch: app.fetch, port });
26
+ console.log(`Clocktopus dashboard running at http://localhost:${DASHBOARD_PORT}`);
27
+ serve({ fetch: app.fetch, port: DASHBOARD_PORT });
28
28
  }
@@ -5,8 +5,14 @@ export function indexPage() {
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>Clocktopus Dashboard</title>
8
+ <script>
9
+ if (!window.__TAURI_INTERNALS__ && !window.__TAURI__) {
10
+ document.documentElement.classList.add('browser');
11
+ }
12
+ </script>
8
13
  <style>
9
14
  * { box-sizing: border-box; margin: 0; padding: 0; }
15
+ html.browser { background: #0d1117; }
10
16
  body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: transparent; color: #e1e4e8; padding: 2rem; }
11
17
  h1 { font-size: 1.8rem; margin-bottom: 0; color: #fff; }
12
18
  h2 { font-size: 1.1rem; color: #fff; margin-bottom: 1rem; }
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ import { completeLatestSession, getLatestSession } from './lib/db.js';
11
11
  import { stopJiraTimer } from './lib/jira.js';
12
12
  import { startDashboard } from './dashboard/server.js';
13
13
  import { ensureNativeAddons } from './lib/ensure-native-addons.js';
14
+ import { DASHBOARD_PORT, DASHBOARD_URL } from './lib/constants.js';
14
15
  const __filename = fileURLToPath(import.meta.url);
15
16
  const __dirname = path.dirname(__filename);
16
17
  const program = new Command();
@@ -281,7 +282,7 @@ program
281
282
  });
282
283
  program
283
284
  .command('dash')
284
- .description('Start the Clocktopus web dashboard on localhost:4001.')
285
+ .description(`Start the Clocktopus web dashboard on localhost:${DASHBOARD_PORT}.`)
285
286
  .action(() => {
286
287
  startDashboard();
287
288
  });
@@ -352,7 +353,7 @@ program
352
353
  execSync(`${pm2Bin} start ${scriptPath} --name ${DASH_PM2_NAME} --interpreter ${bunPath} -- dash`, {
353
354
  stdio: 'inherit',
354
355
  });
355
- console.log(chalk.green('Dashboard running at http://localhost:4001'));
356
+ console.log(chalk.green(`Dashboard running at ${DASHBOARD_URL}`));
356
357
  console.log(chalk.gray(' Stop: clocktopus serve:stop'));
357
358
  console.log(chalk.gray(' Logs: clocktopus serve:logs'));
358
359
  }
@@ -6,6 +6,8 @@ const AUTH_PROXY_URL = 'https://clocktopus-auth.clocktopus.workers.dev';
6
6
  // Fallback: direct Atlassian API (when user provides their own credentials)
7
7
  const ATLASSIAN_TOKEN_URL = 'https://auth.atlassian.com/oauth/token';
8
8
  const ATLASSIAN_RESOURCES_URL = 'https://api.atlassian.com/oauth/token/accessible-resources';
9
+ // Hardcoded — registered with Atlassian; cannot vary with CLOCKTOPUS_PORT
10
+ // without re-registering the redirect URI on the OAuth app.
9
11
  const REDIRECT_URI = 'http://localhost:4001/api/jira/callback';
10
12
  function hasLocalCredentials() {
11
13
  return !!(resolveCredential('ATLASSIAN_CLIENT_ID') && resolveCredential('ATLASSIAN_CLIENT_SECRET'));
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Dashboard HTTP port. Override via CLOCKTOPUS_PORT env var if 4001 is busy.
3
+ *
4
+ * NOTE: OAuth redirect URIs (Atlassian, Google) are registered with the
5
+ * provider at port 4001. Changing this port breaks OAuth unless the redirect
6
+ * URI is re-registered with the provider as well.
7
+ */
8
+ export const DASHBOARD_PORT = (() => {
9
+ const raw = process.env.CLOCKTOPUS_PORT;
10
+ if (!raw)
11
+ return 4001;
12
+ const n = parseInt(raw, 10);
13
+ return Number.isFinite(n) && n > 0 && n < 65536 ? n : 4001;
14
+ })();
15
+ export const DASHBOARD_URL = `http://localhost:${DASHBOARD_PORT}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clocktopus",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -11,6 +11,7 @@
11
11
  "dist",
12
12
  "data/.gitkeep",
13
13
  "scripts/postinstall.cjs",
14
+ "assets/logo.png",
14
15
  "!dist/desktop"
15
16
  ],
16
17
  "scripts": {