logwork-cli 0.0.0-stage → 1.0.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.
Files changed (91) hide show
  1. package/LICENSE +11 -0
  2. package/README.md +287 -2
  3. package/RELEASE.md +89 -0
  4. package/bin/logwork.mjs +101 -0
  5. package/commands/auth.mjs +227 -0
  6. package/commands/delete.mjs +69 -0
  7. package/commands/doctor.mjs +24 -0
  8. package/commands/edit.mjs +88 -0
  9. package/commands/history.mjs +62 -0
  10. package/commands/query.mjs +52 -0
  11. package/commands/quick.mjs +163 -0
  12. package/commands/reconcile.mjs +55 -0
  13. package/commands/release.mjs +77 -0
  14. package/commands/reminder.mjs +172 -0
  15. package/commands/setup.mjs +504 -0
  16. package/commands/tui.mjs +50 -0
  17. package/commands/update.mjs +161 -0
  18. package/docs/advanced.md +334 -0
  19. package/docs/mcp-setup.md +347 -0
  20. package/docs/operations-guide.md +587 -0
  21. package/docs/recipes.md +259 -0
  22. package/docs/security.md +151 -0
  23. package/examples/mcp/antigravity.mcp_config.json +8 -0
  24. package/examples/mcp/codex.config.toml +5 -0
  25. package/examples/mcp/copilot.mcp.json +9 -0
  26. package/lib/api-auth.mjs +1259 -0
  27. package/lib/api.mjs +1422 -0
  28. package/lib/apply-ledger.mjs +227 -0
  29. package/lib/atomic-file.mjs +24 -0
  30. package/lib/auth-diagnostics.mjs +113 -0
  31. package/lib/auth-errors.mjs +22 -0
  32. package/lib/auth-redaction.mjs +78 -0
  33. package/lib/auth-terminal.mjs +97 -0
  34. package/lib/auth.mjs +323 -0
  35. package/lib/batch-parser.mjs +145 -0
  36. package/lib/batch-workflow.mjs +842 -0
  37. package/lib/config.mjs +191 -0
  38. package/lib/credential-store.mjs +130 -0
  39. package/lib/diagnostics.mjs +307 -0
  40. package/lib/entrypoint.mjs +14 -0
  41. package/lib/file-lock.mjs +44 -0
  42. package/lib/http.mjs +125 -0
  43. package/lib/jira-api.mjs +258 -0
  44. package/lib/jira-auth.mjs +203 -0
  45. package/lib/jira-workflow.mjs +523 -0
  46. package/lib/keychain.mjs +105 -0
  47. package/lib/legacy-migration.mjs +66 -0
  48. package/lib/list-navigation.mjs +19 -0
  49. package/lib/logwork-config.mjs +199 -0
  50. package/lib/logwork-f03-enrich.mjs +41 -0
  51. package/lib/macos.mjs +33 -0
  52. package/lib/manual-apply-state.mjs +35 -0
  53. package/lib/manual-commands.mjs +294 -0
  54. package/lib/manual-drafts.mjs +154 -0
  55. package/lib/manual-ink-app.mjs +1654 -0
  56. package/lib/manual-ink-auth.mjs +349 -0
  57. package/lib/manual-ink-ui.mjs +675 -0
  58. package/lib/manual-input.mjs +21 -0
  59. package/lib/manual-logwork-wizard.mjs +310 -0
  60. package/lib/manual-mcp.mjs +187 -0
  61. package/lib/manual-repl.mjs +1037 -0
  62. package/lib/manual-targets.mjs +78 -0
  63. package/lib/mcp-server.mjs +793 -0
  64. package/lib/os-notification.mjs +105 -0
  65. package/lib/package-info.mjs +17 -0
  66. package/lib/paths.mjs +68 -0
  67. package/lib/project-identity.mjs +66 -0
  68. package/lib/project-mapping-workflow.mjs +309 -0
  69. package/lib/project-resolver.mjs +373 -0
  70. package/lib/query-workflow.mjs +553 -0
  71. package/lib/reconciliation-workflow.mjs +446 -0
  72. package/lib/release-doctor.mjs +448 -0
  73. package/lib/reminder-config.mjs +71 -0
  74. package/lib/reminder-scheduler.mjs +301 -0
  75. package/lib/reminder-service.mjs +118 -0
  76. package/lib/reminder-state.mjs +38 -0
  77. package/lib/reminder-workflow.mjs +299 -0
  78. package/lib/ro-delete-args.mjs +38 -0
  79. package/lib/ro-delete-workflow.mjs +115 -0
  80. package/lib/ro-edit-args.mjs +142 -0
  81. package/lib/ro-edit-workflow.mjs +632 -0
  82. package/lib/ro-entry-reader.mjs +56 -0
  83. package/lib/ro-resubmit-workflow.mjs +134 -0
  84. package/lib/terminal-auth-prompts.mjs +120 -0
  85. package/lib/ui.mjs +133 -0
  86. package/lib/update-service.mjs +350 -0
  87. package/lib/util.mjs +66 -0
  88. package/lib/windows-credential-manager.mjs +260 -0
  89. package/lib/worklog-task-resolver.mjs +113 -0
  90. package/package.json +72 -4
  91. package/setup.sh +22 -0
