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.
- package/LICENSE +11 -0
- package/README.md +287 -2
- package/RELEASE.md +89 -0
- package/bin/logwork.mjs +101 -0
- package/commands/auth.mjs +227 -0
- package/commands/delete.mjs +69 -0
- package/commands/doctor.mjs +24 -0
- package/commands/edit.mjs +88 -0
- package/commands/history.mjs +62 -0
- package/commands/query.mjs +52 -0
- package/commands/quick.mjs +163 -0
- package/commands/reconcile.mjs +55 -0
- package/commands/release.mjs +77 -0
- package/commands/reminder.mjs +172 -0
- package/commands/setup.mjs +504 -0
- package/commands/tui.mjs +50 -0
- package/commands/update.mjs +161 -0
- package/docs/advanced.md +334 -0
- package/docs/mcp-setup.md +347 -0
- package/docs/operations-guide.md +587 -0
- package/docs/recipes.md +259 -0
- package/docs/security.md +151 -0
- package/examples/mcp/antigravity.mcp_config.json +8 -0
- package/examples/mcp/codex.config.toml +5 -0
- package/examples/mcp/copilot.mcp.json +9 -0
- package/lib/api-auth.mjs +1259 -0
- package/lib/api.mjs +1422 -0
- package/lib/apply-ledger.mjs +227 -0
- package/lib/atomic-file.mjs +24 -0
- package/lib/auth-diagnostics.mjs +113 -0
- package/lib/auth-errors.mjs +22 -0
- package/lib/auth-redaction.mjs +78 -0
- package/lib/auth-terminal.mjs +97 -0
- package/lib/auth.mjs +323 -0
- package/lib/batch-parser.mjs +145 -0
- package/lib/batch-workflow.mjs +842 -0
- package/lib/config.mjs +191 -0
- package/lib/credential-store.mjs +130 -0
- package/lib/diagnostics.mjs +307 -0
- package/lib/entrypoint.mjs +14 -0
- package/lib/file-lock.mjs +44 -0
- package/lib/http.mjs +125 -0
- package/lib/jira-api.mjs +258 -0
- package/lib/jira-auth.mjs +203 -0
- package/lib/jira-workflow.mjs +523 -0
- package/lib/keychain.mjs +105 -0
- package/lib/legacy-migration.mjs +66 -0
- package/lib/list-navigation.mjs +19 -0
- package/lib/logwork-config.mjs +199 -0
- package/lib/logwork-f03-enrich.mjs +41 -0
- package/lib/macos.mjs +33 -0
- package/lib/manual-apply-state.mjs +35 -0
- package/lib/manual-commands.mjs +294 -0
- package/lib/manual-drafts.mjs +154 -0
- package/lib/manual-ink-app.mjs +1654 -0
- package/lib/manual-ink-auth.mjs +349 -0
- package/lib/manual-ink-ui.mjs +675 -0
- package/lib/manual-input.mjs +21 -0
- package/lib/manual-logwork-wizard.mjs +310 -0
- package/lib/manual-mcp.mjs +187 -0
- package/lib/manual-repl.mjs +1037 -0
- package/lib/manual-targets.mjs +78 -0
- package/lib/mcp-server.mjs +793 -0
- package/lib/os-notification.mjs +105 -0
- package/lib/package-info.mjs +17 -0
- package/lib/paths.mjs +68 -0
- package/lib/project-identity.mjs +66 -0
- package/lib/project-mapping-workflow.mjs +309 -0
- package/lib/project-resolver.mjs +373 -0
- package/lib/query-workflow.mjs +553 -0
- package/lib/reconciliation-workflow.mjs +446 -0
- package/lib/release-doctor.mjs +448 -0
- package/lib/reminder-config.mjs +71 -0
- package/lib/reminder-scheduler.mjs +301 -0
- package/lib/reminder-service.mjs +118 -0
- package/lib/reminder-state.mjs +38 -0
- package/lib/reminder-workflow.mjs +299 -0
- package/lib/ro-delete-args.mjs +38 -0
- package/lib/ro-delete-workflow.mjs +115 -0
- package/lib/ro-edit-args.mjs +142 -0
- package/lib/ro-edit-workflow.mjs +632 -0
- package/lib/ro-entry-reader.mjs +56 -0
- package/lib/ro-resubmit-workflow.mjs +134 -0
- package/lib/terminal-auth-prompts.mjs +120 -0
- package/lib/ui.mjs +133 -0
- package/lib/update-service.mjs +350 -0
- package/lib/util.mjs +66 -0
- package/lib/windows-credential-manager.mjs +260 -0
- package/lib/worklog-task-resolver.mjs +113 -0
- package/package.json +72 -4
- 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
|
+
}
|