logwork-cli 1.2.0 → 1.3.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.
@@ -1,21 +1,26 @@
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, win32 } from 'node:path';
3
+ import { delimiter, dirname, join, relative, resolve } 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
7
  import { npmInvocation } from '../lib/npm-command.mjs';
8
8
  import { writeRuntimeInfo } from '../lib/node-runtime.mjs';
9
9
  import { RUNTIME_INFO_FILE_NAME, helperHome } from '../lib/paths.mjs';
10
+ import { mcpArgs, resolveLaunchCommand, writeLauncher } from '../lib/launcher.mjs';
10
11
  import {
12
+ MCP_LAUNCHER_NOTE,
11
13
  MCP_NODE_PATH_NOTE,
12
14
  MCP_SERVER_NAME,
15
+ formatClaudeMcpAddCommand,
13
16
  formatCodexToml,
14
17
  stdioMcpServersConfig,
15
18
  vscodeMcpConfig
16
19
  } from '../lib/manual-mcp.mjs';
20
+ import { createMcpInstallContext, describeMcpPlan, planMcpInstall } from '../lib/mcp-install.mjs';
17
21
  import { loadReminderConfig } from '../lib/reminder-config.mjs';
18
22
  import { installReminderScheduler } from '../lib/reminder-scheduler.mjs';
23
+ import { formatMcpInstallResults, isInteractiveTerminal, runMcpInstall } from './mcp.mjs';
19
24
 
20
25
  // Package name before the rename. Its global install provides a `logwork` bin
21
26
  // that would block `npm link` of logwork-cli, so setup removes it first.
@@ -28,8 +33,9 @@ const EXCLUDED_DIRS = new Set([
28
33
  ]);
29
34
 
30
35
  // `logwork setup`: copy this package into ~/.logwork-cli, install its