@@ -0,0 +1,55 @@
1
+ import {
2
+ normalizeReconciliationPeriod,
3
+ reconcileLogwork
4
+ } from '../lib/reconciliation-workflow.mjs';
5
+
6
+ export function parseReconcileArgs(args = []) {
7
+ const options = {
8
+ period: 'this_week',
9
+ json: false,
10
+ help: false
11
+ };
12
+ const positional = [];
13
+ for (const arg of args) {
14
+ if (arg === '--json') {
15
+ options.json = true;
16
+ continue;
17
+ }
18
+ if (arg === '--help' || arg === '-h') {
19
+ options.help = true;
20
+ continue;
21
+ }
22
+ if (arg.startsWith('-')) {
23
+ throw new Error(`Unknown reconcile option: ${arg}`);
24
+ }
25
+ positional.push(arg);
26
+ }
27
+ if (positional.length > 1) {
28
+ throw new Error('Reconcile accepts exactly one preset period.');
29
+ }
30
+ if (positional[0]) {
31
+ options.period = normalizeReconciliationPeriod(positional[0]);
32
+ }
33
+ return options;
34
+ }
35
+
36
+ export async function runReconcileCommand(args = [], {
37
+ reconcile = reconcileLogwork,
38
+ print = console.log
39
+ } = {}) {
40
+ const options = parseReconcileArgs(args);
41
+ if (options.help) {
42
+ print(formatReconcileHelp());
43
+ return null;
44
+ }
45
+ const result = await reconcile({ period: options.period });
46
+ print(options.json ? JSON.stringify(result, null, 2) : result.summary);
47
+ return result;
48
+ }
49
+
50
+ export function formatReconcileHelp() {
51
+ return `Usage:
52
+ logwork reconcile [today|yesterday|this-week|last-week|this-month|last-month] [--json]
53
+
54
+ Compares Resource Optimiser and Jira worklog hours by day. This command is read-only. Correction suggestions must still go through the existing target-specific preview and approval flow.`;
55
+ }
@@ -0,0 +1,77 @@
1
+ import {
2
+ formatReleaseDoctorReport,
3
+ runReleaseDoctor
4
+ } from '../lib/release-doctor.mjs';
5
+
6
+ export function parseReleaseArgs(args = []) {
7
+ const [command, ...rest] = args;
8
+ if (!command || command === '--help' || command === '-h') {
9
+ return {
10
+ type: 'help'
11
+ };
12
+ }
13
+
14
+ if (command !== 'doctor') {
15
+ throw new Error(`Unknown release command: ${command}`);
16
+ }
17
+
18
+ const options = {
19
+ type: 'doctor',
20
+ full: false
21
+ };
22
+ for (const arg of rest) {
23
+ if (arg === '--full') {
24
+ options.full = true;
25
+ continue;
26
+ }
27
+ if (arg === '--help' || arg === '-h') {
28
+ return {
29
+ type: 'help'
30
+ };
31
+ }
32
+ throw new Error(`Unknown release doctor option: ${arg}`);
33
+ }
34
+
35
+ return options;
36
+ }
37
+
38
+ export function run(args = []) {
39
+ return runReleaseCommand({ args });
40
+ }
41
+
42
+ export async function runReleaseCommand({
43
+ args = [],
44
+ stdout = console.log,
45
+ stderr = console.error,
46
+ runDoctor = runReleaseDoctor
47
+ } = {}) {
48
+ const parsed = parseReleaseArgs(args);
49
+ if (parsed.type === 'help') {
50
+ stdout(formatReleaseHelp());
51
+ return 0;
52
+ }
53
+
54
+ const result = await runDoctor({
55
+ full: parsed.full
56
+ });
57
+ stdout(formatReleaseDoctorReport(result));
58
+ if (!result.ok) {
59
+ stderr('Release doctor found blocking issues.');
60
+ return 1;
61
+ }
62
+ return 0;
63
+ }
64
+
65
+ function formatReleaseHelp() {
66
+ return `Usage:
67
+ logwork release doctor [--full]
68
+
69
+ Checks release readiness before npm/GitHub publishing.
70
+
71
+ Commands:
72
+ doctor Check version, git state, npm auth, GitHub auth, tags, and artifacts
73
+
74
+ Options:
75
+ --full Also run npm test, npm audit, npm pack --dry-run, and git diff --check
76
+ `;
77
+ }
@@ -0,0 +1,172 @@
1
+ import { confirm, isCancel } from '@clack/prompts';
2
+ import {
3
+ normalizeReminderTime,
4
+ REMINDER_TARGETS
5
+ } from '../lib/reminder-config.mjs';
6
+ import {
7
+ disableLogworkReminder,
8
+ enableLogworkReminder,
9
+ getLogworkReminderStatus,
10
+ testLogworkReminder
11
+ } from '../lib/reminder-service.mjs';
12
+ import { runScheduledReminder } from '../lib/reminder-workflow.mjs';
13
+
14
+ export function parseReminderArgs(args = []) {
15
+ const [command = 'help', ...rest] = args;
16
+ if (command === '--help' || command === '-h' || command === 'help') {
17
+ return { command: 'help' };
18
+ }
19
+ if (!['enable', 'status', 'test', 'disable', 'run'].includes(command)) {
20
+ throw new Error(`Unknown reminder command: ${command}.`);
21
+ }
22
+
23
+ const options = {
24
+ command,
25
+ time: undefined,
26
+ target: undefined,
27
+ yes: false,
28
+ json: false,
29
+ home: undefined,
30
+ help: false
31
+ };
32
+ for (let index = 0; index < rest.length; index += 1) {
33
+ const arg = rest[index];
34
+ if (arg === '--help' || arg === '-h') {
35
+ options.help = true;
36
+ continue;
37
+ }
38
+ if (arg === '--time' || arg === '--target' || arg === '--home') {
39
+ const value = rest[index + 1];
40
+ if (!value) {
41
+ throw new Error(`${arg} requires a value.`);
42
+ }
43
+ options[arg.slice(2)] = value;
44
+ index += 1;
45
+ continue;
46
+ }
47
+ if (arg === '--yes' || arg === '-y') {
48
+ options.yes = true;
49
+ continue;
50
+ }
51
+ if (arg === '--json') {
52
+ options.json = true;
53
+ continue;
54
+ }
55
+ throw new Error(`Unknown reminder option: ${arg}`);
56
+ }
57
+
58
+ if (!['enable', 'test'].includes(command) && options.target) {
59
+ throw new Error('--target is only valid with reminder enable or test.');
60
+ }
61
+ if (command !== 'enable' && options.time) {
62
+ throw new Error('--time is only valid with reminder enable.');
63
+ }
64
+ if (command !== 'run' && options.home) {
65
+ throw new Error('--home is reserved for the OS reminder runner.');
66
+ }
67
+ if (!['enable', 'test', 'disable'].includes(command) && options.yes) {
68
+ throw new Error('--yes is only valid with reminder enable, test, or disable.');
69
+ }
70
+ if (options.time) {
71
+ options.time = normalizeReminderTime(options.time);
72
+ }
73
+ if (options.target) {
74
+ options.target = options.target.toLowerCase();
75
+ if (!REMINDER_TARGETS.includes(options.target)) {
76
+ throw new Error('Reminder target must be ro, jira, or both.');
77
+ }
78
+ }
79
+ if (command === 'run' && (options.yes || options.json || options.time || options.target)) {
80
+ throw new Error('Reminder run only accepts the managed --home option.');
81
+ }
82
+ return options;
83
+ }
84
+
85
+ export async function runReminderCli(args = [], {
86
+ enable = enableLogworkReminder,
87
+ disable = disableLogworkReminder,
88
+ status = getLogworkReminderStatus,
89
+ test = testLogworkReminder,
90
+ run = runScheduledReminder,
91
+ promptConfirm = defaultConfirm,
92
+ isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY),
93
+ print = console.log
94
+ } = {}) {
95
+ const options = parseReminderArgs(args);
96
+ if (options.command === 'help' || options.help) {
97
+ print(formatReminderHelp());
98
+ return null;
99
+ }
100
+
101
+ if (options.command === 'run') {
102
+ if (options.home) {
103
+ process.env.LOGWORK_HOME = options.home;
104
+ }
105
+ return run();
106
+ }
107
+
108
+ if (options.command === 'status') {
109
+ const result = await status();
110
+ printResult(result, options.json, print);
111
+ return result;
112
+ }
113
+
114
+ const actionLabel = options.command === 'enable'
115
+ ? `Enable ${options.target || 'both'} logwork reminder at ${options.time || '17:00'}, Monday-Friday?`
116
+ : options.command === 'disable'
117
+ ? 'Disable the logwork reminder and remove its OS schedule?'
118
+ : `Send a ${options.target || 'configured'} test notification now?`;
119
+ const approved = options.yes || await requireInteractiveConfirmation({
120
+ isTTY,
121
+ promptConfirm,
122
+ message: actionLabel
123
+ });
124
+ if (!approved) {
125
+ print('Reminder command cancelled.');
126
+ return null;
127
+ }
128
+
129
+ let result;
130
+ if (options.command === 'enable') {
131
+ result = await enable({
132
+ time: options.time,
133
+ target: options.target,
134
+ confirm: true
135
+ });
136
+ } else if (options.command === 'disable') {
137
+ result = await disable({ confirm: true });
138
+ } else {
139
+ result = await test({
140
+ target: options.target,
141
+ confirm: true
142
+ });
143
+ }
144
+ printResult(result, options.json, print);
145
+ return result;
146
+ }
147
+
148
+ export function formatReminderHelp() {
149
+ return `Usage:
150
+ logwork reminder enable [--time HH:mm] [--target ro|jira|both] [--yes]
151
+ logwork reminder status [--json]
152
+ logwork reminder test [--target ro|jira|both] [--yes]
153
+ logwork reminder disable [--yes]
154
+
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.`;
156
+ }
157
+
158
+ async function requireInteractiveConfirmation({ isTTY, promptConfirm, message }) {
159
+ if (!isTTY) {
160
+ throw new Error('Interactive confirmation is unavailable. Re-run with --yes.');
161
+ }
162
+ return promptConfirm(message);
163
+ }
164
+
165
+ async function defaultConfirm(message) {
166
+ const result = await confirm({ message, initialValue: false });
167
+ return !isCancel(result) && result === true;
168
+ }
169
+
170
+ function printResult(result, json, print) {
171
+ print(json ? JSON.stringify(result, null, 2) : result.summary);
172
+ }