logwork-cli 1.1.0 → 1.2.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/README.md +35 -9
- package/RELEASE.md +1 -0
- package/commands/commits.mjs +57 -6
- package/commands/history.mjs +0 -4
- package/commands/quick.mjs +38 -23
- package/commands/reminder.mjs +41 -2
- package/commands/setup.mjs +157 -19
- package/commands/tui.mjs +1 -0
- package/docs/advanced.md +12 -2
- package/docs/mcp-setup.md +57 -15
- package/docs/operations-guide.md +37 -2
- package/docs/recipes.md +27 -0
- package/docs/security.md +3 -1
- package/examples/mcp/antigravity.mcp_config.json +1 -1
- package/examples/mcp/codex.config.toml +1 -1
- package/examples/mcp/copilot.mcp.json +1 -1
- package/lib/api-auth-forms.mjs +393 -0
- package/lib/api-auth-http.mjs +184 -0
- package/lib/api-auth-support.mjs +101 -0
- package/lib/api-auth-tokens.mjs +129 -0
- package/lib/api-auth.mjs +39 -813
- package/lib/api.mjs +38 -1422
- package/lib/apply-ledger.mjs +1 -1
- package/lib/batch-parser.mjs +23 -0
- package/lib/batch-workflow.mjs +201 -2
- package/lib/commit-workflow.mjs +278 -30
- package/lib/config.mjs +7 -0
- package/lib/diagnostics.mjs +53 -4
- package/lib/jira-api.mjs +25 -3
- package/lib/jira-workflow.mjs +36 -6
- package/lib/keychain.mjs +19 -12
- package/lib/logwork-config.mjs +64 -13
- package/lib/manual-apply-state.mjs +15 -0
- package/lib/manual-commands.mjs +2 -2
- package/lib/manual-ink-app.mjs +108 -1281
- package/lib/manual-ink-draft-actions.mjs +227 -0
- package/lib/manual-ink-interaction.mjs +306 -0
- package/lib/manual-ink-session.mjs +41 -0
- package/lib/manual-ink-task-editor.mjs +338 -0
- package/lib/manual-ink-wizard.mjs +520 -0
- package/lib/manual-mcp.mjs +27 -15
- package/lib/manual-repl.mjs +39 -3
- package/lib/mcp-server.mjs +63 -21
- package/lib/node-runtime.mjs +115 -0
- package/lib/npm-command.mjs +29 -0
- package/lib/paths.mjs +11 -1
- package/lib/project-mapping-workflow.mjs +30 -7
- package/lib/query-workflow.mjs +0 -1
- package/lib/reconciliation-workflow.mjs +1 -1
- package/lib/release-doctor.mjs +4 -2
- package/lib/reminder-config.mjs +38 -2
- package/lib/reminder-scheduler.mjs +54 -3
- package/lib/reminder-service.mjs +28 -3
- package/lib/reminder-workflow.mjs +118 -22
- package/lib/ro-api-diagnostics.mjs +109 -0
- package/lib/ro-api-entries.mjs +359 -0
- package/lib/ro-api-fields.mjs +290 -0
- package/lib/ro-api-http.mjs +68 -0
- package/lib/ro-api-projects.mjs +137 -0
- package/lib/ro-api-tasks.mjs +50 -0
- package/lib/ro-api-timesheet.mjs +413 -0
- package/lib/ro-delete-workflow.mjs +5 -8
- package/lib/ro-edit-workflow.mjs +66 -8
- package/lib/ro-entry-reader.mjs +49 -6
- package/lib/ro-resubmit-workflow.mjs +35 -21
- package/lib/terminal-auth-prompts.mjs +7 -4
- package/lib/ui.mjs +80 -59
- package/lib/update-service.mjs +5 -2
- package/package.json +11 -4
- package/skills/logwork/SKILL.md +6 -4
package/README.md
CHANGED
|
@@ -30,16 +30,26 @@ logwork setup
|
|
|
30
30
|
|
|
31
31
|
## Upgrading from logwork-helper
|
|
32
32
|
|
|
33
|
-
The package was renamed from `logwork-helper` to `logwork-cli`, and the `logwork-helper` command no longer exists.
|
|
33
|
+
The package was renamed from `logwork-helper` to `logwork-cli`, and the `logwork-helper` command no longer exists. Both packages install a `logwork` command, so remove the old one first (otherwise npm stops with `EEXIST`):
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
+
npm uninstall -g logwork-helper
|
|
36
37
|
npm install -g logwork-cli
|
|
37
38
|
logwork setup
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
Most of the move is automatic:
|
|
41
42
|
|
|
42
|
-
- **Old global package:** `logwork setup` removes the global `logwork-helper` package
|
|
43
|
+
- **Old global package:** if it is still installed, `logwork setup` removes the global `logwork-helper` package before linking. You can also run `npx logwork-cli@latest setup` without uninstalling first.
|
|
44
|
+
- **`EEXIST ... bin/logwork` even after uninstalling:** the old package was linked from `~/.logwork-helper`. If that folder was deleted first, `npm uninstall` cannot remove its links. Remove the broken links, then install again:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
P="$(npm prefix -g)"
|
|
48
|
+
rm -f "$P/bin/logwork" "$P"/bin/logwork-helper* "$P/lib/node_modules/logwork-helper"
|
|
49
|
+
npm install -g logwork-cli && logwork setup
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`npx logwork-cli@latest setup` also removes those broken links (macOS).
|
|
43
53
|
- **Local state:** on the first run, settings, project mappings, drafts, apply history and the reminder config are copied from `~/.logwork-helper` to `~/.logwork-cli`. The old directory is left in place.
|
|
44
54
|
- **Login sessions:** your RO and Jira sessions are copied from the old Keychain / Credential Manager entry (`logwork-helper`) to the new one (`logwork-cli`), so you do not need to log in again.
|
|
45
55
|
- **Repo mappings:** a repo that has `.logwork-helper.json` keeps using it until a `.logwork.json` exists.
|
|
@@ -110,12 +120,13 @@ How the draft is built:
|
|
|
110
120
|
| `chore` | Other Activities |
|
|
111
121
|
|
|
112
122
|
Conventional prefixes are used first, then keywords in the subject.
|
|
113
|
-
- **
|
|
123
|
+
- **Project:** a project mapping whose `repos` list holds the repo wins (`/map --repo . 5234` in the terminal UI, or the MCP tool `map_project` with `repos`). Otherwise ticket and keyword mappings and the day's booked projects pick it, the same way `log_work` does. Repos with no project are listed so you can map them.
|
|
124
|
+
- **Hours:** each project's RO remaining time (booked − logged) is split across that project's groups in 0.5h steps, weighted by commits and lines changed, and never exceeds it. A project with no room left gets 0h. Groups without a project share the room of booked projects that no group claimed. Days without a booking get 0h. Without RO access, a weekday counts as 8h, split across all groups.
|
|
114
125
|
|
|
115
126
|
Nothing is written. Paste the draft into `/logwork`, or let your AI agent use it.
|
|
116
127
|
|
|
117
128
|
For agents:
|
|
118
|
-
- **MCP tool `draft_from_commits`** returns the same draft as `draftText
|
|
129
|
+
- **MCP tool `draft_from_commits`** returns the same draft as `draftText`, plus `projectOverrides` (preview entry id → `projectMemberId`) for every line with a project. The agent passes both to `log_work` unchanged. In the terminal UI, pasted lines are resolved again from mappings and bookings.
|
|
119
130
|
- **MCP prompt `log_from_commits`** gives an agent the full procedure:
|
|
120
131
|
1. find gaps with `get_logwork`;
|
|
121
132
|
2. draft from commits;
|
|
@@ -176,13 +187,13 @@ The default tasks shared by all projects are:
|
|
|
176
187
|
|
|
177
188
|
`logwork setup` prints the configs. You can also open the terminal UI with `logwork` and type `/mcp` (or `/mcp cursor`, `/mcp antigravity`, `/mcp copilot`, `/mcp claude-code`, `/mcp codex`).
|
|
178
189
|
|
|
179
|
-
Every client runs the same thing,
|
|
190
|
+
Every client runs the same thing, `<node> ~/.logwork-cli/bin/logwork.mjs mcp`, under the server name `logwork`. The printed configs use the absolute path of your Node binary, because IDEs started from the Dock or Finder often do not see nvm's `node` on `PATH`:
|
|
180
191
|
|
|
181
192
|
```json
|
|
182
193
|
{
|
|
183
194
|
"mcpServers": {
|
|
184
195
|
"logwork": {
|
|
185
|
-
"command": "node",
|
|
196
|
+
"command": "/Users/you/.nvm/versions/node/v24.1.0/bin/node",
|
|
186
197
|
"args": ["/Users/you/.logwork-cli/bin/logwork.mjs", "mcp"]
|
|
187
198
|
}
|
|
188
199
|
}
|
|
@@ -192,9 +203,11 @@ Every client runs the same thing, `node ~/.logwork-cli/bin/logwork.mjs mcp`, und
|
|
|
192
203
|
For Claude Code, run:
|
|
193
204
|
|
|
194
205
|
```bash
|
|
195
|
-
claude mcp add --transport stdio logwork -- node ~/.logwork-cli/bin/logwork.mjs mcp
|
|
206
|
+
claude mcp add --transport stdio logwork -- "$(which node)" ~/.logwork-cli/bin/logwork.mjs mcp
|
|
196
207
|
```
|
|
197
208
|
|
|
209
|
+
If you upgrade or remove that Node (`nvm uninstall`, `brew upgrade node`), re-run `logwork setup` and paste the new config. `logwork doctor` warns when the recorded Node path is gone.
|
|
210
|
+
|
|
198
211
|
Restart or reload the IDE's MCP tools after saving.
|
|
199
212
|
|
|
200
213
|
The server exposes 16 tools and one prompt (`log_from_commits`). It also sends `instructions` covering the approval rules, the line format and the recommended call order. Every write is **two calls to the same tool**:
|
|
@@ -279,7 +292,7 @@ In the terminal UI, `/logwork` opens a target picker. `/logwork ro` writes Resou
|
|
|
279
292
|
- `/query this-week`
|
|
280
293
|
- `/reconcile`
|
|
281
294
|
- `/projects`
|
|
282
|
-
- `/map SCB 5234`
|
|
295
|
+
- `/map SCB 5234` (ticket prefix) or `/map --repo . 5234` (this git repo, for commit drafts)
|
|
283
296
|
- `/history`
|
|
284
297
|
- `/diagnostics`
|
|
285
298
|
|
|
@@ -288,6 +301,7 @@ In the terminal UI, `/logwork` opens a target picker. `/logwork ro` writes Resou
|
|
|
288
301
|
```bash
|
|
289
302
|
logwork reminder enable # 17:00 Monday-Friday, RO and Jira
|
|
290
303
|
logwork reminder enable --time 18:00 --target ro
|
|
304
|
+
logwork reminder enable --repo ~/code/app --repo ~/code/api # add commit evidence
|
|
291
305
|
logwork reminder test
|
|
292
306
|
logwork reminder status
|
|
293
307
|
logwork reminder disable
|
|
@@ -295,7 +309,19 @@ logwork reminder disable
|
|
|
295
309
|
|
|
296
310
|
For Resource Optimiser, the reminder compares today's logged and booked hours. For Jira, it checks whether you have any worklog today. Complete days stay silent.
|
|
297
311
|
|
|
298
|
-
|
|
312
|
+
With `--repo` (repeatable; `--no-repos` clears the list), a reminder about missing RO hours also counts your commits today in those repos and points you to a draft:
|
|
313
|
+
|
|
314
|
+
```text
|
|
315
|
+
Logwork hôm nay chưa hoàn tất
|
|
316
|
+
RO: 4/8h, còn thiếu 4h
|
|
317
|
+
Hôm nay: 5 commit trong 2 repo
|
|
318
|
+
|
|
319
|
+
Mở Terminal và chạy: logwork commits today (bản nháp từ commit)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Repos listed on project mappings are used too. Run outside a git repo, `logwork commits today` falls back to these repos. If reading commits fails, the reminder shows the plain message.
|
|
323
|
+
|
|
324
|
+
macOS uses a user `launchd` agent (`sg.vinova.logwork-cli.reminder`) and Windows uses Task Scheduler (`Logwork Reminder`). Neither schedule stores credentials. The schedule stores the absolute Node path; after upgrading or removing that Node, run `logwork reminder enable` (or `logwork setup`) again. `logwork reminder status` warns when that path no longer exists.
|
|
299
325
|
|
|
300
326
|
## Reconcile RO And Jira
|
|
301
327
|
|
package/RELEASE.md
CHANGED
package/commands/commits.mjs
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
1
3
|
import { draftLogworkFromCommits } from '../lib/commit-workflow.mjs';
|
|
2
4
|
import { normalizeReconciliationPeriod } from '../lib/reconciliation-workflow.mjs';
|
|
5
|
+
import { loadReminderConfig } from '../lib/reminder-config.mjs';
|
|
6
|
+
import { collectReminderRepos } from '../lib/reminder-workflow.mjs';
|
|
3
7
|
|
|
4
8
|
// `logwork commits [period] [--repo path]... [--author x] [--no-bookings] [--json]`:
|
|
5
9
|
// turn your git commits into a logwork draft. Read-only.
|
|
@@ -44,29 +48,71 @@ export function parseCommitsArgs(args = []) {
|
|
|
44
48
|
return options;
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
export async function run(args = [], {
|
|
51
|
+
export async function run(args = [], {
|
|
52
|
+
draft = draftLogworkFromCommits,
|
|
53
|
+
print = console.log,
|
|
54
|
+
cwd = process.cwd(),
|
|
55
|
+
reminderRepos = loadReminderRepos
|
|
56
|
+
} = {}) {
|
|
48
57
|
const options = parseCommitsArgs(args);
|
|
49
58
|
if (options.help) {
|
|
50
59
|
print(formatCommitsHelp());
|
|
51
60
|
return 0;
|
|
52
61
|
}
|
|
62
|
+
// The reminder tells the user to run `logwork commits today` from a fresh
|
|
63
|
+
// terminal, which starts outside any repo. There, fall back to the
|
|
64
|
+
// reminder's repos instead of reporting no commits.
|
|
65
|
+
let repos = options.repos;
|
|
66
|
+
let fallbackNote = '';
|
|
67
|
+
if (!repos.length && !isInsideGitWorkTree(cwd)) {
|
|
68
|
+
const fallback = await reminderRepos().catch(() => []);
|
|
69
|
+
if (fallback.length) {
|
|
70
|
+
repos = fallback;
|
|
71
|
+
fallbackNote = `Not in a git repo; using the reminder repos: ${fallback.join(', ')}`;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
53
74
|
const result = await draft({
|
|
54
75
|
period: options.period,
|
|
55
|
-
repos
|
|
76
|
+
repos,
|
|
56
77
|
author: options.author,
|
|
57
78
|
useBookings: options.useBookings
|
|
58
79
|
});
|
|
59
|
-
|
|
80
|
+
if (options.json) {
|
|
81
|
+
print(JSON.stringify(result, null, 2));
|
|
82
|
+
} else {
|
|
83
|
+
print(fallbackNote ? `${fallbackNote}\n\n${result.summary}` : result.summary);
|
|
84
|
+
}
|
|
60
85
|
return 0;
|
|
61
86
|
}
|
|
62
87
|
|
|
88
|
+
// True when dir or a parent holds .git (a directory, or a file for worktrees
|
|
89
|
+
// and submodules). No git process is started.
|
|
90
|
+
export function isInsideGitWorkTree(dir, { exists = existsSync } = {}) {
|
|
91
|
+
let current = resolve(dir);
|
|
92
|
+
for (;;) {
|
|
93
|
+
if (exists(join(current, '.git'))) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
const parent = dirname(current);
|
|
97
|
+
if (parent === current) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
current = parent;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function loadReminderRepos() {
|
|
105
|
+
return collectReminderRepos({ config: await loadReminderConfig() });
|
|
106
|
+
}
|
|
107
|
+
|
|
63
108
|
export function formatCommitsHelp() {
|
|
64
109
|
return `Usage:
|
|
65
110
|
logwork commits [today|yesterday|this-week|last-week|this-month|last-month]
|
|
66
111
|
[--repo <path>]... [--author <email>] [--no-bookings] [--json]
|
|
67
112
|
|
|
68
113
|
Reads your commits (all branches, your git user.email) from each repo (default:
|
|
69
|
-
the current directory
|
|
114
|
+
the current directory; outside a git repo, the repos set with
|
|
115
|
+
\`logwork reminder enable --repo\`) and drafts logwork lines:
|
|
70
116
|
+<hours> <worklog task> | <description>
|
|
71
117
|
|
|
72
118
|
- Commits are grouped per day by repo, ticket key and kind of work.
|
|
@@ -74,8 +120,13 @@ the current directory) and drafts logwork lines:
|
|
|
74
120
|
fix -> Bug Fix, refactor/perf/review -> Code Review & Refactoring,
|
|
75
121
|
test -> Testing Activities, ci/build/release -> Deployment & Release,
|
|
76
122
|
docs/design -> Requirements & Design, chore -> Other Activities.
|
|
77
|
-
-
|
|
78
|
-
|
|
123
|
+
- Each group gets an RO project: a mapping whose repos holds the repo (/map
|
|
124
|
+
--repo in the terminal UI, or map_project), else ticket/keyword mappings and
|
|
125
|
+
that day's bookings.
|
|
126
|
+
- Each project's RO remaining time (booked - logged) is split across its
|
|
127
|
+
groups by how much work each holds; groups without a project share the room
|
|
128
|
+
of booked projects no group claimed. With --no-bookings or without login, a
|
|
129
|
+
weekday is 8h split across all groups.
|
|
79
130
|
|
|
80
131
|
Nothing is written. Review the draft, then paste it into /logwork or give it
|
|
81
132
|
to your AI agent (log_work).`;
|
package/commands/history.mjs
CHANGED
package/commands/quick.mjs
CHANGED
|
@@ -10,41 +10,56 @@ import {
|
|
|
10
10
|
import * as ui from '../lib/ui.mjs';
|
|
11
11
|
import { todayLocalDateISO } from '../lib/util.mjs';
|
|
12
12
|
|
|
13
|
+
const QUICK_API = { addLogtime, fetchWorklogTasksForProject, getTodayTimesheetProjects };
|
|
14
|
+
|
|
13
15
|
// `logwork quick [-m "what you did"]`: log one Resource Optimiser entry for
|
|
14
16
|
// today with guided prompts (project -> worklog task -> description -> hours).
|
|
15
|
-
|
|
17
|
+
// The second argument of run and the options of runQuickLog are test seams;
|
|
18
|
+
// the CLI uses the defaults.
|
|
19
|
+
export async function run(argv = [], {
|
|
20
|
+
quickLog = runQuickLog,
|
|
21
|
+
quickUi = ui,
|
|
22
|
+
showIntro = intro,
|
|
23
|
+
print = console.log
|
|
24
|
+
} = {}) {
|
|
16
25
|
const { message, help } = parseQuickArgs(argv);
|
|
17
26
|
if (help) {
|
|
18
|
-
|
|
27
|
+
print(formatQuickHelp());
|
|
19
28
|
return 0;
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
try {
|
|
23
|
-
|
|
24
|
-
const result = await
|
|
25
|
-
|
|
32
|
+
showIntro('Logwork quick');
|
|
33
|
+
const result = await quickLog({ message });
|
|
34
|
+
quickUi.showSuccess(result.dryRun
|
|
26
35
|
? 'DRY RUN: payload generated; no API write performed.'
|
|
27
36
|
: 'Logwork submitted.');
|
|
28
37
|
return 0;
|
|
29
38
|
} catch (error) {
|
|
30
|
-
|
|
39
|
+
quickUi.showError(error);
|
|
31
40
|
return 1;
|
|
32
41
|
}
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
export async function runQuickLog({
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
export async function runQuickLog({
|
|
45
|
+
message = '',
|
|
46
|
+
quickUi = ui,
|
|
47
|
+
openSession: open = openSession,
|
|
48
|
+
api = QUICK_API,
|
|
49
|
+
today = todayLocalDateISO
|
|
50
|
+
} = {}) {
|
|
51
|
+
const localDateISO = today();
|
|
52
|
+
let session = await open();
|
|
38
53
|
|
|
39
54
|
let projects;
|
|
40
55
|
try {
|
|
41
|
-
projects = await loadBookedProjects(session, localDateISO);
|
|
56
|
+
projects = await loadBookedProjects(session, localDateISO, api);
|
|
42
57
|
} catch (error) {
|
|
43
58
|
if (!isUnauthorized(error)) {
|
|
44
59
|
throw error;
|
|
45
60
|
}
|
|
46
|
-
session = await
|
|
47
|
-
projects = await loadBookedProjects(session, localDateISO);
|
|
61
|
+
session = await open({ forceLogin: true });
|
|
62
|
+
projects = await loadBookedProjects(session, localDateISO, api);
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
const daySummaries = projects.map((project) => ({
|
|
@@ -52,8 +67,8 @@ export async function runQuickLog({ message = '' } = {}) {
|
|
|
52
67
|
totalHours: project.loggedHours,
|
|
53
68
|
ok: true
|
|
54
69
|
}));
|
|
55
|
-
const project = await
|
|
56
|
-
const tasks = (await fetchWorklogTasksForProject(session.token, project.projectId)).map((task) => ({
|
|
70
|
+
const project = await quickUi.chooseProject(projects, daySummaries);
|
|
71
|
+
const tasks = (await api.fetchWorklogTasksForProject(session.token, project.projectId)).map((task) => ({
|
|
57
72
|
taskId: task.id,
|
|
58
73
|
name: task.name,
|
|
59
74
|
typeOfWork: task.typeOfWork || 'other',
|
|
@@ -63,11 +78,11 @@ export async function runQuickLog({ message = '' } = {}) {
|
|
|
63
78
|
if (!tasks.length) {
|
|
64
79
|
throw new Error(`No worklog tasks are available for ${project.projectName}.`);
|
|
65
80
|
}
|
|
66
|
-
const task = await
|
|
67
|
-
const description = await
|
|
68
|
-
const hours = await
|
|
81
|
+
const task = await quickUi.chooseTask(tasks);
|
|
82
|
+
const description = await quickUi.chooseMessageSource(message);
|
|
83
|
+
const hours = await quickUi.askHours(CONFIG.defaultHours);
|
|
69
84
|
|
|
70
|
-
await
|
|
85
|
+
await quickUi.confirmSubmit({
|
|
71
86
|
projectName: project.projectName,
|
|
72
87
|
hours,
|
|
73
88
|
taskName: task.name,
|
|
@@ -88,13 +103,13 @@ export async function runQuickLog({ message = '' } = {}) {
|
|
|
88
103
|
};
|
|
89
104
|
|
|
90
105
|
try {
|
|
91
|
-
return await addLogtime(session.token, entry);
|
|
106
|
+
return await api.addLogtime(session.token, entry);
|
|
92
107
|
} catch (error) {
|
|
93
108
|
if (!isUnauthorized(error)) {
|
|
94
109
|
throw error;
|
|
95
110
|
}
|
|
96
|
-
session = await
|
|
97
|
-
return addLogtime(session.token, entry);
|
|
111
|
+
session = await open({ forceLogin: true });
|
|
112
|
+
return api.addLogtime(session.token, entry);
|
|
98
113
|
}
|
|
99
114
|
}
|
|
100
115
|
|
|
@@ -150,8 +165,8 @@ async function openSession({ forceLogin = false } = {}) {
|
|
|
150
165
|
return { token, userId: getUserIdFromJwt(token) };
|
|
151
166
|
}
|
|
152
167
|
|
|
153
|
-
async function loadBookedProjects(session, localDateISO) {
|
|
154
|
-
const projects = await getTodayTimesheetProjects(session.token, session.userId, localDateISO);
|
|
168
|
+
async function loadBookedProjects(session, localDateISO, api) {
|
|
169
|
+
const projects = await api.getTodayTimesheetProjects(session.token, session.userId, localDateISO);
|
|
155
170
|
if (!projects.length) {
|
|
156
171
|
throw new Error(`You have no booked project on ${localDateISO}.`);
|
|
157
172
|
}
|
package/commands/reminder.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { confirm, isCancel } from '@clack/prompts';
|
|
2
2
|
import {
|
|
3
|
+
normalizeReminderRepos,
|
|
3
4
|
normalizeReminderTime,
|
|
4
5
|
REMINDER_TARGETS
|
|
5
6
|
} from '../lib/reminder-config.mjs';
|
|
@@ -27,6 +28,7 @@ export function parseReminderArgs(args = []) {
|
|
|
27
28
|
yes: false,
|
|
28
29
|
json: false,
|
|
29
30
|
home: undefined,
|
|
31
|
+
repos: undefined,
|
|
30
32
|
help: false
|
|
31
33
|
};
|
|
32
34
|
for (let index = 0; index < rest.length; index += 1) {
|
|
@@ -44,6 +46,27 @@ export function parseReminderArgs(args = []) {
|
|
|
44
46
|
index += 1;
|
|
45
47
|
continue;
|
|
46
48
|
}
|
|
49
|
+
if (arg === '--repo' || arg.startsWith('--repo=')) {
|
|
50
|
+
const value = arg === '--repo' ? rest[index + 1] : arg.slice('--repo='.length);
|
|
51
|
+
if (!value || (arg === '--repo' && value.startsWith('--'))) {
|
|
52
|
+
throw new Error('--repo requires a path.');
|
|
53
|
+
}
|
|
54
|
+
if (options.repos === null) {
|
|
55
|
+
throw new Error('--repo cannot be combined with --no-repos.');
|
|
56
|
+
}
|
|
57
|
+
options.repos = [...(options.repos || []), value];
|
|
58
|
+
if (arg === '--repo') {
|
|
59
|
+
index += 1;
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (arg === '--no-repos') {
|
|
64
|
+
if (options.repos?.length) {
|
|
65
|
+
throw new Error('--repo cannot be combined with --no-repos.');
|
|
66
|
+
}
|
|
67
|
+
options.repos = null;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
47
70
|
if (arg === '--yes' || arg === '-y') {
|
|
48
71
|
options.yes = true;
|
|
49
72
|
continue;
|
|
@@ -61,6 +84,16 @@ export function parseReminderArgs(args = []) {
|
|
|
61
84
|
if (command !== 'enable' && options.time) {
|
|
62
85
|
throw new Error('--time is only valid with reminder enable.');
|
|
63
86
|
}
|
|
87
|
+
if (command !== 'enable' && options.repos !== undefined) {
|
|
88
|
+
throw new Error('--repo and --no-repos are only valid with reminder enable.');
|
|
89
|
+
}
|
|
90
|
+
if (Array.isArray(options.repos)) {
|
|
91
|
+
// Resolve against the directory the command runs in; the scheduled run
|
|
92
|
+
// starts in the helper home.
|
|
93
|
+
options.repos = normalizeReminderRepos(options.repos);
|
|
94
|
+
} else if (options.repos === null) {
|
|
95
|
+
options.repos = [];
|
|
96
|
+
}
|
|
64
97
|
if (command !== 'run' && options.home) {
|
|
65
98
|
throw new Error('--home is reserved for the OS reminder runner.');
|
|
66
99
|
}
|
|
@@ -131,6 +164,7 @@ export async function runReminderCli(args = [], {
|
|
|
131
164
|
result = await enable({
|
|
132
165
|
time: options.time,
|
|
133
166
|
target: options.target,
|
|
167
|
+
...(options.repos !== undefined ? { repos: options.repos } : {}),
|
|
134
168
|
confirm: true
|
|
135
169
|
});
|
|
136
170
|
} else if (options.command === 'disable') {
|
|
@@ -147,12 +181,17 @@ export async function runReminderCli(args = [], {
|
|
|
147
181
|
|
|
148
182
|
export function formatReminderHelp() {
|
|
149
183
|
return `Usage:
|
|
150
|
-
logwork reminder enable [--time HH:mm] [--target ro|jira|both]
|
|
184
|
+
logwork reminder enable [--time HH:mm] [--target ro|jira|both]
|
|
185
|
+
[--repo <path>]... [--no-repos] [--yes]
|
|
151
186
|
logwork reminder status [--json]
|
|
152
187
|
logwork reminder test [--target ro|jira|both] [--yes]
|
|
153
188
|
logwork reminder disable [--yes]
|
|
154
189
|
|
|
155
|
-
The reminder runs Monday-Friday at 17:00 by default. It uses launchd on macOS and Task Scheduler on Windows. RO compares logged and booked hours; Jira checks whether the current user has a worklog today
|
|
190
|
+
The reminder runs Monday-Friday at 17:00 by default. It uses launchd on macOS and Task Scheduler on Windows. RO compares logged and booked hours; Jira checks whether the current user has a worklog today.
|
|
191
|
+
|
|
192
|
+
--repo (repeatable) sets the git repos whose commits today are counted when RO hours are missing, for example "Hôm nay: 5 commit trong 2 repo", and points to \`logwork commits today\` for a draft. Repos listed on project mappings are also used. --repo replaces the saved list; --no-repos clears it; without either the saved list is kept.
|
|
193
|
+
|
|
194
|
+
The schedule stores the absolute path of the Node that ran \`reminder enable\`. After upgrading or removing that Node (nvm, Homebrew), run \`logwork reminder enable\` or \`logwork setup\` again; \`logwork reminder status\` warns when the path is gone.`;
|
|
156
195
|
}
|
|
157
196
|
|
|
158
197
|
async function requireInteractiveConfirmation({ isTTY, promptConfirm, message }) {
|