kalvium-worklog 1.0.2 → 1.1.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.
- package/dist/catchup.d.ts +2 -0
- package/dist/catchup.js +10 -0
- package/dist/cli.js +14 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/scheduler.js +67 -10
- package/dist/submit.d.ts +6 -0
- package/dist/submit.js +90 -6
- package/package.json +1 -1
- package/templates/worklog_standalone.html +1 -1
- package/templates/worklog_template.html +1 -1
package/dist/catchup.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Catch-up entry point — runs on login/wake.
|
|
4
|
+
* Checks if today's worklog was submitted, and if not, submits it.
|
|
5
|
+
* Used by the login launchd job (RunAtLoad).
|
|
6
|
+
*/
|
|
7
|
+
import { catchUpSubmit } from "./submit.js";
|
|
8
|
+
catchUpSubmit().then((ok) => {
|
|
9
|
+
process.exit(ok ? 0 : 1);
|
|
10
|
+
});
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { captureToken } from "./capture-token.js";
|
|
4
4
|
import { discoverPosition } from "./discover-position.js";
|
|
5
|
-
import { submitWorklog, checkStatus, dailySubmit } from "./submit.js";
|
|
5
|
+
import { submitWorklog, checkStatus, dailySubmit, catchUpSubmit } from "./submit.js";
|
|
6
6
|
import { setupScheduler, removeScheduler } from "./scheduler.js";
|
|
7
7
|
import { generateWebapp } from "./generate-webapp.js";
|
|
8
8
|
import { loadToken, saveConfig, getPositionIdFromToken } from "./config.js";
|
|
@@ -41,7 +41,7 @@ program
|
|
|
41
41
|
// ─── submit ────────────────────────────────────────────────────────────────
|
|
42
42
|
program
|
|
43
43
|
.command("submit [text]")
|
|
44
|
-
.description("Submit worklog (default text: '
|
|
44
|
+
.description("Submit worklog (default text: 'Working on assigned task')")
|
|
45
45
|
.option("--status <type>", "Work status", "on_site")
|
|
46
46
|
.option("--dry-run", "Test without submitting")
|
|
47
47
|
.action(async (text, opts) => {
|
|
@@ -52,7 +52,7 @@ program
|
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
const result = await submitWorklog({
|
|
55
|
-
text: text ?? "
|
|
55
|
+
text: text ?? "Working on assigned task",
|
|
56
56
|
status: opts.status,
|
|
57
57
|
dryRun: opts.dryRun,
|
|
58
58
|
});
|
|
@@ -69,12 +69,21 @@ program
|
|
|
69
69
|
// ─── daily ─────────────────────────────────────────────────────────────────
|
|
70
70
|
program
|
|
71
71
|
.command("daily")
|
|
72
|
-
.description("Run daily auto-submit (used by scheduler — skips weekends)")
|
|
72
|
+
.description("Run daily auto-submit (used by scheduler — skips weekends, retries on network failure)")
|
|
73
73
|
.action(async () => {
|
|
74
74
|
const ok = await dailySubmit();
|
|
75
75
|
if (!ok)
|
|
76
76
|
process.exit(1);
|
|
77
77
|
});
|
|
78
|
+
// ─── catchup ───────────────────────────────────────────────────────────────
|
|
79
|
+
program
|
|
80
|
+
.command("catchup")
|
|
81
|
+
.description("Check if today's worklog is missing and submit if needed (runs on login/wake)")
|
|
82
|
+
.action(async () => {
|
|
83
|
+
const ok = await catchUpSubmit();
|
|
84
|
+
if (!ok)
|
|
85
|
+
process.exit(1);
|
|
86
|
+
});
|
|
78
87
|
// ─── schedule ──────────────────────────────────────────────────────────────
|
|
79
88
|
program
|
|
80
89
|
.command("schedule [time]")
|
|
@@ -201,7 +210,7 @@ program
|
|
|
201
210
|
await generateWebapp();
|
|
202
211
|
console.log("\n✅ Setup complete!\n");
|
|
203
212
|
console.log("Commands:");
|
|
204
|
-
console.log(" kalvium-worklog submit # submit with
|
|
213
|
+
console.log(" kalvium-worklog submit # submit with default text");
|
|
205
214
|
console.log(" kalvium-worklog submit 'my update' # custom text");
|
|
206
215
|
console.log(" kalvium-worklog status # check status");
|
|
207
216
|
console.log(" kalvium-worklog schedule --remove # stop automation");
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { captureToken } from "./capture-token.js";
|
|
2
2
|
export { discoverPosition } from "./discover-position.js";
|
|
3
|
-
export { submitWorklog, checkStatus, dailySubmit, STATUS_OPTIONS } from "./submit.js";
|
|
3
|
+
export { submitWorklog, checkStatus, dailySubmit, catchUpSubmit, STATUS_OPTIONS } from "./submit.js";
|
|
4
4
|
export type { SubmitOptions, SubmitResult } from "./submit.js";
|
|
5
5
|
export { setupScheduler, removeScheduler } from "./scheduler.js";
|
|
6
6
|
export type { ScheduleOptions } from "./scheduler.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { captureToken } from "./capture-token.js";
|
|
2
2
|
export { discoverPosition } from "./discover-position.js";
|
|
3
|
-
export { submitWorklog, checkStatus, dailySubmit, STATUS_OPTIONS } from "./submit.js";
|
|
3
|
+
export { submitWorklog, checkStatus, dailySubmit, catchUpSubmit, STATUS_OPTIONS } from "./submit.js";
|
|
4
4
|
export { setupScheduler, removeScheduler } from "./scheduler.js";
|
|
5
5
|
export { generateWebapp } from "./generate-webapp.js";
|
|
6
6
|
export { refreshToken } from "./refresh.js";
|
package/dist/scheduler.js
CHANGED
|
@@ -37,30 +37,39 @@ function formatTime(hour, minute) {
|
|
|
37
37
|
return `${hour - 12}:${minute.toString().padStart(2, "0")} PM`;
|
|
38
38
|
}
|
|
39
39
|
/**
|
|
40
|
-
* Copy the daily-submit.js
|
|
41
|
-
*
|
|
42
|
-
* This way it works even if the package is updated.
|
|
40
|
+
* Copy the daily-submit.js and catchup.js scripts to ~/.kalvium/ as wrappers
|
|
41
|
+
* that import from the npm package's dist directory (using absolute paths).
|
|
43
42
|
*/
|
|
44
43
|
function copyDailyScript() {
|
|
45
44
|
ensureInstallDir();
|
|
46
|
-
const dst = join(INSTALL_DIR, "daily_submit.js");
|
|
47
|
-
// Create a wrapper that imports from the package's dist directory
|
|
48
45
|
const distDir = __dirname;
|
|
49
|
-
|
|
46
|
+
// Daily submit wrapper
|
|
47
|
+
const dailyDst = join(INSTALL_DIR, "daily_submit.js");
|
|
48
|
+
const dailyWrapper = `#!/usr/bin/env node
|
|
50
49
|
// Auto-generated by kalvium-worklog. Do not edit.
|
|
51
50
|
import { dailySubmit } from "${join(distDir, "submit.js")}";
|
|
52
51
|
dailySubmit().then((ok) => process.exit(ok ? 0 : 1));
|
|
53
52
|
`;
|
|
54
|
-
writeFileSync(
|
|
55
|
-
|
|
53
|
+
writeFileSync(dailyDst, dailyWrapper);
|
|
54
|
+
// Catch-up wrapper (runs on login/wake)
|
|
55
|
+
const catchupDst = join(INSTALL_DIR, "catchup_submit.js");
|
|
56
|
+
const catchupWrapper = `#!/usr/bin/env node
|
|
57
|
+
// Auto-generated by kalvium-worklog. Do not edit.
|
|
58
|
+
import { catchUpSubmit } from "${join(distDir, "submit.js")}";
|
|
59
|
+
catchUpSubmit().then((ok) => process.exit(ok ? 0 : 1));
|
|
60
|
+
`;
|
|
61
|
+
writeFileSync(catchupDst, catchupWrapper);
|
|
62
|
+
return dailyDst;
|
|
56
63
|
}
|
|
57
64
|
function setupLaunchd(hour, minute) {
|
|
58
65
|
const launchAgentsDir = join(homedir(), "Library", "LaunchAgents");
|
|
59
66
|
if (!existsSync(launchAgentsDir)) {
|
|
60
67
|
mkdirSync(launchAgentsDir, { recursive: true });
|
|
61
68
|
}
|
|
62
|
-
const plistPath = join(launchAgentsDir, "com.kalvium.worklog.plist");
|
|
63
69
|
const dailyScript = copyDailyScript();
|
|
70
|
+
const catchupScript = join(INSTALL_DIR, "catchup_submit.js");
|
|
71
|
+
// ─── 1. Scheduled daily plist (fires at chosen time) ────────────────────
|
|
72
|
+
const plistPath = join(launchAgentsDir, "com.kalvium.worklog.plist");
|
|
64
73
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
65
74
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
66
75
|
<plist version="1.0">
|
|
@@ -103,7 +112,43 @@ function setupLaunchd(hour, minute) {
|
|
|
103
112
|
writeFileSync(plistPath, plist);
|
|
104
113
|
execSync(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
|
|
105
114
|
execSync(`launchctl load "${plistPath}"`, { stdio: "ignore" });
|
|
115
|
+
// ─── 2. Catch-up plist (fires on login/wake — RunAtLoad) ────────────────
|
|
116
|
+
// This checks if today's worklog was already submitted. If not, submits it.
|
|
117
|
+
// Handles the case where the scheduled time was missed (laptop asleep/offline).
|
|
118
|
+
const catchupPlistPath = join(launchAgentsDir, "com.kalvium.worklog.catchup.plist");
|
|
119
|
+
const catchupPlist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
120
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
121
|
+
<plist version="1.0">
|
|
122
|
+
<dict>
|
|
123
|
+
<key>Label</key>
|
|
124
|
+
<string>com.kalvium.worklog.catchup</string>
|
|
125
|
+
<key>ProgramArguments</key>
|
|
126
|
+
<array>
|
|
127
|
+
<string>${process.execPath}</string>
|
|
128
|
+
<string>${catchupScript}</string>
|
|
129
|
+
</array>
|
|
130
|
+
<key>RunAtLoad</key>
|
|
131
|
+
<true/>
|
|
132
|
+
<key>StartInterval</key>
|
|
133
|
+
<integer>1800</integer>
|
|
134
|
+
<key>StandardOutPath</key>
|
|
135
|
+
<string>${LOG_FILE}</string>
|
|
136
|
+
<key>StandardErrorPath</key>
|
|
137
|
+
<string>${LOG_FILE}</string>
|
|
138
|
+
<key>EnvironmentVariables</key>
|
|
139
|
+
<dict>
|
|
140
|
+
<key>HOME</key>
|
|
141
|
+
<string>${homedir()}</string>
|
|
142
|
+
<key>PATH</key>
|
|
143
|
+
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/homebrew/bin</string>
|
|
144
|
+
</dict>
|
|
145
|
+
</dict>
|
|
146
|
+
</plist>`;
|
|
147
|
+
writeFileSync(catchupPlistPath, catchupPlist);
|
|
148
|
+
execSync(`launchctl unload "${catchupPlistPath}"`, { stdio: "ignore" });
|
|
149
|
+
execSync(`launchctl load "${catchupPlistPath}"`, { stdio: "ignore" });
|
|
106
150
|
console.log(`Daily auto-submit installed (${formatTime(hour, minute)} every weekday)`);
|
|
151
|
+
console.log(" + Catch-up check on login/wake (every 30 min)");
|
|
107
152
|
return true;
|
|
108
153
|
}
|
|
109
154
|
function setupTaskScheduler(hour, minute) {
|
|
@@ -159,6 +204,7 @@ function setupCron(hour, minute) {
|
|
|
159
204
|
*/
|
|
160
205
|
export async function removeScheduler() {
|
|
161
206
|
if (IS_MAC) {
|
|
207
|
+
// Remove daily plist
|
|
162
208
|
const plistPath = join(homedir(), "Library/LaunchAgents/com.kalvium.worklog.plist");
|
|
163
209
|
try {
|
|
164
210
|
execSync(`launchctl unload "${plistPath}"`, { stdio: "ignore" });
|
|
@@ -169,7 +215,18 @@ export async function removeScheduler() {
|
|
|
169
215
|
if (existsSync(plistPath)) {
|
|
170
216
|
execSync(`rm "${plistPath}"`);
|
|
171
217
|
}
|
|
172
|
-
|
|
218
|
+
// Remove catch-up plist
|
|
219
|
+
const catchupPlistPath = join(homedir(), "Library/LaunchAgents/com.kalvium.worklog.catchup.plist");
|
|
220
|
+
try {
|
|
221
|
+
execSync(`launchctl unload "${catchupPlistPath}"`, { stdio: "ignore" });
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// ignore
|
|
225
|
+
}
|
|
226
|
+
if (existsSync(catchupPlistPath)) {
|
|
227
|
+
execSync(`rm "${catchupPlistPath}"`);
|
|
228
|
+
}
|
|
229
|
+
console.log("Daily auto-submit removed (including catch-up).");
|
|
173
230
|
return true;
|
|
174
231
|
}
|
|
175
232
|
else if (IS_WINDOWS) {
|
package/dist/submit.d.ts
CHANGED
|
@@ -22,5 +22,11 @@ export declare function checkStatus(): Promise<void>;
|
|
|
22
22
|
/**
|
|
23
23
|
* Daily auto-submit (used by scheduler).
|
|
24
24
|
* Skips weekends. Logs to file.
|
|
25
|
+
* Retries on network failure (up to 5 times with 60s delay).
|
|
25
26
|
*/
|
|
26
27
|
export declare function dailySubmit(): Promise<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Catch-up check: if it's a weekday and today's worklog hasn't been
|
|
30
|
+
* submitted yet, submit it. Used on login/wake to handle missed runs.
|
|
31
|
+
*/
|
|
32
|
+
export declare function catchUpSubmit(): Promise<boolean>;
|
package/dist/submit.js
CHANGED
|
@@ -14,7 +14,7 @@ export const STATUS_OPTIONS = {
|
|
|
14
14
|
* for re-login and retries the submission.
|
|
15
15
|
*/
|
|
16
16
|
export async function submitWorklog(options = {}) {
|
|
17
|
-
const text = options.text ?? "
|
|
17
|
+
const text = options.text ?? "Working on assigned task";
|
|
18
18
|
const statusKey = options.status ?? "on_site";
|
|
19
19
|
const dryRun = options.dryRun ?? false;
|
|
20
20
|
const worklogApi = getWorklogApi();
|
|
@@ -177,6 +177,7 @@ export async function checkStatus() {
|
|
|
177
177
|
/**
|
|
178
178
|
* Daily auto-submit (used by scheduler).
|
|
179
179
|
* Skips weekends. Logs to file.
|
|
180
|
+
* Retries on network failure (up to 5 times with 60s delay).
|
|
180
181
|
*/
|
|
181
182
|
export async function dailySubmit() {
|
|
182
183
|
const day = new Date().getDay();
|
|
@@ -185,12 +186,95 @@ export async function dailySubmit() {
|
|
|
185
186
|
return true;
|
|
186
187
|
}
|
|
187
188
|
log("[INFO] Starting daily worklog submission...");
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
189
|
+
// Retry up to 5 times in case of network failure
|
|
190
|
+
// (covers: laptop waking up, internet reconnecting, etc.)
|
|
191
|
+
const MAX_RETRIES = 5;
|
|
192
|
+
const RETRY_DELAY_MS = 60000; // 1 minute between retries
|
|
193
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
194
|
+
if (attempt > 1) {
|
|
195
|
+
log(`[INFO] Retry ${attempt}/${MAX_RETRIES} (waiting for network...)`);
|
|
196
|
+
}
|
|
197
|
+
const result = await submitWorklog({
|
|
198
|
+
text: "Working on assigned task",
|
|
199
|
+
status: "on_site",
|
|
200
|
+
});
|
|
201
|
+
if (result.success) {
|
|
202
|
+
log(`[INFO] SUCCESS: ${result.message}`);
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
// Check if it's a network error (retry) vs a real error (give up)
|
|
206
|
+
const isNetworkError = result.message.includes("fetch") ||
|
|
207
|
+
result.message.includes("ECONNREFUSED") ||
|
|
208
|
+
result.message.includes("ENOTFOUND") ||
|
|
209
|
+
result.message.includes("ETIMEDOUT") ||
|
|
210
|
+
result.message.includes("network") ||
|
|
211
|
+
result.message.includes("Failed to fetch") ||
|
|
212
|
+
result.message.includes("refresh failed");
|
|
213
|
+
// "Already submitted" is a success, not an error
|
|
214
|
+
if (result.message.includes("Already submitted")) {
|
|
215
|
+
log(`[INFO] ${result.message}`);
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
if (isNetworkError && attempt < MAX_RETRIES) {
|
|
219
|
+
log(`[WARN] Network error: ${result.message}. Retrying in 60s...`);
|
|
220
|
+
await sleep(RETRY_DELAY_MS);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
// Non-network error or max retries reached
|
|
224
|
+
log(`[ERROR] ${result.message}`);
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
function sleep(ms) {
|
|
230
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Catch-up check: if it's a weekday and today's worklog hasn't been
|
|
234
|
+
* submitted yet, submit it. Used on login/wake to handle missed runs.
|
|
235
|
+
*/
|
|
236
|
+
export async function catchUpSubmit() {
|
|
237
|
+
const day = new Date().getDay();
|
|
238
|
+
if (day === 0 || day === 6) {
|
|
239
|
+
return true; // weekend, nothing to do
|
|
240
|
+
}
|
|
241
|
+
log("[INFO] Catch-up check: verifying today's worklog...");
|
|
242
|
+
// Check if today's worklog was already submitted
|
|
243
|
+
const worklogApi = getWorklogApi();
|
|
244
|
+
if (!worklogApi) {
|
|
245
|
+
log("[ERROR] No config found. Run `kalvium-worklog discover` first.");
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
// Try to refresh token and check status
|
|
249
|
+
const refreshResult = await refreshToken();
|
|
250
|
+
if (!refreshResult.success || !refreshResult.accessToken) {
|
|
251
|
+
log("[WARN] Could not refresh token for catch-up check. Will try submit directly.");
|
|
252
|
+
// Fall through to submit attempt — it has its own retry/relogin logic
|
|
191
253
|
}
|
|
192
254
|
else {
|
|
193
|
-
|
|
255
|
+
try {
|
|
256
|
+
const res = await fetch(worklogApi, {
|
|
257
|
+
headers: { authorization: `Bearer ${refreshResult.accessToken}` },
|
|
258
|
+
});
|
|
259
|
+
if (res.ok) {
|
|
260
|
+
const worklogs = await res.json();
|
|
261
|
+
if (Array.isArray(worklogs)) {
|
|
262
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
263
|
+
const todayEntry = worklogs.find((w) => (w.worklogDate ?? "").slice(0, 10) === today &&
|
|
264
|
+
w.status === "complete");
|
|
265
|
+
if (todayEntry) {
|
|
266
|
+
log("[INFO] Today's worklog already submitted. No catch-up needed.");
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// Network error — fall through to submit attempt
|
|
274
|
+
log("[WARN] Could not check status for catch-up. Will try submit directly.");
|
|
275
|
+
}
|
|
194
276
|
}
|
|
195
|
-
|
|
277
|
+
// Today's worklog is missing — submit it
|
|
278
|
+
log("[INFO] Today's worklog is missing. Running catch-up submit...");
|
|
279
|
+
return dailySubmit();
|
|
196
280
|
}
|
package/package.json
CHANGED
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
</div>
|
|
81
81
|
</div>
|
|
82
82
|
|
|
83
|
-
<textarea id="text" placeholder="What did you worked on today?">
|
|
83
|
+
<textarea id="text" placeholder="What did you worked on today?">Working on assigned task</textarea>
|
|
84
84
|
|
|
85
85
|
<button class="btn btn-submit" id="submitBtn" onclick="submitWorklog()">Submit Worklog</button>
|
|
86
86
|
<button class="btn btn-status" onclick="checkStatus()">Check Status</button>
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
78
78
|
|
|
79
|
-
<textarea id="text" placeholder="What did you work on today?">
|
|
79
|
+
<textarea id="text" placeholder="What did you work on today?">Working on assigned task</textarea>
|
|
80
80
|
|
|
81
81
|
<button class="btn btn-submit" id="submitBtn" onclick="submitWorklog()">Submit Worklog</button>
|
|
82
82
|
<button class="btn btn-status" onclick="checkStatus()">Check Status</button>
|