31
- // dependencies, link the `logwork` command, refresh a migrated reminder, and
32
- // print the MCP configs.
36
+ // dependencies, link the `logwork` command, write the launcher, refresh a
37
+ // migrated reminder, and add or update the `logwork` entry in the MCP clients
38
+ // found on this machine.
33
39
  export async function run(argv = []) {
34
40
  if (argv.includes('--help') || argv.includes('-h')) {
35
41
  console.log(formatSetupHelp());
@@ -44,16 +50,19 @@ export async function run(argv = []) {
44
50
 
45
51
  export function formatSetupHelp() {
46
52
  return `Usage:
47
- logwork setup [--login|--no-login]
53
+ logwork setup [--yes] [--no-mcp-install] [--login|--no-login]
48
54
 
49
- Installs the runtime to ~/.logwork-cli, links the \`logwork\` command, and prints
50
- MCP configs for Cursor, Antigravity, Claude Code, Codex and VS Code.
51
- Settings, mappings and the login session from an older logwork-helper install
52
- are carried over automatically.
55
+ Installs the runtime to ~/.logwork-cli, links the \`logwork\` command, and adds
56
+ the "logwork" MCP server to the clients it finds (Claude Code, Cursor, Codex,
57
+ VS Code, Google Antigravity), asking once per client. Each edited config is
58
+ backed up first. Settings, mappings and the login session from an older
59
+ logwork-helper install are carried over automatically.
53
60
 
54
61
  Options:
55
- --login Log in to Resource Optimiser after installing
56
- --no-login Do not ask to log in`;
62
+ --yes, -y Update every detected MCP client without asking
63
+ --no-mcp-install Do not touch MCP client configs
64
+ --login Log in to Resource Optimiser after installing
65
+ --no-login Do not ask to log in`;
57
66
  }
58
67
 
59
68
  export async function installUserRuntime({
@@ -67,7 +76,9 @@ export async function installUserRuntime({
67
76
  linkFn = linkGlobalBinaries,
68
77
  authFn = maybeRunAuthLogin,
69
78
  runtimeInfoFn = recordRuntimeInfo,
79
+ launcherFn = writeRuntimeLauncher,
70
80
  reminderFn = refreshReminderSchedule,
81
+ mcpInstallFn = installMcpClients,
71
82
  printFn = printMcpInstructions
72
83
  }) {
73
84
  await mkdirFn(targetDir, { recursive: true });
@@ -79,21 +90,61 @@ export async function installUserRuntime({
79
90
  const linkResult = await linkFn(targetDir);
80
91
  const authResult = await authFn(targetDir, options);
81
92
  await runtimeInfoFn(targetDir);
93
+ await launcherFn(targetDir);
82
94
  const reminderResult = await reminderFn(targetDir);
83
- await printFn(targetDir, authResult, linkResult, cleanupResult, reminderResult);
95
+ const mcpResult = await mcpInstallFn(targetDir, options);
96
+ await printFn(targetDir, authResult, linkResult, cleanupResult, reminderResult, mcpResult);
84
97
 
85
98
  return {
86
99
  targetDir,
87
100
  authResult,
88
101
  linkResult,
89
102
  cleanupResult,
90
- reminderResult
103
+ reminderResult,
104
+ mcpResult
91
105
  };
92
106
  }
93
107
 
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.
108
+ // macOS/Linux: <home>/bin/logwork-launch, which MCP configs and the reminder
109
+ // run. It must exist before the reminder is re-registered.
110
+ export async function writeRuntimeLauncher(targetDir, {
111
+ nodePath = process.execPath,
112
+ platform = process.platform,
113
+ writeFn = writeLauncher
114
+ } = {}) {
115
+ return writeFn({ home: targetDir, nodePath, platform });
116
+ }
117
+
118
+ // Adds or updates the `logwork` entry in the detected MCP clients:
119
+ // - --no-mcp-install: skipped;
120
+ // - --yes: every detected client, without asking;
121
+ // - a terminal: one yes/no per client that needs a change;
122
+ // - otherwise (npm exec from `logwork update`, CI): only the plan.
123
+ export async function installMcpClients(targetDir, options = {}, {
124
+ interactive = isInteractiveTerminal(),
125
+ ctx = createMcpInstallContext({ home: targetDir }),
126
+ install = runMcpInstall,
127
+ plan = planMcpInstall
128
+ } = {}) {
129
+ if (options.mcpInstall === false) {
130
+ return { mode: 'skipped', launch: ctx.launch };
131
+ }
132
+ try {
133
+ if (options.yes || interactive) {
134
+ const result = await install({ ctx, yes: Boolean(options.yes), interactive });
135
+ return { mode: 'installed', launch: ctx.launch, results: result.results };
136
+ }
137
+ const plans = await plan(ctx);
138
+ return { mode: 'planned', launch: ctx.launch, plans, lines: plans.flatMap((item) => describeMcpPlan(item, ctx)) };
139
+ } catch (error) {
140
+ return { mode: 'error', launch: ctx.launch, error: error.message };
141
+ }
142
+ }
143
+
144
+ // Records the node that ran setup. The macOS/Linux launcher tries it first;
145
+ // on Windows the MCP configs and the reminder run it (or the nvm-windows
146
+ // symlink). doctor and reminder status compare it with the current Node.
147
+ // Best effort: a failed write must not fail the install.
97
148
  export async function recordRuntimeInfo(targetDir, {
98
149
  nodePath = process.execPath,
99
150
  nodeVersion = process.version,
@@ -117,6 +168,7 @@ export async function recordRuntimeInfo(targetDir, {
117
168
  // at a Node that nvm or Homebrew removed. Re-register it so the OS scheduler
118
169
  // runs this runtime with the current node (and the legacy entry is removed by
119
170
  // installReminderScheduler).
171
+ // The schedule runs the launcher (macOS) or a stable node.exe (Windows).
120
172
  export async function refreshReminderSchedule(targetDir, {
121
173
  nodePath = process.execPath,
122
174
  loadConfig = loadReminderConfig,
@@ -437,6 +489,8 @@ export async function findCommandOnPath(commandName, {
437
489
 
438
490
  export function parseSetupUserArgs(args = []) {
439
491
  let loginMode = 'prompt';
492
+ let yes = false;
493
+ let mcpInstall = true;
440
494
 
441
495
  for (const arg of args) {
442
496
  if (arg === '--login') {
@@ -447,10 +501,18 @@ export function parseSetupUserArgs(args = []) {
447
501
  loginMode = 'skip';
448
502
  continue;
449
503
  }
504
+ if (arg === '--yes' || arg === '-y') {
505
+ yes = true;
506
+ continue;
507
+ }
508
+ if (arg === '--no-mcp-install') {
509
+ mcpInstall = false;
510
+ continue;
511
+ }
450
512
  throw new Error(`Unknown setup option: ${arg}`);
451
513
  }
452
514
 
453
- return { loginMode };
515
+ return { loginMode, yes, mcpInstall };
454
516
  }
455
517
 
456
518
  async function maybeRunAuthLogin(targetDir, { loginMode }) {
@@ -521,13 +583,14 @@ function runAuthLogin(targetDir) {
521
583
  });
522
584
  }
523
585
 
524
- function printMcpInstructions(targetDir, authResult = null, linkResult = null, cleanupResult = null, reminderResult = null) {
586
+ function printMcpInstructions(targetDir, authResult = null, linkResult = null, cleanupResult = null, reminderResult = null, mcpResult = null) {
525
587
  console.log(formatSetupUserInstructions({
526
588
  targetDir,
527
589
  authResult,
528
590
  linkResult,
529
591
  cleanupResult,
530
- reminderResult
592
+ reminderResult,
593
+ mcpResult
531
594
  }));
532
595
  }
533
596
 
@@ -537,13 +600,14 @@ export function formatSetupUserInstructions({
537
600
  linkResult = null,
538
601
  cleanupResult = null,
539
602
  reminderResult = null,
603
+ mcpResult = null,
540
604
  platform = process.platform,
541
- nodePath = process.execPath
605
+ nodePath = process.execPath,
606
+ env = process.env,
607
+ launch = mcpResult?.launch || resolveLaunchCommand({ platform, home: targetDir, execPath: nodePath, env })
542
608
  } = {}) {
543
- const entryPath = platform === 'win32'
544
- ? win32.resolve(targetDir, 'bin', 'logwork.mjs')
545
- : resolve(targetDir, 'bin', 'logwork.mjs');
546
609
  const storeLabel = credentialStoreLabel({ platform });
610
+ const mcp = formatMcpSection({ mcpResult, launch, platform });
547
611
  const sections = [
548
612
  `logwork-cli installed to ${targetDir}.`,
549
613
  '',
@@ -559,28 +623,18 @@ export function formatSetupUserInstructions({
559
623
  formatTerminalCommandInstructions(linkResult),
560
624
  '',
561
625
  'Next steps',
562
- ...formatNextSteps(authResult),
563
- '',
564
- `MCP config (server name "${MCP_SERVER_NAME}", runs: ${nodePath} ${entryPath} mcp)`,
565
- MCP_NODE_PATH_NOTE,
626
+ ...formatNextSteps(authResult, mcp.manual),
566
627
  '',
567
- 'Cursor / Antigravity / Claude Code (.mcp.json):',
568
- JSON.stringify(stdioMcpServersConfig(entryPath, { nodePath }), null, 2),
569
- '',
570
- 'Codex config.toml:',
571
- formatCodexToml(entryPath, { nodePath }),
572
- '',
573
- 'GitHub Copilot / VS Code mcp.json:',
574
- JSON.stringify(vscodeMcpConfig(entryPath, { nodePath }), null, 2),
628
+ ...mcp.lines,
575
629
  '',
576
630
  'AI agent skill (log work from your commits)',
577
631
  '- Claude Code / Codex: run `logwork skill install`.',
578
632
  '- Other MCP clients: use the MCP prompt "log_from_commits".',
579
633
  '',
580
634
  'Verify',
581
- '- Restart or reload your IDE MCP tools after pasting config.',
635
+ '- Restart or reload your IDE MCP tools after a config change.',
582
636
  '- In the IDE, ask: Check my logwork for this week.',
583
- '- An old "logwork-helper" MCP entry can be deleted; it points at the old runtime.',
637
+ '- `logwork mcp status` shows which clients have a current "logwork" entry.',
584
638
  '',
585
639
  'Safety',
586
640
  '- Do not paste passwords, 2FA codes, Bearer tokens, cookies, or raw curl auth logs into AI chat.',
@@ -591,6 +645,70 @@ export function formatSetupUserInstructions({
591
645
  return sections.join('\n');
592
646
  }
593
647
 
648
+ // The MCP part of the setup summary. `manual` is true when the user still has
649
+ // to paste a config somewhere.
650
+ function formatMcpSection({ mcpResult, launch, platform }) {
651
+ const header = `MCP server "${MCP_SERVER_NAME}" runs: ${[launch.command, ...mcpArgs(launch)].join(' ')}`;
652
+ if (mcpResult?.mode === 'installed' && mcpResult.results?.length) {
653
+ const failed = mcpResult.results.some((result) => result.status === 'failed');
654
+ return {
655
+ manual: failed,
656
+ lines: [header, 'MCP clients', ...formatMcpInstallResults(mcpResult.results)]
657
+ };
658
+ }
659
+ if (mcpResult?.mode === 'planned') {
660
+ const pending = (mcpResult.plans || []).some((plan) => plan.action === 'add' || plan.action === 'update');
661
+ if (mcpResult.plans?.length) {
662
+ return {
663
+ manual: false,
664
+ lines: [
665
+ header,
666
+ 'MCP clients (not changed: this is not an interactive terminal)',
667
+ ...(mcpResult.lines || []),
668
+ ...(pending ? ['To apply: run `logwork mcp install` (asks per client) or `logwork mcp install --yes`.'] : [])
669
+ ]
670
+ };
671
+ }
672
+ }
673
+ if (mcpResult?.mode === 'skipped') {
674
+ return {
675
+ manual: false,
676
+ lines: [header, 'MCP clients: not changed (--no-mcp-install). Run `logwork mcp install` to add or update them, or `logwork mcp status` to check.']
677
+ };
678
+ }
679
+ // No client found, an error, or an older caller: print every snippet.
680
+ const lines = [header];
681
+ if (mcpResult?.mode === 'error') {
682
+ lines.push(`MCP clients: could not update automatically (${redactText(mcpResult.error || 'unknown error')}).`);
683
+ } else if (mcpResult) {
684
+ lines.push('MCP clients: none found (Claude Code, Cursor, Codex, VS Code, Google Antigravity).');
685
+ }
686
+ lines.push(...formatManualMcpSnippets(launch, { platform }));
687
+ return { manual: true, lines };
688
+ }
689
+
690
+ export function formatManualMcpSnippets(launch, { platform = process.platform } = {}) {
691
+ const note = launch.stable ? (launch.kind === 'launcher' ? MCP_LAUNCHER_NOTE : '') : MCP_NODE_PATH_NOTE;
692
+ return [
693
+ 'Manual MCP config (paste into your client):',
694
+ ...(note ? [note] : []),
695
+ '',
696
+ 'Cursor / Antigravity / Claude Code (.mcp.json):',
697
+ JSON.stringify(stdioMcpServersConfig(launch), null, 2),
698
+ '',
699
+ 'Claude Code CLI:',
700
+ formatClaudeMcpAddCommand(launch, { platform }),
701
+ '',
702
+ 'Codex config.toml:',
703
+ formatCodexToml(launch),
704
+ '',
705
+ 'GitHub Copilot / VS Code mcp.json:',
706
+ JSON.stringify(vscodeMcpConfig(launch), null, 2),
707
+ '',
708
+ 'An old "logwork-helper" MCP entry can be deleted; it points at the old runtime.'
709
+ ];
710
+ }
711
+
594
712
  function formatAuthStatus(authResult) {
595
713
  if (authResult?.ok) {
596
714
  return 'completed';
@@ -601,7 +719,7 @@ function formatAuthStatus(authResult) {
601
719
  return 'not run';
602
720
  }
603
721
 
604
- function formatNextSteps(authResult) {
722
+ function formatNextSteps(authResult, manualMcp = true) {
605
723
  const steps = [];
606
724
  let index = 1;
607
725
  if (!authResult?.ok) {
@@ -609,9 +727,12 @@ function formatNextSteps(authResult) {
609
727
  steps.push(' logwork login');
610
728
  index += 1;
611
729
  }
612
- steps.push(`${index}. Paste one MCP config below into your IDE.`);
613
- steps.push(`${index + 1}. Restart or reload your IDE MCP tools.`);
614
- steps.push(`${index + 2}. Verify with: Check my logwork for this week.`);
730
+ if (manualMcp) {
731
+ steps.push(`${index}. Paste the MCP config below into your IDE (or run \`logwork mcp install\`).`);
732
+ index += 1;
733
+ }
734
+ steps.push(`${index}. Restart or reload your IDE MCP tools.`);
735
+ steps.push(`${index + 1}. Verify with: Check my logwork for this week.`);
615
736
  return steps;
616
737
  }
617
738
 
package/docs/advanced.md CHANGED
@@ -78,9 +78,9 @@ To install and start auth immediately:
78
78
  logwork setup --login
79
79
  ```
80
80
 
81
- Use `--no-login` when installing in scripts or CI.
81
+ Use `--no-login` when installing in scripts or CI. Add `--no-mcp-install` to leave MCP client configs alone, or `--yes` to update every detected client without asking. Without a terminal and without `--yes`, setup only prints what `logwork mcp install` would change.
82
82
 
83
- `logwork setup` copies the package into `~/.logwork-cli`, runs `npm ci --omit=dev` there, removes a globally installed `logwork-helper` package (its `logwork` bin would block linking), runs `npm link`, re-registers an enabled reminder, and prints MCP configs.
83
+ `logwork setup` copies the package into `~/.logwork-cli`, runs `npm ci --omit=dev` there, removes a globally installed `logwork-helper` package (its `logwork` bin would block linking), runs `npm link`, writes `runtime.json` and the launcher (`bin/logwork-launch`, macOS), re-registers an enabled reminder, and adds or updates the `logwork` entry in the detected MCP clients ([details](mcp-setup.md#automatic-setup)). It prints the manual snippets only for clients it could not update, or when it found none.
84
84
 
85
85
  ## Update
86
86
 
@@ -91,7 +91,7 @@ logwork update install # interactive confirmation
91
91
  logwork update install --version 0.3.1 --yes
92
92
  ```
93
93
 
94
- The installer accepts only the version currently published under npm's `latest` tag. It runs `npm exec --package=logwork-cli@<version> -- logwork setup --no-login` with a fixed argument list. Then it verifies the copied `package.json` version in `~/.logwork-cli`.
94
+ The installer accepts only the version currently published under npm's `latest` tag. It runs `npm exec --package=logwork-cli@<version> -- logwork setup --no-login` with a fixed argument list. Then it verifies the copied `package.json` version in `~/.logwork-cli`. That setup runs without a terminal, so it does not edit MCP configs; they keep working because they point at the launcher (macOS) or a stable `node.exe` (Windows). `logwork mcp status` shows whether anything needs `logwork mcp install`.
95
95
 
96
96
  Project mappings, drafts, apply history, update cache, and OS credential-store records are preserved.
97
97
 
@@ -222,7 +222,8 @@ Useful commands inside the session:
222
222
  /projects
223
223
  /projects 5234
224
224
  /map SCB 5234
225
- /map --repo . 5234
225
+ /map --repo
226
+ /map --repo ~/code/fcs GKE Fair Chem
226
227
  /history
227
228
  /diagnostics
228
229
  ```
@@ -272,8 +273,8 @@ Inside `logwork`, use `/history`, `/history ro`, `/history jira`, or `/history b
272
273
 
273
274
  ## Troubleshooting
274
275
 
275
- - **IDE does not show tools:** restart or reload the MCP client. Check that the config runs `node ~/.logwork-cli/bin/logwork.mjs mcp` (on Windows, `%USERPROFILE%\.logwork-cli\bin\logwork.mjs`) and that the server name is `logwork`.
276
- - **Tools show old names such as `preview_logwork_batch`:** the IDE is still connected to the old `logwork-helper` entry. Replace that entry with the config printed by `logwork setup`; the [tool name table](mcp-setup.md#tool-name-changes-from-logwork-helper) lists the renames.
276
+ - **IDE does not show tools:** run `logwork mcp status`. If the client's entry is missing or stale, run `logwork mcp install`, then restart or reload the MCP client. The entry should run `~/.logwork-cli/bin/logwork-launch mcp` (on Windows, `node.exe %USERPROFILE%\.logwork-cli\bin\logwork.mjs mcp`) under the server name `logwork`. `logwork doctor` also checks that the launcher finds a usable Node.
277
+ - **Tools show old names such as `preview_logwork_batch`:** the IDE is still connected to the old `logwork-helper` entry. `logwork mcp install` removes it; the [tool name table](mcp-setup.md#tool-name-changes-from-logwork-helper) lists the renames.
277
278
  - **`logwork: command not found` after `logwork setup`:** open a new terminal first. If it still fails, run `npm install -g logwork-cli`, or make sure your npm global bin directory is on `PATH`.
278
279
  - **`npm link` fails with `EEXIST` on `logwork`:** an old global `logwork-helper` package still owns the bin. Run `npm rm -g logwork-helper`, then `logwork setup` again.
279
280
  - **Not authenticated to Resource Optimiser:** run `logwork login`, or ask the assistant to call `login`. Enter secrets only in the terminal.
package/docs/mcp-setup.md CHANGED
@@ -1,81 +1,130 @@
1
1
  # MCP Setup
2
2
 
3
- `logwork setup` prints copy-ready configs that use your real path. You can also open the terminal UI with `logwork` and type `/mcp`, or `/mcp <client>` for a single client.
3
+ Run `logwork setup` and answer yes for each client. That is the whole setup: you do not edit MCP JSON or TOML yourself, and you do not need to know where Node is installed.
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 `<absolute path to node> <home>/.logwork-cli/bin/logwork.mjs mcp` under the server name `logwork`.
7
+ ## Automatic Setup
8
8
 
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.
9
+ `logwork setup` (and `logwork mcp install` at any time later) looks for these clients and adds or updates the `logwork` server in each one it finds. A client counts as found only when its config directory or CLI exists; setup never creates config for a client you do not have.
12
10
 
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).
11
+ | Client | Detected by | What is changed |
12
+ |---|---|---|
13
+ | Claude Code | `claude` on `PATH`, or `~/.claude.json` | `claude mcp remove --scope user logwork`, then `claude mcp add --scope user logwork -- <command> <args>`. Without the CLI: the top-level `mcpServers` in `~/.claude.json`. |
14
+ | Cursor | `~/.cursor` | `mcpServers.logwork` in `~/.cursor/mcp.json` |
15
+ | Codex | `~/.codex` | the `[mcp_servers.logwork]` table in `~/.codex/config.toml` |
16
+ | VS Code (user) | `~/Library/Application Support/Code/User` (macOS), `%APPDATA%\Code\User` (Windows) | `servers.logwork` (`"type": "stdio"`) in `mcp.json` there |
17
+ | Google Antigravity | `~/.gemini/antigravity` | `mcpServers.logwork` in `~/.gemini/antigravity/mcp_config.json` |
18
+
19
+ How files are edited:
20
+ - Each file is read, parsed and merged. Only the `logwork` entry changes; other servers and settings stay as they are. Extra fields you added to the `logwork` entry (such as `env`) are kept, except in Codex, where the whole `[mcp_servers.logwork]` table (with sub-tables such as `[mcp_servers.logwork.env]`) is replaced.
21
+ - An old `logwork-helper` entry is removed. In Claude Code, a stale project-scope (`--scope local`) `logwork` entry is removed too, because it would hide the user-scope one.
22
+ - Before the first change, the file is copied to `<file>.logwork-backup-<YYYYMMDDHHmmss>` next to it. Then the new file is written atomically. A symlinked config is written through the link.
23
+ - When the entry is already current, nothing is written and no backup is made.
24
+ - When a file does not parse cleanly (JSON with comments or trailing commas, TOML with an unusual layout such as an inline `mcp_servers` table), it is not touched. The result says "could not update automatically" and shows the snippet to paste.
25
+
26
+ Commands:
27
+
28
+ ```bash
29
+ logwork mcp status # per client: current, stale (and why), missing, leftover logwork-helper
30
+ logwork mcp install # asks once per client that needs a change (default: yes)
31
+ logwork mcp install --client cursor # only some clients (repeatable: claude, cursor, codex, vscode, antigravity)
32
+ logwork mcp install --dry-run # show the planned changes, write nothing
33
+ logwork mcp install --yes # no questions
34
+ logwork setup --yes # setup, updating every detected client without asking
35
+ logwork setup --no-mcp-install # setup without touching MCP configs
36
+ ```
37
+
38
+ When setup does not run in a terminal (for example from `logwork update`), it only prints what `logwork mcp install` would change.
39
+
40
+ `logwork doctor` includes the same per-client check.
41
+
42
+ ## What The Configs Run
43
+
44
+ Every client runs one command under the server name `logwork`. The command never names a Node version, so `nvm install`, `brew upgrade node` or a new Node installer do not break it:
45
+
46
+ - **macOS:** `~/.logwork-cli/bin/logwork-launch mcp`. `logwork setup` writes this small `sh` script. It starts `~/.logwork-cli/bin/logwork.mjs` with the first Node 22+ it finds, in this order:
47
+ 1. the Node that ran `logwork setup`;
48
+ 2. `node` on `PATH`;
49
+ 3. the newest nvm Node (`$NVM_DIR` or `~/.nvm`);
50
+ 4. Volta (`~/.volta/bin/node`) and the fnm default;
51
+ 5. `/opt/homebrew/bin/node`, `/usr/local/bin/node`.
52
+
53
+ If none is found, it prints one line ("install Node.js 22 or newer, or run `logwork setup` again") and exits with code 127.
54
+ - **Windows:** `<node.exe> %USERPROFILE%\.logwork-cli\bin\logwork.mjs mcp`. GUI clients would need `cmd /c` to run a `.cmd` launcher, so the config uses a stable `node.exe` instead:
55
+ - when Node runs from an nvm-windows version folder and `NVM_SYMLINK` is set, `%NVM_SYMLINK%\node.exe`, which follows `nvm use`;
56
+ - otherwise the Node that ran setup, usually `C:\Program Files\nodejs\node.exe`, which an installer upgrade keeps.
57
+
58
+ With any other Node location (Scoop, a portable zip), re-run `logwork setup` after moving or upgrading Node.
59
+
60
+ The paths are absolute because GUI IDEs (started from the Dock, Finder or Start menu) often do not get your shell's `PATH`, so a bare `node` or `logwork` would not be found.
61
+
62
+ ## Manual Setup (Fallback)
14
63
 
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.
64
+ Use this only for a client that `logwork setup` does not support, or one whose file it could not update. `logwork setup` prints the snippet for each client it could not update. You can also open the terminal UI with `logwork` and type `/mcp`, or `/mcp <client>` for a single client; both print your real paths.
16
65
 
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.
66
+ The examples use `/Users/<user>` as a placeholder. On Windows, use the paths printed by `logwork setup` or `/mcp`, for example `"command": "C:\\Program Files\\nodejs\\node.exe"` and `"args": ["C:\\Users\\<user>\\.logwork-cli\\bin\\logwork.mjs", "mcp"]` inside JSON or TOML strings.
18
67
 
19
- ## Cursor / Google Antigravity
68
+ ### Cursor / Google Antigravity
20
69
 
21
- Cursor MCP settings, or `~/.gemini/antigravity/mcp_config.json`:
70
+ `~/.cursor/mcp.json`, or `~/.gemini/antigravity/mcp_config.json`:
22
71
 
23
72
  ```json
24
73
  {
25
74
  "mcpServers": {
26
75
  "logwork": {
27
- "command": "<absolute path to node>",
28
- "args": ["/Users/<user>/.logwork-cli/bin/logwork.mjs", "mcp"]
76
+ "command": "/Users/<user>/.logwork-cli/bin/logwork-launch",
77
+ "args": ["mcp"]
29
78
  }
30
79
  }
31
80
  }
32
81
  ```
33
82
 
34
- ## Codex
83
+ ### Codex
35
84
 
36
85
  `~/.codex/config.toml`, or project `.codex/config.toml`:
37
86
 
38
87
  ```toml
39
88
  [mcp_servers.logwork]
40
- command = "<absolute path to node>"
41
- args = ["/Users/<user>/.logwork-cli/bin/logwork.mjs", "mcp"]
89
+ command = "/Users/<user>/.logwork-cli/bin/logwork-launch"
90
+ args = ["mcp"]
42
91
  startup_timeout_sec = 20
43
92
  tool_timeout_sec = 120
44
93
  ```
45
94
 
46
- ## GitHub Copilot / VS Code
95
+ ### GitHub Copilot / VS Code
47
96
 
48
- Workspace `.vscode/mcp.json`, or your VS Code user MCP config:
97
+ Workspace `.vscode/mcp.json`, or your VS Code user `mcp.json`:
49
98
 
50
99
  ```json
51
100
  {
52
101
  "servers": {
53
102
  "logwork": {
54
103
  "type": "stdio",
55
- "command": "<absolute path to node>",
56
- "args": ["/Users/<user>/.logwork-cli/bin/logwork.mjs", "mcp"]
104
+ "command": "/Users/<user>/.logwork-cli/bin/logwork-launch",
105
+ "args": ["mcp"]
57
106
  }
58
107
  }
59
108
  }
60
109
  ```
61
110
 
62
- ## Claude Code
111
+ ### Claude Code
63
112
 
64
113
  ```bash
65
- claude mcp add --transport stdio logwork -- "<absolute path to node>" "/Users/<user>/.logwork-cli/bin/logwork.mjs" mcp
114
+ claude mcp add --transport stdio --scope user logwork -- ~/.logwork-cli/bin/logwork-launch mcp
66
115
  ```
67
116
 
68
117
  From Windows PowerShell:
69
118
 
70
119
  ```powershell
71
- claude mcp add --transport stdio logwork -- 'C:\Program Files\nodejs\node.exe' 'C:\Users\<user>\.logwork-cli\bin\logwork.mjs' mcp
120
+ claude mcp add --transport stdio --scope user logwork -- 'C:\Program Files\nodejs\node.exe' 'C:\Users\<user>\.logwork-cli\bin\logwork.mjs' mcp
72
121
  ```
73
122
 
74
123
  Or add the Cursor JSON above to the project `.mcp.json`.
75
124
 
76
- Templates are also in [examples/mcp](../examples/mcp/).
125
+ Templates (macOS form) are also in [examples/mcp](../examples/mcp/); on Windows, use the paths printed by `/mcp`.
77
126
 
78
- If you used `logwork-helper` before, delete its old MCP entry. It points at `~/.logwork-helper/mcp-server.mjs`, which is the old runtime.
127
+ If you used `logwork-helper` before and edit configs by hand, delete its old MCP entry. It points at `~/.logwork-helper/mcp-server.mjs`, which is the old runtime.
79
128
 
80
129
  ## Exposed Tools
81
130
 
@@ -95,7 +144,7 @@ Previews expire after 1 hour. They are also lost when the server restarts.
95
144
  | Group | Tool | Kind | Purpose |
96
145
  |---|---|---|---|
97
146
  | Read | `get_logwork` | read-only | Booked vs logged RO hours by day and project, with entries (`logworkId`, worklog task, description, status). |
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`. |
147
+ | 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`, `unmappedRepos`, and `repoSuggestions` (likely project per unmapped repo, with reasons) plus `projectCandidates`. |
99
148
  | Read | `list_projects` | read-only | RO memberships (`projectMemberId`, `projectId`, `projectName`) and local mappings. |
100
149
  | Read | `list_tasks` | read-only | RO worklog task catalog for one `projectId`: id, name, type of work, and when to use each task. |
101
150
  | Read | `get_jira_issue` | read-only | Jira issue key, summary and status. |
@@ -198,7 +247,10 @@ Expected flow:
198
247
  - 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
248
  - 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
249
  - 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`.
250
+ - It also returns `unmappedRepos` (repo paths whose lines got no project), `repoSuggestions` and `projectCandidates`:
251
+ - `repoSuggestions` maps each unmapped repo to up to 5 projects, best first: `{ projectName, projectMemberId, projectId, score, reasons }`. The evidence is a ticket from the repo's commits that you logged to the project in the last 30 days, the repo folder or git remote name matching the project name (its acronym in parentheses or its initials), and the only project booked on the repo's commit days. `reasons` are readable, for example `ticket FCS-12 was logged to this project on 2026-11-03` or `repo name matches FCS`.
252
+ - `projectCandidates` lists every member project (booked ones first).
253
+ - The assistant asks one short question per repo by project name ("Is fcs-web part of GKE Fair Chem System (FCS)?"). On a yes it calls `map_project` with `projectName` (or the suggestion's `projectMemberId`), `repos` and `confirm: true`, then runs `draft_from_commits` again.
202
254
  3. `list_tasks` confirms the worklog task names for each project.
203
255
  4. The assistant rewrites vague descriptions, shows you the block and waits for approval.
204
256
  5. `log_work(text, projectOverrides)` previews the entries, then `log_work(batchId, confirm: true)` applies them.
@@ -353,11 +405,11 @@ The two applies are intentionally not atomic. `log_work` never writes Jira, and
353
405
  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`.
354
406
 
355
407
  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`.
408
+ - When `draft_from_commits` reports `unmappedRepos`, the assistant confirms the top `repoSuggestions` entry with you by name, then calls `map_project` with `projectName`, `repos` (absolute paths) and `confirm: true`.
357
409
  - At least one ticket prefix or repo is required.
358
410
  - A subfolder is stored as the root of its repo.
359
411
  - 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.
412
+ - In a terminal, `logwork commits` asks for each unmapped repo with a picker (likely project first). In the terminal UI, `/map --repo [path]` opens the same picker and `/map --repo [path] <project name>` maps by name. The path defaults to the current folder.
361
413
 
362
414
  Mappings are stored in one of two places:
363
415
  - **User scope (default):** `~/.logwork-cli/.logwork.json`.
@@ -19,6 +19,8 @@ npm install -g logwork-cli
19
19
  logwork setup
20
20
  ```
21
21
 
22
+ `logwork setup` also writes the launcher (`~/.logwork-cli/bin/logwork-launch` on macOS) and adds the `logwork` MCP server to the clients it finds (Claude Code, Cursor, Codex, VS Code, Google Antigravity), asking once per client. Each edited config is backed up first. Options: `--yes` (no questions), `--no-mcp-install` (do not touch MCP configs). Check or redo it later with `logwork mcp status` and `logwork mcp install`; see [MCP setup](mcp-setup.md#automatic-setup).
23
+
22
24
  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
25
 
24
26
  Authenticate each target from Terminal:
@@ -348,6 +350,14 @@ RO project mapping can use:
348
350
  - Task keywords, for example `maintenance mode`.
349
351
  - Explicit project selection during preview.
350
352
  - Jira project key/name metadata in the terminal Both flow to rank RO memberships.
353
+ - Git repo roots (`repos`), for commit drafts.
354
+
355
+ A repo with no mapping is mapped once, in place, without ids:
356
+ - `logwork commits` in an interactive terminal asks `Which RO project does <repo> belong to?` with the likely project preselected, saves the choice and redoes the draft (`--no-prompt` turns this off).
357
+ - In the terminal UI, `/map --repo [path]` opens the same picker; `/map --repo [path] <project name>` maps by name (case-insensitive, then a unique part of the name; an ambiguous name opens the picker).
358
+ - MCP: `draft_from_commits` returns `repoSuggestions` per unmapped repo; the assistant confirms the top one by name and calls `map_project` with `projectName` and `repos`.
359
+
360
+ The likely project comes from ticket keys in the repo's commits that you logged to a project in the last 30 days, then the repo folder or git remote name against the project name (acronym in parentheses, initials, shared words), then the only project booked on the repo's commit days.
351
361
 
352
362
  Example MCP sequence:
353
363
 
@@ -453,8 +463,10 @@ Behavior:
453
463
  - Missing auth produces an actionable notification without exposing API details.
454
464
  - macOS uses a user `launchd` agent (`sg.vinova.logwork-cli.reminder`).
455
465
  - Windows uses an interactive user Task Scheduler task (`Logwork Reminder`).
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.
466
+ - The scheduler runs the same command as the MCP configs with `reminder run --home ~/.logwork-cli`:
467
+ - macOS: `~/.logwork-cli/bin/logwork-launch`, which finds a Node 22+ itself, so a Node upgrade does not break the reminder;
468
+ - Windows: a stable `node.exe` (the nvm-windows symlink, or `C:\Program Files\nodejs\node.exe`) plus `~/.logwork-cli/bin/logwork.mjs`.
469
+ - `logwork reminder status` and `logwork doctor` warn when the scheduled command is missing, when the launcher finds no usable Node, and when a schedule from an older version still runs a raw Node path. Run `logwork setup` (or `logwork reminder enable`) to re-register it.
458
470
  - Enabling or disabling the reminder also removes a leftover `logwork-helper` schedule.
459
471
 
460
472
  Commit evidence:
@@ -567,6 +579,8 @@ The release doctor checks package version consistency, self-dependencies, clean/
567
579
  | Update cache | `.logwork-cli/update-state.json` | No |
568
580
  | Reminder config/state | `.logwork-cli/reminder.json`, `.logwork-cli/reminder-state.json` | No |
569
581
  | Runtime Node record | `.logwork-cli/runtime.json` (Node path and version from `logwork setup`) | No |
582
+ | Launcher (macOS) | `.logwork-cli/bin/logwork-launch`, written by `logwork setup` | No |
583
+ | MCP config backups | `<client config>.logwork-backup-<timestamp>`, next to each edited client config | No |
570
584
  | RO/Jira sessions | OS credential store, service `logwork-cli` | Yes |
571
585
 
572
586
  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.
@@ -616,7 +630,8 @@ Recommended end-user checks after installation:
616
630
  - Partial Both result: inspect `/history both`, then preview only the missing target.
617
631
  - npm `E401`: refresh npm login with `npm login --registry=https://registry.npmjs.org/`.
618
632
  - 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.
633
+ - MCP client or reminder does not start after a Node change: run `logwork doctor` or `logwork mcp status`. `logwork setup` rewrites the launcher, re-registers the reminder and updates the MCP configs it can edit; paste the printed snippet only for a client it reports as "could not update automatically".
634
+ - `logwork mcp install` reports "could not update automatically": the file has comments, trailing commas or an unusual TOML layout. Paste the printed snippet by hand, or remove the comments and run it again.
620
635
  - MCP after update: restart or reload the MCP connection.
621
636
 
622
637
  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).