clocktopus 1.10.2 → 1.10.4
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/dist/dashboard/routes/monitor.js +2 -2
- package/dist/dashboard/views.js +2 -3
- package/dist/index.js +57 -33
- package/dist/lib/constants.js +22 -0
- package/package.json +1 -1
|
@@ -3,11 +3,11 @@ import { execSync } from 'child_process';
|
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { createRequire } from 'module';
|
|
6
|
+
import { IS_DEV } from '../../lib/constants.js';
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = path.dirname(__filename);
|
|
8
9
|
const SCRIPT_PATH = path.resolve(__dirname, '../../index.js');
|
|
9
|
-
const
|
|
10
|
-
const PM2_NAME = isDev ? 'clocktopus-monitor-dev' : 'clocktopus-monitor';
|
|
10
|
+
const PM2_NAME = IS_DEV ? 'clocktopus-monitor-dev' : 'clocktopus-monitor';
|
|
11
11
|
const pm2Bin = path.join(path.dirname(createRequire(import.meta.url).resolve('pm2')), 'bin', 'pm2');
|
|
12
12
|
const bunBin = (() => {
|
|
13
13
|
try {
|
package/dist/dashboard/views.js
CHANGED
|
@@ -13,9 +13,8 @@ export function indexPage() {
|
|
|
13
13
|
<style>
|
|
14
14
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
15
15
|
html.browser { background: #0d1117; }
|
|
16
|
-
html
|
|
17
|
-
|
|
18
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: transparent; color: #e1e4e8; padding: 2rem; }
|
|
16
|
+
html { border-radius: 12px; overflow: hidden; height: 100vh; background: transparent; }
|
|
17
|
+
body { border-radius: 12px; height: 100vh; overflow-y: auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: transparent; color: #e1e4e8; padding: 2rem; }
|
|
19
18
|
h1 { font-size: 1.8rem; margin-bottom: 0; color: #fff; }
|
|
20
19
|
h2 { font-size: 1.1rem; color: #fff; margin-bottom: 1rem; }
|
|
21
20
|
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { execSync } from 'child_process';
|
|
|
9
9
|
import { closeStaleOpenSessions, completeLatestSession, getLatestSession, setSessionJiraWorklogId } from './lib/db.js';
|
|
10
10
|
import { isClockifyEnabled } from './lib/credentials.js';
|
|
11
11
|
import { ensureNativeAddons } from './lib/ensure-native-addons.js';
|
|
12
|
-
import { DASHBOARD_PORT, DASHBOARD_URL } from './lib/constants.js';
|
|
12
|
+
import { DASHBOARD_PORT, DASHBOARD_URL, IS_DEV } from './lib/constants.js';
|
|
13
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
14
|
const __dirname = path.dirname(__filename);
|
|
15
15
|
const program = new Command();
|
|
@@ -268,44 +268,70 @@ program
|
|
|
268
268
|
}
|
|
269
269
|
// Safer restart w/ cooldown; only resume a recent auto-completed session
|
|
270
270
|
let lastResumeAt = 0;
|
|
271
|
+
let resuming = false;
|
|
271
272
|
const RESUME_COOLDOWN_MS = 10000;
|
|
273
|
+
// Shared between lock-state and idle-time watchers so a resume triggered
|
|
274
|
+
// by one path disarms the other (otherwise both fire and we get two starts).
|
|
275
|
+
let isLocked = false;
|
|
276
|
+
let lastIdle = false;
|
|
272
277
|
async function safeRestartTimerIfNeeded() {
|
|
273
278
|
const now = Date.now();
|
|
274
|
-
if (now - lastResumeAt < RESUME_COOLDOWN_MS)
|
|
275
|
-
return;
|
|
276
|
-
// Small delay lets services settle after wake/activity
|
|
277
|
-
await sleep(800);
|
|
278
|
-
const latestSession = getLatestSession();
|
|
279
|
-
if (!latestSession)
|
|
280
|
-
return;
|
|
281
|
-
const twoHoursAgo = Date.now() - 2 * 60 * 60 * 1000;
|
|
282
|
-
const completedMs = latestSession.completedAt ? new Date(latestSession.completedAt).getTime() : 0;
|
|
283
|
-
if (!latestSession.isAutoCompleted || completedMs <= twoHoursAgo)
|
|
279
|
+
if (resuming || now - lastResumeAt < RESUME_COOLDOWN_MS)
|
|
284
280
|
return;
|
|
285
|
-
|
|
286
|
-
|
|
281
|
+
resuming = true;
|
|
282
|
+
// Gate concurrent callers immediately; refine on success/failure below.
|
|
283
|
+
lastResumeAt = now;
|
|
284
|
+
try {
|
|
285
|
+
// Small delay lets services settle after wake/activity
|
|
286
|
+
await sleep(800);
|
|
287
|
+
const latestSession = getLatestSession();
|
|
288
|
+
if (!latestSession)
|
|
287
289
|
return;
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
+
const twoHoursAgo = Date.now() - 2 * 60 * 60 * 1000;
|
|
291
|
+
const completedMs = latestSession.completedAt ? new Date(latestSession.completedAt).getTime() : 0;
|
|
292
|
+
if (!latestSession.isAutoCompleted || completedMs <= twoHoursAgo)
|
|
293
|
+
return;
|
|
294
|
+
if (isClockifyEnabled()) {
|
|
295
|
+
if (!latestSession.projectId)
|
|
296
|
+
return;
|
|
297
|
+
const activeEntry = await (await clockify()).getActiveTimer(workspaceId, userId);
|
|
298
|
+
if (activeEntry) {
|
|
299
|
+
isLocked = false;
|
|
300
|
+
lastIdle = false;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
await (await clockify()).startTimer(workspaceId, latestSession.projectId, latestSession.description, latestSession.jiraTicket ?? undefined);
|
|
304
|
+
console.log(chalk.green('Timer restarted for the last used project.'));
|
|
305
|
+
lastResumeAt = Date.now();
|
|
306
|
+
isLocked = false;
|
|
307
|
+
lastIdle = false;
|
|
290
308
|
return;
|
|
291
|
-
|
|
292
|
-
|
|
309
|
+
}
|
|
310
|
+
// Jira-only resume: new DB session with a fresh uuid, same ticket.
|
|
311
|
+
// Re-read latest to guard against a concurrent insert from another path.
|
|
312
|
+
if (!latestSession.jiraTicket)
|
|
313
|
+
return;
|
|
314
|
+
const fresh = getLatestSession();
|
|
315
|
+
if (fresh && !fresh.completedAt) {
|
|
316
|
+
isLocked = false;
|
|
317
|
+
lastIdle = false;
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const { v4: uuidv4 } = await import('uuid');
|
|
321
|
+
const { logSessionStart } = await import('./lib/db.js');
|
|
322
|
+
const sessionId = uuidv4();
|
|
323
|
+
const startedAt = new Date().toISOString();
|
|
324
|
+
logSessionStart(sessionId, latestSession.projectId ?? null, latestSession.description, startedAt, latestSession.jiraTicket);
|
|
325
|
+
console.log(chalk.green(`Resumed Jira timer for ${latestSession.jiraTicket}.`));
|
|
293
326
|
lastResumeAt = Date.now();
|
|
294
|
-
|
|
327
|
+
isLocked = false;
|
|
328
|
+
lastIdle = false;
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
resuming = false;
|
|
295
332
|
}
|
|
296
|
-
// Jira-only resume: new DB session with a fresh uuid, same ticket
|
|
297
|
-
if (!latestSession.jiraTicket)
|
|
298
|
-
return;
|
|
299
|
-
const { v4: uuidv4 } = await import('uuid');
|
|
300
|
-
const { logSessionStart } = await import('./lib/db.js');
|
|
301
|
-
const sessionId = uuidv4();
|
|
302
|
-
const startedAt = new Date().toISOString();
|
|
303
|
-
logSessionStart(sessionId, latestSession.projectId ?? null, latestSession.description, startedAt, latestSession.jiraTicket);
|
|
304
|
-
console.log(chalk.green(`Resumed Jira timer for ${latestSession.jiraTicket}.`));
|
|
305
|
-
lastResumeAt = Date.now();
|
|
306
333
|
}
|
|
307
334
|
console.log(chalk.blue('Monitoring display events (Unified Log) and idle time...'));
|
|
308
|
-
let isLocked = false;
|
|
309
335
|
let pollInterval = null;
|
|
310
336
|
console.log(chalk.blue('Monitoring display/lock state (macos-notification-state) and idle time...'));
|
|
311
337
|
try {
|
|
@@ -351,7 +377,6 @@ program
|
|
|
351
377
|
console.error(err);
|
|
352
378
|
}
|
|
353
379
|
const IDLE_THRESHOLD_SECONDS = 300; // 5 minutes
|
|
354
|
-
let lastIdle = false;
|
|
355
380
|
const idleInterval = setInterval(async () => {
|
|
356
381
|
try {
|
|
357
382
|
const idleModule = await import('desktop-idle');
|
|
@@ -398,9 +423,8 @@ program
|
|
|
398
423
|
const { startDashboard } = await import('./dashboard/server.js');
|
|
399
424
|
startDashboard();
|
|
400
425
|
});
|
|
401
|
-
const
|
|
402
|
-
const
|
|
403
|
-
const DASH_PM2_NAME = isDev ? 'clocktopus-dash-dev' : 'clocktopus-dash';
|
|
426
|
+
const MONITOR_PM2_NAME = IS_DEV ? 'clocktopus-monitor-dev' : 'clocktopus-monitor';
|
|
427
|
+
const DASH_PM2_NAME = IS_DEV ? 'clocktopus-dash-dev' : 'clocktopus-dash';
|
|
404
428
|
const pm2Bin = path.join(path.dirname(createRequire(import.meta.url).resolve('pm2')), 'bin', 'pm2');
|
|
405
429
|
const bunBin = (() => {
|
|
406
430
|
try {
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
/**
|
|
3
|
+
* True only when running from the clocktopus source repo (e.g. `bun run`
|
|
4
|
+
* during local development). Bun-linked global installs live under
|
|
5
|
+
* `node_modules/` and must NOT be flagged as dev — otherwise PM2 names and
|
|
6
|
+
* data paths pick up the dev variants. Set CLOCKTOPUS_DEV=1 to force on.
|
|
7
|
+
*/
|
|
8
|
+
export const IS_DEV = (() => {
|
|
9
|
+
const override = process.env.CLOCKTOPUS_DEV;
|
|
10
|
+
if (override != null && override !== '') {
|
|
11
|
+
return override === '1' || override.toLowerCase() === 'true';
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
|
15
|
+
if (scriptDir.includes('/node_modules/'))
|
|
16
|
+
return false;
|
|
17
|
+
return scriptDir.includes('/Projects/') || scriptDir.includes('/src/');
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
})();
|
|
1
23
|
/**
|
|
2
24
|
* Dashboard HTTP port. Override via CLOCKTOPUS_PORT env var if 4001 is busy.
|
|
3
25
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clocktopus",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.4",
|
|
4
4
|
"description": "Time-tracking automation for Clockify with idle monitoring, Jira integration, Google Calendar sync, CLI, web dashboard, and desktop app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|