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/commands/setup.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { constants as fsConstants, promises as fs } from 'node:fs';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
|
-
import { delimiter, dirname, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { delimiter, dirname, join, relative, resolve, win32 } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { redactText } from '../lib/auth-redaction.mjs';
|
|
6
6
|
import { credentialStoreLabel } from '../lib/credential-store.mjs';
|
|
7
|
-
import {
|
|
7
|
+
import { npmInvocation } from '../lib/npm-command.mjs';
|
|
8
|
+
import { writeRuntimeInfo } from '../lib/node-runtime.mjs';
|
|
9
|
+
import { RUNTIME_INFO_FILE_NAME, helperHome } from '../lib/paths.mjs';
|
|
8
10
|
import {
|
|
11
|
+
MCP_NODE_PATH_NOTE,
|
|
9
12
|
MCP_SERVER_NAME,
|
|
10
|
-
defaultMcpServerPath,
|
|
11
13
|
formatCodexToml,
|
|
12
14
|
stdioMcpServersConfig,
|
|
13
15
|
vscodeMcpConfig
|
|
@@ -59,11 +61,12 @@ export async function installUserRuntime({
|
|
|
59
61
|
sourceDir,
|
|
60
62
|
targetDir,
|
|
61
63
|
mkdirFn = fs.mkdir,
|
|
62
|
-
copyFn =
|
|
64
|
+
copyFn = copyRuntimeFiles,
|
|
63
65
|
cleanupFn = sanitizeRuntimeMetadata,
|
|
64
66
|
installFn = installDependencies,
|
|
65
67
|
linkFn = linkGlobalBinaries,
|
|
66
68
|
authFn = maybeRunAuthLogin,
|
|
69
|
+
runtimeInfoFn = recordRuntimeInfo,
|
|
67
70
|
reminderFn = refreshReminderSchedule,
|
|
68
71
|
printFn = printMcpInstructions
|
|
69
72
|
}) {
|
|
@@ -75,6 +78,7 @@ export async function installUserRuntime({
|
|
|
75
78
|
await installFn(targetDir);
|
|
76
79
|
const linkResult = await linkFn(targetDir);
|
|
77
80
|
const authResult = await authFn(targetDir, options);
|
|
81
|
+
await runtimeInfoFn(targetDir);
|
|
78
82
|
const reminderResult = await reminderFn(targetDir);
|
|
79
83
|
await printFn(targetDir, authResult, linkResult, cleanupResult, reminderResult);
|
|
80
84
|
|
|
@@ -87,22 +91,83 @@ export async function installUserRuntime({
|
|
|
87
91
|
};
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
async function
|
|
94
|
+
// Records the node that ran setup (the one the printed MCP configs and the
|
|
95
|
+
// reminder use), so doctor and reminder status can spot a removed or switched
|
|
96
|
+
// Node later. Best effort: a failed write must not fail the install.
|
|
97
|
+
export async function recordRuntimeInfo(targetDir, {
|
|
98
|
+
nodePath = process.execPath,
|
|
99
|
+
nodeVersion = process.version,
|
|
100
|
+
writeFn = writeRuntimeInfo,
|
|
101
|
+
readFileFn = fs.readFile
|
|
102
|
+
} = {}) {
|
|
103
|
+
try {
|
|
104
|
+
const manifest = JSON.parse(await readFileFn(join(targetDir, 'package.json'), 'utf8'));
|
|
105
|
+
return await writeFn({
|
|
106
|
+
path: join(targetDir, RUNTIME_INFO_FILE_NAME),
|
|
107
|
+
nodePath,
|
|
108
|
+
nodeVersion,
|
|
109
|
+
version: manifest.version || null
|
|
110
|
+
});
|
|
111
|
+
} catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// A reminder enabled by an older install still points at the old runtime, or
|
|
117
|
+
// at a Node that nvm or Homebrew removed. Re-register it so the OS scheduler
|
|
118
|
+
// runs this runtime with the current node (and the legacy entry is removed by
|
|
119
|
+
// installReminderScheduler).
|
|
120
|
+
export async function refreshReminderSchedule(targetDir, {
|
|
121
|
+
nodePath = process.execPath,
|
|
122
|
+
loadConfig = loadReminderConfig,
|
|
123
|
+
install = installReminderScheduler
|
|
124
|
+
} = {}) {
|
|
94
125
|
try {
|
|
95
|
-
const config = await
|
|
126
|
+
const config = await loadConfig();
|
|
96
127
|
if (!config?.enabled) {
|
|
97
128
|
return null;
|
|
98
129
|
}
|
|
99
|
-
await
|
|
100
|
-
return { ok: true };
|
|
130
|
+
await install(config, { home: targetDir, nodePath });
|
|
131
|
+
return { ok: true, nodePath };
|
|
101
132
|
} catch (error) {
|
|
102
133
|
return { ok: false, error: error.message };
|
|
103
134
|
}
|
|
104
135
|
}
|
|
105
136
|
|
|
137
|
+
// Copies only what the npm package ships (package.json "files", plus the
|
|
138
|
+
// manifest and lockfile). The target is also the state directory, so a git
|
|
139
|
+
// checkout's .env files, tests, archives or a repo-scope .logwork.json must
|
|
140
|
+
// never land there.
|
|
141
|
+
export async function copyRuntimeFiles(sourceDir, targetDir) {
|
|
142
|
+
const manifest = JSON.parse(await fs.readFile(join(sourceDir, 'package.json'), 'utf8'));
|
|
143
|
+
const listed = Array.isArray(manifest.files) ? manifest.files : [];
|
|
144
|
+
const names = ['package.json', 'package-lock.json', 'npm-shrinkwrap.json', ...listed.map((item) => String(item).replace(/[\\/]+$/, ''))];
|
|
145
|
+
for (const name of [...new Set(names)]) {
|
|
146
|
+
const sourcePath = join(sourceDir, name);
|
|
147
|
+
let stat;
|
|
148
|
+
try {
|
|
149
|
+
stat = await fs.stat(sourcePath);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error.code === 'ENOENT') {
|
|
152
|
+
// A lockfile from an earlier install that no longer matches this
|
|
153
|
+
// package.json would make `npm ci` fail; let npm install write a new one.
|
|
154
|
+
if (name === 'package-lock.json') {
|
|
155
|
+
await fs.rm(join(targetDir, name), { force: true });
|
|
156
|
+
}
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
if (stat.isDirectory()) {
|
|
162
|
+
await fs.mkdir(join(targetDir, name), { recursive: true });
|
|
163
|
+
await copyDirectory(sourcePath, targetDir, sourceDir);
|
|
164
|
+
} else {
|
|
165
|
+
await fs.mkdir(dirname(join(targetDir, name)), { recursive: true });
|
|
166
|
+
await fs.copyFile(sourcePath, join(targetDir, name));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
106
171
|
async function copyDirectory(sourceDir, targetDir, rootDir) {
|
|
107
172
|
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
|
108
173
|
|
|
@@ -139,7 +204,8 @@ function installDependencies(targetDir) {
|
|
|
139
204
|
|
|
140
205
|
function runCommand(command, args, options = {}) {
|
|
141
206
|
return new Promise((resolveValue, reject) => {
|
|
142
|
-
const
|
|
207
|
+
const invocation = command === 'npm' ? npmInvocation(args) : { command, args, shell: false };
|
|
208
|
+
const child = spawn(invocation.command, invocation.args, { ...options, shell: invocation.shell });
|
|
143
209
|
|
|
144
210
|
child.on('error', reject);
|
|
145
211
|
child.on('close', (code) => {
|
|
@@ -243,9 +309,73 @@ function isStaleSelfDependency(value) {
|
|
|
243
309
|
return typeof value === 'string' && /^file:.*logwork-(helper|cli).*\.tgz$/i.test(value);
|
|
244
310
|
}
|
|
245
311
|
|
|
312
|
+
const LEGACY_GLOBAL_LINKS = [
|
|
313
|
+
'bin/logwork',
|
|
314
|
+
'bin/logwork-helper',
|
|
315
|
+
'bin/logwork-helper-install',
|
|
316
|
+
'bin/logwork-helper-manual',
|
|
317
|
+
'bin/logwork-helper-mcp',
|
|
318
|
+
'bin/logwork-helper-setup-user',
|
|
319
|
+
'lib/node_modules/logwork-helper'
|
|
320
|
+
];
|
|
321
|
+
|
|
322
|
+
// The old package was installed with `npm link` from ~/.logwork-helper. When
|
|
323
|
+
// that folder is deleted first, its global links dangle: `npm rm -g` cannot
|
|
324
|
+
// read the package any more and leaves bin/logwork behind, which then blocks
|
|
325
|
+
// `npm install -g logwork-cli` and `npm link` with EEXIST. Remove only links
|
|
326
|
+
// that point into a logwork-helper path that no longer exists.
|
|
327
|
+
export async function removeDanglingLegacyLinks(globalPrefix, {
|
|
328
|
+
platform = process.platform,
|
|
329
|
+
lstatFn = fs.lstat,
|
|
330
|
+
statFn = fs.stat,
|
|
331
|
+
readlinkFn = fs.readlink,
|
|
332
|
+
unlinkFn = fs.unlink
|
|
333
|
+
} = {}) {
|
|
334
|
+
const removed = [];
|
|
335
|
+
if (!globalPrefix || platform === 'win32') {
|
|
336
|
+
return removed;
|
|
337
|
+
}
|
|
338
|
+
for (const relativePath of LEGACY_GLOBAL_LINKS) {
|
|
339
|
+
const path = join(globalPrefix, relativePath);
|
|
340
|
+
try {
|
|
341
|
+
if (!(await lstatFn(path)).isSymbolicLink()) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
if (!/logwork-helper/.test(await readlinkFn(path))) {
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
try {
|
|
348
|
+
await statFn(path);
|
|
349
|
+
continue;
|
|
350
|
+
} catch {
|
|
351
|
+
// Dangling: the target is gone.
|
|
352
|
+
}
|
|
353
|
+
await unlinkFn(path);
|
|
354
|
+
removed.push(path);
|
|
355
|
+
} catch {
|
|
356
|
+
// Missing or unreadable: nothing to clean.
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return removed;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function readNpmGlobalPrefix(cwd) {
|
|
363
|
+
return new Promise((resolveValue) => {
|
|
364
|
+
const invocation = npmInvocation(['prefix', '-g']);
|
|
365
|
+
const child = spawn(invocation.command, invocation.args, { cwd, shell: invocation.shell, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
366
|
+
let stdout = '';
|
|
367
|
+
child.stdout.setEncoding('utf8');
|
|
368
|
+
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
369
|
+
child.on('error', () => resolveValue(''));
|
|
370
|
+
child.on('close', (code) => resolveValue(code === 0 ? stdout.trim() : ''));
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
246
374
|
export async function linkGlobalBinaries(targetDir, {
|
|
247
375
|
runner = runCommand,
|
|
248
|
-
commandFinder = findCommandOnPath
|
|
376
|
+
commandFinder = findCommandOnPath,
|
|
377
|
+
prefixReader = readNpmGlobalPrefix,
|
|
378
|
+
legacyLinkCleaner = removeDanglingLegacyLinks
|
|
249
379
|
} = {}) {
|
|
250
380
|
// Best effort: drop the pre-rename global package so its `logwork` bin does
|
|
251
381
|
// not block linking this one. A no-op when it is not installed.
|
|
@@ -253,6 +383,10 @@ export async function linkGlobalBinaries(targetDir, {
|
|
|
253
383
|
cwd: targetDir,
|
|
254
384
|
stdio: 'ignore'
|
|
255
385
|
}).catch(() => {});
|
|
386
|
+
await Promise.resolve()
|
|
387
|
+
.then(() => prefixReader(targetDir))
|
|
388
|
+
.then((prefix) => legacyLinkCleaner(prefix))
|
|
389
|
+
.catch(() => []);
|
|
256
390
|
|
|
257
391
|
try {
|
|
258
392
|
await runner('npm', ['link'], {
|
|
@@ -403,9 +537,12 @@ export function formatSetupUserInstructions({
|
|
|
403
537
|
linkResult = null,
|
|
404
538
|
cleanupResult = null,
|
|
405
539
|
reminderResult = null,
|
|
406
|
-
platform = process.platform
|
|
540
|
+
platform = process.platform,
|
|
541
|
+
nodePath = process.execPath
|
|
407
542
|
} = {}) {
|
|
408
|
-
const entryPath =
|
|
543
|
+
const entryPath = platform === 'win32'
|
|
544
|
+
? win32.resolve(targetDir, 'bin', 'logwork.mjs')
|
|
545
|
+
: resolve(targetDir, 'bin', 'logwork.mjs');
|
|
409
546
|
const storeLabel = credentialStoreLabel({ platform });
|
|
410
547
|
const sections = [
|
|
411
548
|
`logwork-cli installed to ${targetDir}.`,
|
|
@@ -424,16 +561,17 @@ export function formatSetupUserInstructions({
|
|
|
424
561
|
'Next steps',
|
|
425
562
|
...formatNextSteps(authResult),
|
|
426
563
|
'',
|
|
427
|
-
`MCP config (server name "${MCP_SERVER_NAME}", runs:
|
|
564
|
+
`MCP config (server name "${MCP_SERVER_NAME}", runs: ${nodePath} ${entryPath} mcp)`,
|
|
565
|
+
MCP_NODE_PATH_NOTE,
|
|
428
566
|
'',
|
|
429
567
|
'Cursor / Antigravity / Claude Code (.mcp.json):',
|
|
430
|
-
JSON.stringify(stdioMcpServersConfig(entryPath), null, 2),
|
|
568
|
+
JSON.stringify(stdioMcpServersConfig(entryPath, { nodePath }), null, 2),
|
|
431
569
|
'',
|
|
432
570
|
'Codex config.toml:',
|
|
433
|
-
formatCodexToml(entryPath),
|
|
571
|
+
formatCodexToml(entryPath, { nodePath }),
|
|
434
572
|
'',
|
|
435
573
|
'GitHub Copilot / VS Code mcp.json:',
|
|
436
|
-
JSON.stringify(vscodeMcpConfig(entryPath), null, 2),
|
|
574
|
+
JSON.stringify(vscodeMcpConfig(entryPath, { nodePath }), null, 2),
|
|
437
575
|
'',
|
|
438
576
|
'AI agent skill (log work from your commits)',
|
|
439
577
|
'- Claude Code / Codex: run `logwork skill install`.',
|
package/commands/tui.mjs
CHANGED
|
@@ -43,6 +43,7 @@ Common commands inside the UI:
|
|
|
43
43
|
/reconcile this-week Compare RO and Jira
|
|
44
44
|
/projects List RO projects
|
|
45
45
|
/map SCB 5234 Map a ticket prefix to a project
|
|
46
|
+
/map --repo . 5234 Map this git repo to a project (commit drafts)
|
|
46
47
|
/mcp Show MCP config for an IDE
|
|
47
48
|
/history Local apply history
|
|
48
49
|
|
package/docs/advanced.md
CHANGED
|
@@ -27,7 +27,7 @@ Supported overrides:
|
|
|
27
27
|
```text
|
|
28
28
|
LOGWORK_PROFILE (LOGWORK_HELPER_PROFILE still accepted)
|
|
29
29
|
LOGWORK_HOME (local state directory; LOGWORK_HELPER_HOME still accepted)
|
|
30
|
-
LOGWORK_DRY_RUN=1 (RO writes return the request
|
|
30
|
+
LOGWORK_DRY_RUN=1 (RO and Jira writes return the request instead of calling the API)
|
|
31
31
|
LOGWORK_API_BASE
|
|
32
32
|
LOGWORK_LOGIN_URL
|
|
33
33
|
LOGWORK_JIRA_BASE_URL
|
|
@@ -222,6 +222,7 @@ Useful commands inside the session:
|
|
|
222
222
|
/projects
|
|
223
223
|
/projects 5234
|
|
224
224
|
/map SCB 5234
|
|
225
|
+
/map --repo . 5234
|
|
225
226
|
/history
|
|
226
227
|
/diagnostics
|
|
227
228
|
```
|
|
@@ -285,11 +286,20 @@ Inside `logwork`, use `/history`, `/history ro`, `/history jira`, or `/history b
|
|
|
285
286
|
|
|
286
287
|
## Dry Run
|
|
287
288
|
|
|
288
|
-
`LOGWORK_DRY_RUN=1` makes Resource Optimiser
|
|
289
|
+
`LOGWORK_DRY_RUN=1` makes every Resource Optimiser and Jira write return the request (`{ dryRun: true, method, path, body }`) instead of calling the API. The request never contains the token or the `Authorization` header. Reads still hit the API: Jira issue lookup and the duplicate-worklog check still call Jira, and RO previews still read RO.
|
|
290
|
+
|
|
291
|
+
What a dry run reports:
|
|
292
|
+
|
|
293
|
+
- RO apply summaries start with `DRY RUN`; edit, resubmit and delete return `status: "dry_run"`.
|
|
294
|
+
- Jira apply (`log_jira_work` with `confirm: true`, or the terminal UI `/apply`) returns `status: "dry_run"`, `dryRun: true`, one `dry_run` result per entry with its `request`, and a summary that starts with `DRY RUN (LOGWORK_DRY_RUN=1)`.
|
|
295
|
+
- Apply history records a Jira dry run with status `dry_run` and `dryRun: true` entries. They are not counted as submitted.
|
|
296
|
+
- The terminal UI status bar shows `DRY RUN`.
|
|
289
297
|
|
|
290
298
|
```bash
|
|
291
299
|
LOGWORK_DRY_RUN=1 logwork quick -m "Dry run task"
|
|
292
300
|
LOGWORK_DRY_RUN=1 logwork edit 304032 --desc "Dry run" --yes
|
|
301
|
+
npm run dry-run # terminal UI, RO and Jira writes stubbed
|
|
302
|
+
LOGWORK_DRY_RUN=1 logwork mcp # MCP server, RO and Jira writes stubbed
|
|
293
303
|
```
|
|
294
304
|
|
|
295
305
|
In Windows PowerShell:
|
package/docs/mcp-setup.md
CHANGED
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
For the full RO/Jira/Both processing model, mapping, reconciliation, history, reminders and updates, see the [operations guide](operations-guide.md).
|
|
6
6
|
|
|
7
|
-
Every client runs
|
|
7
|
+
Every client runs `<absolute path to node> <home>/.logwork-cli/bin/logwork.mjs mcp` under the server name `logwork`.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Both paths are absolute:
|
|
10
|
+
- The entry is `bin/logwork.mjs` instead of the `logwork` command, because GUI IDEs often start without the npm global bin directory on `PATH`.
|
|
11
|
+
- The `command` is the absolute path of your Node binary instead of a bare `node`. Cursor or VS Code started from the Dock, Finder or Start menu usually does not get the `PATH` that nvm (or another version manager) sets up in your shell, so `node` is not found and the server never starts.
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
`logwork setup` and `/mcp` print both paths for the Node that ran them (`process.execPath`). To find it yourself, run `which node` (macOS) or `(Get-Command node).Source` (PowerShell).
|
|
14
|
+
|
|
15
|
+
If Node is upgraded or moved (`nvm uninstall`, `brew upgrade node`, a new installer), the old path stops working. Re-run `logwork setup` and paste the new config. `logwork doctor` warns when the Node recorded by setup no longer exists.
|
|
16
|
+
|
|
17
|
+
The examples use `/Users/<user>` and `<absolute path to node>` as placeholders. With nvm on macOS the Node path looks like `/Users/<user>/.nvm/versions/node/v24.x.y/bin/node`; with Homebrew, `/opt/homebrew/bin/node`. On Windows, use the paths printed by `logwork setup` or `/mcp`, for example `C:\\Program Files\\nodejs\\node.exe` and `C:\\Users\\<user>\\.logwork-cli\\bin\\logwork.mjs` inside JSON or TOML strings.
|
|
12
18
|
|
|
13
19
|
## Cursor / Google Antigravity
|
|
14
20
|
|
|
@@ -18,7 +24,7 @@ Cursor MCP settings, or `~/.gemini/antigravity/mcp_config.json`:
|
|
|
18
24
|
{
|
|
19
25
|
"mcpServers": {
|
|
20
26
|
"logwork": {
|
|
21
|
-
"command": "node",
|
|
27
|
+
"command": "<absolute path to node>",
|
|
22
28
|
"args": ["/Users/<user>/.logwork-cli/bin/logwork.mjs", "mcp"]
|
|
23
29
|
}
|
|
24
30
|
}
|
|
@@ -31,7 +37,7 @@ Cursor MCP settings, or `~/.gemini/antigravity/mcp_config.json`:
|
|
|
31
37
|
|
|
32
38
|
```toml
|
|
33
39
|
[mcp_servers.logwork]
|
|
34
|
-
command = "node"
|
|
40
|
+
command = "<absolute path to node>"
|
|
35
41
|
args = ["/Users/<user>/.logwork-cli/bin/logwork.mjs", "mcp"]
|
|
36
42
|
startup_timeout_sec = 20
|
|
37
43
|
tool_timeout_sec = 120
|
|
@@ -46,7 +52,7 @@ Workspace `.vscode/mcp.json`, or your VS Code user MCP config:
|
|
|
46
52
|
"servers": {
|
|
47
53
|
"logwork": {
|
|
48
54
|
"type": "stdio",
|
|
49
|
-
"command": "node",
|
|
55
|
+
"command": "<absolute path to node>",
|
|
50
56
|
"args": ["/Users/<user>/.logwork-cli/bin/logwork.mjs", "mcp"]
|
|
51
57
|
}
|
|
52
58
|
}
|
|
@@ -56,13 +62,13 @@ Workspace `.vscode/mcp.json`, or your VS Code user MCP config:
|
|
|
56
62
|
## Claude Code
|
|
57
63
|
|
|
58
64
|
```bash
|
|
59
|
-
claude mcp add --transport stdio logwork -- node "/Users/<user>/.logwork-cli/bin/logwork.mjs" mcp
|
|
65
|
+
claude mcp add --transport stdio logwork -- "<absolute path to node>" "/Users/<user>/.logwork-cli/bin/logwork.mjs" mcp
|
|
60
66
|
```
|
|
61
67
|
|
|
62
68
|
From Windows PowerShell:
|
|
63
69
|
|
|
64
70
|
```powershell
|
|
65
|
-
claude mcp add --transport stdio logwork -- node 'C:\Users\<user>\.logwork-cli\bin\logwork.mjs' mcp
|
|
71
|
+
claude mcp add --transport stdio logwork -- 'C:\Program Files\nodejs\node.exe' 'C:\Users\<user>\.logwork-cli\bin\logwork.mjs' mcp
|
|
66
72
|
```
|
|
67
73
|
|
|
68
74
|
Or add the Cursor JSON above to the project `.mcp.json`.
|
|
@@ -89,25 +95,25 @@ Previews expire after 1 hour. They are also lost when the server restarts.
|
|
|
89
95
|
| Group | Tool | Kind | Purpose |
|
|
90
96
|
|---|---|---|---|
|
|
91
97
|
| Read | `get_logwork` | read-only | Booked vs logged RO hours by day and project, with entries (`logworkId`, worklog task, description, status). |
|
|
92
|
-
| Read | `draft_from_commits` | read-only | Your git commits (all branches, your author) for a period in the given `repos`, grouped per day into draft lines with a suggested worklog task and hours that fill
|
|
98
|
+
| Read | `draft_from_commits` | read-only | Your git commits (all branches, your author) for a period in the given `repos`, grouped per day into draft lines with a suggested worklog task, an RO project (repo mapping, else ticket/keyword mappings and bookings) and hours that fill each project's remaining booked time. Returns `draftText` and `projectOverrides` for `log_work`, and `unmappedRepos`. |
|
|
93
99
|
| Read | `list_projects` | read-only | RO memberships (`projectMemberId`, `projectId`, `projectName`) and local mappings. |
|
|
94
100
|
| Read | `list_tasks` | read-only | RO worklog task catalog for one `projectId`: id, name, type of work, and when to use each task. |
|
|
95
101
|
| Read | `get_jira_issue` | read-only | Jira issue key, summary and status. |
|
|
96
102
|
| Read | `reconcile` | read-only | Compare RO and Jira hours by day. Never writes. |
|
|
97
103
|
| Read | `get_history` | read-only (local) | Local apply history. Contains no credentials. |
|
|
98
|
-
| RO write | `log_work` | two-call write | Create new RO entries from a weekly block (worklog task + description). |
|
|
104
|
+
| RO write | `log_work` | two-call write | Create new RO entries from a weekly block (worklog task + description). The preview flags possible duplicates of existing RO entries; apply refuses them without `allowDuplicates: true`. |
|
|
99
105
|
| RO write | `edit_entry` | two-call, destructive | Change hours, worklog task, type of work or description of a submitted or approved entry. Apply replaces the entry. |
|
|
100
106
|
| RO write | `resubmit_entry` | two-call write | Fix and resubmit a rejected entry (hours, type, description, note to PM). |
|
|
101
107
|
| RO write | `delete_entry` | two-call, destructive | Delete a submitted or approved entry. |
|
|
102
108
|
| Jira write | `log_jira_work` | two-call write | Create Jira worklogs. The description becomes the worklog comment. |
|
|
103
|
-
| Config | `map_project` | local write, `confirm` | Map ticket prefixes or
|
|
109
|
+
| Config | `map_project` | local write, `confirm` | Map ticket prefixes, keywords or git repos (`repos`) to an RO project (`.logwork.json`). |
|
|
104
110
|
| Config | `reminder` | `action`: status / enable / disable / test | Native OS reminder. Every action except `status` needs `confirm: true`. |
|
|
105
111
|
| Config | `update` | `action`: check / install | npm self-update. `install` needs `confirm: true`. |
|
|
106
112
|
| Auth | `login` | local | `target: "ro"` (default) opens or explains a terminal login. `target: "jira"` explains how to save a PAT. Never accepts credentials. |
|
|
107
113
|
|
|
108
114
|
### Tool Name Changes From logwork-helper
|
|
109
115
|
|
|
110
|
-
| logwork-helper (
|
|
116
|
+
| logwork-helper (0.2.x and earlier) | logwork-cli |
|
|
111
117
|
|---|---|
|
|
112
118
|
| `preview_logwork_batch`, `apply_logwork_batch` | `log_work` |
|
|
113
119
|
| `query_logwork` | `get_logwork` |
|
|
@@ -189,9 +195,15 @@ Expected flow:
|
|
|
189
195
|
|
|
190
196
|
1. `get_logwork` shows which days and projects still have booked hours to fill.
|
|
191
197
|
2. `draft_from_commits` (with `repos` set to the workspace folders) groups your commits into draft lines with hours.
|
|
198
|
+
- Each line gets a project. A `map_project` mapping whose `repos` holds the repo comes first; then ticket/keyword mappings and that day's booked projects.
|
|
199
|
+
- Each project's remaining hours are split across its own lines and never exceeded. Lines without a project share the room of booked projects that no line claimed.
|
|
200
|
+
- It returns `draftText`, plus `projectOverrides` (preview entry id → `projectMemberId`) for each line with a project.
|
|
201
|
+
- It also returns `unmappedRepos`. For those, the assistant asks which project the repo belongs to and offers `map_project` with `repos`.
|
|
192
202
|
3. `list_tasks` confirms the worklog task names for each project.
|
|
193
203
|
4. The assistant rewrites vague descriptions, shows you the block and waits for approval.
|
|
194
|
-
5. `log_work(text)` previews the entries, then `log_work(batchId, confirm: true)` applies them.
|
|
204
|
+
5. `log_work(text, projectOverrides)` previews the entries, then `log_work(batchId, confirm: true)` applies them.
|
|
205
|
+
- Preview entry ids are `<date>-<NN>`, where NN counts entry lines through the whole text. The overrides match `draftText` as returned.
|
|
206
|
+
- If lines are added, removed or reordered, the assistant renumbers the overrides.
|
|
195
207
|
|
|
196
208
|
|
|
197
209
|
|
|
@@ -227,7 +239,9 @@ A suggestion is not an approved write. It still goes through the two-call write
|
|
|
227
239
|
2. Call `list_projects`, then `list_tasks` with the `projectId`, to pick valid worklog task names.
|
|
228
240
|
3. Call `log_work` with `text`.
|
|
229
241
|
4. Show the summary and ask for approval. The summary shows each line as `project - worklog task | description`, plus an approval checklist with the total hours, date range and per-project breakdown.
|
|
230
|
-
5. Call `log_work` with `batchId` and `confirm: true`.
|
|
242
|
+
5. Call `log_work` with `batchId` and `confirm: true`.
|
|
243
|
+
- Add `allowUnbooked: true` only if the user agreed to log to a project that is not booked on that date.
|
|
244
|
+
- Add `allowDuplicates: true` only if the user confirmed that lines flagged as possible duplicates are new work (see below).
|
|
231
245
|
|
|
232
246
|
The `batchId` changes whenever a description or `defaultWorklogTask` changes, so an approval always covers the exact text that will be written. If an entry is unresolved, re-preview with `projectOverrides` (preview entry id → `projectMemberId`), or save a mapping with `map_project`.
|
|
233
247
|
|
|
@@ -338,6 +352,13 @@ The two applies are intentionally not atomic. `log_work` never writes Jira, and
|
|
|
338
352
|
|
|
339
353
|
You do not need to create the mapping file by hand. When a preview cannot resolve a ticket like `SCB-213`, the assistant calls `list_projects`. After you approve, it calls `map_project` with `tickets`, an optional `keywords`, and `confirm: true`.
|
|
340
354
|
|
|
355
|
+
For commit drafts, a mapping can also list git repos.
|
|
356
|
+
- When `draft_from_commits` reports `unmappedRepos`, the assistant can call `map_project` with `repos` (absolute paths) and `confirm: true`.
|
|
357
|
+
- At least one ticket prefix or repo is required.
|
|
358
|
+
- A subfolder is stored as the root of its repo.
|
|
359
|
+
- Paths are compared without trailing separators, and case-insensitively on Windows.
|
|
360
|
+
- In the terminal UI, `/map --repo [path] <projectMemberId>` does the same. The path defaults to the current folder.
|
|
361
|
+
|
|
341
362
|
Mappings are stored in one of two places:
|
|
342
363
|
- **User scope (default):** `~/.logwork-cli/.logwork.json`.
|
|
343
364
|
- **Project scope:** `.logwork.json` in the current repo. A repo that still has `.logwork-helper.json` keeps using that file until a `.logwork.json` exists.
|
|
@@ -349,7 +370,8 @@ Mappings are stored in one of two places:
|
|
|
349
370
|
"projectName": "2621A-SIT-HTML BUILDER-PRJ",
|
|
350
371
|
"projectMemberId": 5234,
|
|
351
372
|
"tickets": ["SCB"],
|
|
352
|
-
"keywords": ["question bank", "programme", "cluster"]
|
|
373
|
+
"keywords": ["question bank", "programme", "cluster"],
|
|
374
|
+
"repos": ["/Users/me/code/html-builder"]
|
|
353
375
|
}
|
|
354
376
|
]
|
|
355
377
|
}
|
|
@@ -366,3 +388,23 @@ Only allow this when the matched project is correct. Apply with:
|
|
|
366
388
|
```json
|
|
367
389
|
{ "batchId": "<from preview>", "confirm": true, "allowUnbooked": true }
|
|
368
390
|
```
|
|
391
|
+
|
|
392
|
+
### Possible Duplicates
|
|
393
|
+
|
|
394
|
+
Before showing a `log_work` preview, the server reads your RO entries for each date in the batch (`GET /logwork/day`).
|
|
395
|
+
|
|
396
|
+
An entry is flagged `possibleDuplicate` in two cases:
|
|
397
|
+
- **It matches an existing RO entry** on the same date: the same worklog task (by id, or by name when RO has no id), the same hours and the same description. The description compare ignores extra spaces and letter case.
|
|
398
|
+
- **It repeats an earlier line** of the same batch.
|
|
399
|
+
|
|
400
|
+
The summary shows a `WARNING possible duplicate` line for each one.
|
|
401
|
+
|
|
402
|
+
Apply refuses the whole batch while duplicates remain, and nothing is written. The preview stays cached, so after the user confirms the lines are really new work, call again with the same `batchId`:
|
|
403
|
+
|
|
404
|
+
```json
|
|
405
|
+
{ "batchId": "<from preview>", "confirm": true, "allowDuplicates": true }
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Usually the better fix is to drop the duplicated lines and preview again.
|
|
409
|
+
|
|
410
|
+
If RO cannot be read for a date (other than a login problem), the preview is not blocked. Its summary notes that the duplicate check was skipped for that date. In the terminal UI, `/apply` asks a separate question before submitting possible duplicates; the default answer is no.
|
package/docs/operations-guide.md
CHANGED
|
@@ -19,6 +19,8 @@ npm install -g logwork-cli
|
|
|
19
19
|
logwork setup
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
Upgrading from `logwork-helper`: run `npm uninstall -g logwork-helper` first. Both packages provide a `logwork` command, and npm refuses to overwrite it (`EEXIST`). If `~/.logwork-helper` was already deleted, the uninstall leaves broken links behind; remove them with `P="$(npm prefix -g)"; rm -f "$P/bin/logwork" "$P"/bin/logwork-helper* "$P/lib/node_modules/logwork-helper"`, or run `npx logwork-cli@latest setup`, which removes them.
|
|
23
|
+
|
|
22
24
|
Authenticate each target from Terminal:
|
|
23
25
|
|
|
24
26
|
```bash
|
|
@@ -47,7 +49,7 @@ Resource Optimiser credentials and Jira PATs are never accepted by MCP tools. Th
|
|
|
47
49
|
| Delete RO entry | `logwork delete` | - | `delete_entry` | Apply call only |
|
|
48
50
|
| Log Jira | - | `/logwork jira` | `log_jira_work` | Apply call only |
|
|
49
51
|
| Log Both | - | `/logwork both` | `log_work` + `log_jira_work` separately | Apply calls only |
|
|
50
|
-
| Project mapping | - | `/projects`, `/map` | `list_projects`, `map_project` | Local config only |
|
|
52
|
+
| Project mapping | - | `/projects`, `/map`, `/map --repo` | `list_projects`, `map_project` | Local config only |
|
|
51
53
|
| RO worklog task catalog | - | - | `list_tasks` | No |
|
|
52
54
|
| Reconcile RO/Jira | `logwork reconcile` | `/reconcile` | `reconcile` | No |
|
|
53
55
|
| Apply history | `logwork history` | `/history` | `get_history` | No |
|
|
@@ -186,6 +188,8 @@ MCP uses:
|
|
|
186
188
|
|
|
187
189
|
Every entry must contain exactly one Jira issue key. RO is not written by this flow.
|
|
188
190
|
|
|
191
|
+
In dry run (`LOGWORK_DRY_RUN=1`), the Jira apply still reads Jira for the duplicate check but sends no worklog. It returns `status: "dry_run"` with the request each entry would send, and the summary starts with `DRY RUN`. Apply history records it as `dry_run`, not as submitted.
|
|
192
|
+
|
|
189
193
|
### Both
|
|
190
194
|
|
|
191
195
|
Use the combined terminal flow when the same date/tasks should be sent to both systems:
|
|
@@ -269,6 +273,8 @@ Submitted 2 entries, 3h total.
|
|
|
269
273
|
|
|
270
274
|
If a valid membership is not booked for the selected date, preview prefixes the entry with `UNBOOKED`. Applying it requires a second decision through `allowUnbooked: true`. An unresolved project always blocks apply.
|
|
271
275
|
|
|
276
|
+
Preview also reads the RO entries of each date. An entry with the same worklog task, hours and description as an existing entry (or an earlier line of the batch) gets a `WARNING possible duplicate` line. Applying it requires a separate decision: `allowDuplicates: true`, or a yes to the duplicate question in the terminal UI. Without it, nothing is written. If the day cannot be read, the preview notes that the duplicate check was skipped and does not block.
|
|
277
|
+
|
|
272
278
|
## Jira Processing Example
|
|
273
279
|
|
|
274
280
|
For each entry, Jira preview:
|
|
@@ -431,6 +437,8 @@ Other operations:
|
|
|
431
437
|
|
|
432
438
|
```bash
|
|
433
439
|
logwork reminder enable --time 18:00 --target ro
|
|
440
|
+
logwork reminder enable --repo ~/code/app --repo ~/code/api
|
|
441
|
+
logwork reminder enable --no-repos
|
|
434
442
|
logwork reminder status
|
|
435
443
|
logwork reminder test
|
|
436
444
|
logwork reminder disable
|
|
@@ -445,9 +453,19 @@ Behavior:
|
|
|
445
453
|
- Missing auth produces an actionable notification without exposing API details.
|
|
446
454
|
- macOS uses a user `launchd` agent (`sg.vinova.logwork-cli.reminder`).
|
|
447
455
|
- Windows uses an interactive user Task Scheduler task (`Logwork Reminder`).
|
|
448
|
-
- The scheduler runs
|
|
456
|
+
- The scheduler runs `<absolute node path> ~/.logwork-cli/bin/logwork.mjs reminder run --home ~/.logwork-cli`, using the Node that ran `reminder enable` or `logwork setup`.
|
|
457
|
+
- If that Node is removed or replaced (`nvm uninstall`, `brew upgrade node`), the schedule still looks installed but cannot run. `logwork reminder status` and `logwork doctor` warn about it; run `logwork reminder enable` or `logwork setup` to re-register it with the current Node.
|
|
449
458
|
- Enabling or disabling the reminder also removes a leftover `logwork-helper` schedule.
|
|
450
459
|
|
|
460
|
+
Commit evidence:
|
|
461
|
+
|
|
462
|
+
- `--repo <path>` (repeatable) saves git repos in `reminder.json` as absolute paths. MCP: `reminder` with `action: "enable"` and `repos`. `--repo` or `repos` replaces the saved list, `--no-repos` or `repos: []` clears it, and leaving them out keeps it.
|
|
463
|
+
- Repos listed in a project mapping's `repos` field are added.
|
|
464
|
+
- When RO hours are missing, the reminder reads your commits today in those repos (read-only, the same reader as `logwork commits`, without another RO query) and adds `Hôm nay: <n> commit trong <k> repo`. The action line then points to `logwork commits today`.
|
|
465
|
+
- Run outside a git repo, `logwork commits today` falls back to the reminder repos.
|
|
466
|
+
- With no repos, no commits today, or any error reading them (missing repo, git not on the scheduler's `PATH`, a 20-second timeout), the plain message is shown.
|
|
467
|
+
- The notification stays a plain OS notification on both platforms (macOS `display notification`, a Windows toast). It has no buttons; open a terminal and run the command shown.
|
|
468
|
+
|
|
451
469
|
Example Both notification:
|
|
452
470
|
|
|
453
471
|
```text
|
|
@@ -458,6 +476,19 @@ Jira: chưa có worklog hôm nay
|
|
|
458
476
|
Mở Terminal và chạy: logwork
|
|
459
477
|
```
|
|
460
478
|
|
|
479
|
+
With reminder repos and commits today:
|
|
480
|
+
|
|
481
|
+
```text
|
|
482
|
+
Logwork hôm nay chưa hoàn tất
|
|
483
|
+
RO: 4/8h, còn thiếu 4h
|
|
484
|
+
Jira: chưa có worklog hôm nay
|
|
485
|
+
Hôm nay: 5 commit trong 2 repo
|
|
486
|
+
|
|
487
|
+
Mở Terminal và chạy: logwork commits today (bản nháp từ commit)
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
`logwork reminder test` sends a sample in this format.
|
|
491
|
+
|
|
461
492
|
The notification text is localized in Vietnamese. The OS schedule stores only fixed Node/script paths and reads credentials from the OS credential store when it runs.
|
|
462
493
|
|
|
463
494
|
## End-User Updates
|
|
@@ -535,6 +566,7 @@ The release doctor checks package version consistency, self-dependencies, clean/
|
|
|
535
566
|
| Apply history | `.logwork-cli/apply-ledger.json` | No |
|
|
536
567
|
| Update cache | `.logwork-cli/update-state.json` | No |
|
|
537
568
|
| Reminder config/state | `.logwork-cli/reminder.json`, `.logwork-cli/reminder-state.json` | No |
|
|
569
|
+
| Runtime Node record | `.logwork-cli/runtime.json` (Node path and version from `logwork setup`) | No |
|
|
538
570
|
| RO/Jira sessions | OS credential store, service `logwork-cli` | Yes |
|
|
539
571
|
|
|
540
572
|
The helper home is `~/.logwork-cli` on macOS and `%USERPROFILE%\.logwork-cli` on Windows. `LOGWORK_HOME` overrides it; the older `LOGWORK_HELPER_HOME` is still honored.
|
|
@@ -549,7 +581,9 @@ Migration from `logwork-helper` happens on the first run, unless `LOGWORK_HOME`
|
|
|
549
581
|
Run the cross-platform suite:
|
|
550
582
|
|
|
551
583
|
```bash
|
|
584
|
+
npm run lint
|
|
552
585
|
npm test
|
|
586
|
+
npm run test:coverage # same suite with coverage thresholds; CI runs it on macOS
|
|
553
587
|
git diff --check
|
|
554
588
|
npm pack --dry-run
|
|
555
589
|
```
|
|
@@ -582,6 +616,7 @@ Recommended end-user checks after installation:
|
|
|
582
616
|
- Partial Both result: inspect `/history both`, then preview only the missing target.
|
|
583
617
|
- npm `E401`: refresh npm login with `npm login --registry=https://registry.npmjs.org/`.
|
|
584
618
|
- Reminder config/scheduler mismatch: rerun `reminder enable` or `reminder disable`.
|
|
619
|
+
- Reminder or MCP Node path gone after a Node upgrade: run `logwork setup` (refreshes `runtime.json`, the reminder and the printed MCP configs), then paste the new MCP config.
|
|
585
620
|
- MCP after update: restart or reload the MCP connection.
|
|
586
621
|
|
|
587
622
|
For client configuration details, see [MCP setup](mcp-setup.md). For security boundaries and stored-data details, see [Security and auth](security.md). For copy-ready task recipes, see [Quick recipes](recipes.md).
|
package/docs/recipes.md
CHANGED
|
@@ -39,6 +39,27 @@ List the Resource Optimiser worklog tasks for my booked project today, then log
|
|
|
39
39
|
|
|
40
40
|
The assistant uses `list_projects` and `list_tasks`, writes `+2 <task> | <description>`, and previews. If you write lines without `|`, the assistant can pass `defaultWorklogTask` (for example `Feature Development`) so each line becomes the description.
|
|
41
41
|
|
|
42
|
+
## Log From Commits Into The Right Projects
|
|
43
|
+
|
|
44
|
+
Tell logwork which RO project each repo belongs to, once:
|
|
45
|
+
|
|
46
|
+
```text
|
|
47
|
+
logwork
|
|
48
|
+
/map --repo ~/code/portal 5234
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
or ask your assistant: `Map the repo /Users/me/code/portal to my Portal project` (it calls `list_projects`, then `map_project` with `repos` after you approve).
|
|
52
|
+
|
|
53
|
+
Then `logwork commits yesterday` (or the assistant's `draft_from_commits`) puts that repo's commits on project 5234. The hours come from what is still free on that project: booked minus logged. If the project is already full, its lines get 0h and the summary says so. Repos without a mapping fall back to ticket/keyword mappings and that day's bookings, and are listed at the end so you can map them.
|
|
54
|
+
|
|
55
|
+
The assistant passes the draft's `projectOverrides` to `log_work` with the same text, so each line is logged to the project the draft chose.
|
|
56
|
+
|
|
57
|
+
## Handle A Possible Duplicate RO Entry
|
|
58
|
+
|
|
59
|
+
If a `log_work` preview shows `WARNING possible duplicate`, RO already has an entry on that date with the same worklog task, hours and description, or the batch repeats a line. This often happens after an agent re-ran or a batch failed partway.
|
|
60
|
+
|
|
61
|
+
Check `/query` (or `get_logwork`) first. If the work is already logged, drop the line and preview again. Only if it really is separate work, approve it explicitly: in the terminal UI answer yes to the duplicate question, or let the assistant confirm with `allowDuplicates: true`. Without that, apply writes nothing.
|
|
62
|
+
|
|
42
63
|
## Edit Existing Resource Optimiser Logwork
|
|
43
64
|
|
|
44
65
|
Find the RO logwork ID with `logwork query this-week`, `/query this-week`, or by asking the assistant to call `get_logwork`. Then use Terminal:
|
|
@@ -214,6 +235,12 @@ RO-only reminder at 18:00:
|
|
|
214
235
|
logwork reminder enable --time 18:00 --target ro
|
|
215
236
|
```
|
|
216
237
|
|
|
238
|
+
Add commit evidence from your repos (shown when RO hours are missing):
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
logwork reminder enable --repo ~/code/app --repo ~/code/api
|
|
242
|
+
```
|
|
243
|
+
|
|
217
244
|
Send a sample notification and inspect scheduler status:
|
|
218
245
|
|
|
219
246
|
```bash
|
package/docs/security.md
CHANGED
|
@@ -111,7 +111,8 @@ The runtime and local state live in `~/.logwork-cli` (`%USERPROFILE%\.logwork-cl
|
|
|
111
111
|
| `manual-drafts.json` | Terminal UI drafts. |
|
|
112
112
|
| `apply-ledger.json` | Sanitized apply history. |
|
|
113
113
|
| `update-state.json` | Update check cache: package name, latest SemVer, and check timestamp. Expires after 24 hours and contains no npm credentials. |
|
|
114
|
-
| `reminder.json`, `reminder-state.json` | Reminder config: enabled state, local time, target and
|
|
114
|
+
| `reminder.json`, `reminder-state.json` | Reminder config: enabled state, local time, target, weekdays and optional git repo paths for the commit summary. Last-run state: sanitized timestamps and status summaries. |
|
|
115
|
+
| `runtime.json` | The Node path and version `logwork setup` ran with, its install time and the package version. |
|
|
115
116
|
| `diagnostics/` | Sanitized diagnostics reports from `logwork doctor`. |
|
|
116
117
|
|
|
117
118
|
The apply ledger stores the target, flow target, batch ID, timestamp, workspace path, entry metadata, and submitted/failed/blocked status. It does not store credentials, cookies, or raw API responses, and token-like error text is redacted before writing.
|
|
@@ -145,6 +146,7 @@ The macOS launch agent and Windows scheduled task contain only fixed Node and sc
|
|
|
145
146
|
- `reminder` with `action: "status"` is read-only.
|
|
146
147
|
- `enable`, `disable` and `test` require `confirm: true`. They never accept arbitrary scheduler commands, paths, JQL, or credentials.
|
|
147
148
|
- The background reminder reads RO and Jira sessions from the OS credential store only when it runs. Notification and state text are sanitized and never include raw API responses.
|
|
149
|
+
- With reminder repos, the background reminder runs read-only `git log` in those repos and shows only commit and repo counts. Commit subjects and paths are not put in the notification or the state file.
|
|
148
150
|
|
|
149
151
|
**Login and diagnostics**
|
|
150
152
|
- `login` never accepts credentials. It only opens or explains a terminal login.
|