logwork-cli 0.0.0-stage → 1.1.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 (96) hide show
  1. package/LICENSE +11 -0
  2. package/README.md +330 -2
  3. package/RELEASE.md +89 -0
  4. package/bin/logwork.mjs +104 -0
  5. package/commands/auth.mjs +227 -0
  6. package/commands/commits.mjs +82 -0
  7. package/commands/delete.mjs +69 -0
  8. package/commands/doctor.mjs +24 -0
  9. package/commands/edit.mjs +88 -0
  10. package/commands/history.mjs +62 -0
  11. package/commands/query.mjs +52 -0
  12. package/commands/quick.mjs +163 -0
  13. package/commands/reconcile.mjs +55 -0
  14. package/commands/release.mjs +77 -0
  15. package/commands/reminder.mjs +172 -0
  16. package/commands/setup.mjs +508 -0
  17. package/commands/skill.mjs +91 -0
  18. package/commands/tui.mjs +50 -0
  19. package/commands/update.mjs +161 -0
  20. package/docs/advanced.md +334 -0
  21. package/docs/mcp-setup.md +368 -0
  22. package/docs/operations-guide.md +587 -0
  23. package/docs/recipes.md +259 -0
  24. package/docs/security.md +151 -0
  25. package/examples/mcp/antigravity.mcp_config.json +8 -0
  26. package/examples/mcp/codex.config.toml +5 -0
  27. package/examples/mcp/copilot.mcp.json +9 -0
  28. package/lib/agent-skill.mjs +16 -0
  29. package/lib/api-auth.mjs +1259 -0
  30. package/lib/api.mjs +1422 -0
  31. package/lib/apply-ledger.mjs +227 -0
  32. package/lib/atomic-file.mjs +24 -0
  33. package/lib/auth-diagnostics.mjs +113 -0
  34. package/lib/auth-errors.mjs +22 -0
  35. package/lib/auth-redaction.mjs +78 -0
  36. package/lib/auth-terminal.mjs +97 -0
  37. package/lib/auth.mjs +323 -0
  38. package/lib/batch-parser.mjs +145 -0
  39. package/lib/batch-workflow.mjs +842 -0
  40. package/lib/commit-workflow.mjs +402 -0
  41. package/lib/config.mjs +191 -0
  42. package/lib/credential-store.mjs +130 -0
  43. package/lib/diagnostics.mjs +307 -0
  44. package/lib/entrypoint.mjs +14 -0
  45. package/lib/file-lock.mjs +44 -0
  46. package/lib/http.mjs +125 -0
  47. package/lib/jira-api.mjs +258 -0
  48. package/lib/jira-auth.mjs +203 -0
  49. package/lib/jira-workflow.mjs +523 -0
  50. package/lib/keychain.mjs +105 -0
  51. package/lib/legacy-migration.mjs +66 -0
  52. package/lib/list-navigation.mjs +19 -0
  53. package/lib/logwork-config.mjs +199 -0
  54. package/lib/logwork-f03-enrich.mjs +41 -0
  55. package/lib/macos.mjs +33 -0
  56. package/lib/manual-apply-state.mjs +35 -0
  57. package/lib/manual-commands.mjs +294 -0
  58. package/lib/manual-drafts.mjs +154 -0
  59. package/lib/manual-ink-app.mjs +1654 -0
  60. package/lib/manual-ink-auth.mjs +349 -0
  61. package/lib/manual-ink-ui.mjs +675 -0
  62. package/lib/manual-input.mjs +21 -0
  63. package/lib/manual-logwork-wizard.mjs +310 -0
  64. package/lib/manual-mcp.mjs +187 -0
  65. package/lib/manual-repl.mjs +1037 -0
  66. package/lib/manual-targets.mjs +78 -0
  67. package/lib/mcp-server.mjs +833 -0
  68. package/lib/os-notification.mjs +105 -0
  69. package/lib/package-info.mjs +17 -0
  70. package/lib/paths.mjs +68 -0
  71. package/lib/project-identity.mjs +66 -0
  72. package/lib/project-mapping-workflow.mjs +309 -0
  73. package/lib/project-resolver.mjs +373 -0
  74. package/lib/query-workflow.mjs +553 -0
  75. package/lib/reconciliation-workflow.mjs +446 -0
  76. package/lib/release-doctor.mjs +448 -0
  77. package/lib/reminder-config.mjs +71 -0
  78. package/lib/reminder-scheduler.mjs +301 -0
  79. package/lib/reminder-service.mjs +118 -0
  80. package/lib/reminder-state.mjs +38 -0
  81. package/lib/reminder-workflow.mjs +299 -0
  82. package/lib/ro-delete-args.mjs +38 -0
  83. package/lib/ro-delete-workflow.mjs +115 -0
  84. package/lib/ro-edit-args.mjs +142 -0
  85. package/lib/ro-edit-workflow.mjs +632 -0
  86. package/lib/ro-entry-reader.mjs +56 -0
  87. package/lib/ro-resubmit-workflow.mjs +134 -0
  88. package/lib/terminal-auth-prompts.mjs +120 -0
  89. package/lib/ui.mjs +133 -0
  90. package/lib/update-service.mjs +350 -0
  91. package/lib/util.mjs +66 -0
  92. package/lib/windows-credential-manager.mjs +260 -0
  93. package/lib/worklog-task-resolver.mjs +113 -0
  94. package/package.json +73 -4
  95. package/setup.sh +22 -0
  96. package/skills/logwork/SKILL.md +43 -0
@@ -0,0 +1,113 @@
1
+ const SUGGESTION_LIMIT = 5;
2
+ const TYPE_OF_WORK = new Set(['create', 'correct', 'improve', 'other']);
3
+
4
+ export function readCatalogTypeOfWork(task) {
5
+ const value = String(task?.typeOfWork ?? task?.type_of_work ?? '').trim();
6
+ return TYPE_OF_WORK.has(value) ? value : undefined;
7
+ }
8
+
9
+ export function resolveWorklogTask({ taskName, projectId, tasks = [] }) {
10
+ const normalizedName = String(taskName || '').trim();
11
+ if (!normalizedName) {
12
+ throw new Error('taskName must not be empty.');
13
+ }
14
+
15
+ const projectIdNumber = Number(projectId);
16
+ if (!Number.isFinite(projectIdNumber) || projectIdNumber <= 0) {
17
+ throw new Error('projectId must be a positive number.');
18
+ }
19
+
20
+ const comparable = normalizedName.toLocaleLowerCase();
21
+ const idMatch = /^#(\d+)$/.exec(normalizedName);
22
+ const matches = idMatch
23
+ ? tasks.filter((task) => String(task?.id) === idMatch[1])
24
+ : tasks.filter((task) => {
25
+ const name = String(task?.name || '').trim();
26
+ return name && name.toLocaleLowerCase() === comparable;
27
+ });
28
+
29
+ if (matches.length > 1) {
30
+ const ids = matches.map((task) => task.id).join(', ');
31
+ throw new Error(
32
+ `Ambiguous worklog task name "${normalizedName}" for project ${projectIdNumber} (ids: ${ids}).`
33
+ );
34
+ }
35
+
36
+ if (!matches.length) {
37
+ const suggestions = suggestTaskNames(tasks, normalizedName);
38
+ const hint = suggestions.length
39
+ ? ` Similar tasks: ${suggestions.map((name) => `"${name}"`).join(', ')}.`
40
+ : ' No similar tasks found for this project.';
41
+ throw new Error(
42
+ `No worklog task named "${normalizedName}" for project ${projectIdNumber}.${hint}`
43
+ );
44
+ }
45
+
46
+ const task = matches[0];
47
+ const taskProjectId = readTaskProjectId(task);
48
+ const catalogTypeOfWork = readCatalogTypeOfWork(task);
49
+ return {
50
+ worklogTaskId: task.id,
51
+ taskName: String(task.name || '').trim(),
52
+ needsProjectId: taskProjectId === null || taskProjectId === undefined,
53
+ ...(catalogTypeOfWork ? { typeOfWork: catalogTypeOfWork } : {})
54
+ };
55
+ }
56
+
57
+ export function mergeWorklogTaskLists(projectTasks = [], defaultTasks = []) {
58
+ const byId = new Map();
59
+ for (const task of [...projectTasks, ...defaultTasks]) {
60
+ if (task?.id !== undefined && task?.id !== null && !byId.has(task.id)) {
61
+ byId.set(task.id, task);
62
+ }
63
+ }
64
+ return [...byId.values()];
65
+ }
66
+
67
+ export function normalizeWorklogTask(record = {}) {
68
+ const projectId = readTaskProjectId(record);
69
+ return {
70
+ id: record.id,
71
+ name: String(record.name || '').trim(),
72
+ description: String(record.description || '').trim(),
73
+ projectId: projectId === undefined ? null : projectId,
74
+ typeOfWork: record.type_of_work ?? record.typeOfWork ?? null,
75
+ isDefault: Boolean(record.is_default ?? record.isDefault)
76
+ };
77
+ }
78
+
79
+ function readTaskProjectId(task) {
80
+ if (task?.project_id !== undefined && task?.project_id !== null) {
81
+ return Number(task.project_id);
82
+ }
83
+ if (task?.project?.id !== undefined && task?.project?.id !== null) {
84
+ return Number(task.project.id);
85
+ }
86
+ if (task?.projectId !== undefined && task?.projectId !== null) {
87
+ return Number(task.projectId);
88
+ }
89
+ return null;
90
+ }
91
+
92
+ export function listAvailableTaskNames(tasks = [], limit = 30) {
93
+ return [...new Set(tasks
94
+ .map((task) => String(task?.name || '').trim())
95
+ .filter(Boolean))]
96
+ .slice(0, limit);
97
+ }
98
+
99
+ export function listSuggestedTaskNames(tasks, taskName, limit = SUGGESTION_LIMIT) {
100
+ return suggestTaskNames(tasks, taskName, limit);
101
+ }
102
+
103
+ function suggestTaskNames(tasks, taskName, limit = SUGGESTION_LIMIT) {
104
+ const needle = String(taskName || '').trim().toLocaleLowerCase();
105
+ if (!needle) {
106
+ return [];
107
+ }
108
+
109
+ return tasks
110
+ .map((task) => String(task?.name || '').trim())
111
+ .filter((name) => name && name.toLocaleLowerCase().includes(needle))
112
+ .slice(0, limit);
113
+ }
package/package.json CHANGED
@@ -1,6 +1,75 @@
1
1
  {
2
2
  "name": "logwork-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.1.0",
4
+ "description": "Log work to Resource Optimiser and Jira from the terminal, an MCP server for AI IDEs, and a terminal UI.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "homepage": "https://github.com/malcohelper/logwork-cli#readme",
8
+ "bugs": {
9
+ "url": "https://github.com/malcohelper/logwork-cli/issues"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/malcohelper/logwork-cli.git"
14
+ },
15
+ "keywords": [
16
+ "logwork",
17
+ "resource-optimiser",
18
+ "mcp-server",
19
+ "ai-assistant",
20
+ "nodejs",
21
+ "macos",
22
+ "windows",
23
+ "keycloak",
24
+ "codex",
25
+ "cursor",
26
+ "vscode",
27
+ "claude-code",
28
+ "cli"
29
+ ],
30
+ "engines": {
31
+ "node": ">=22"
32
+ },
33
+ "os": [
34
+ "darwin",
35
+ "win32"
36
+ ],
37
+ "files": [
38
+ "bin/",
39
+ "commands/",
40
+ "lib/",
41
+ "skills/",
42
+ "docs/",
43
+ "examples/mcp/",
44
+ "setup.sh",
45
+ "README.md",
46
+ "LICENSE",
47
+ "RELEASE.md"
48
+ ],
49
+ "bin": {
50
+ "logwork": "bin/logwork.mjs",
51
+ "logwork-cli": "bin/logwork.mjs"
52
+ },
53
+ "scripts": {
54
+ "start": "node bin/logwork.mjs",
55
+ "dry-run": "LOGWORK_DRY_RUN=1 node bin/logwork.mjs",
56
+ "mcp": "node bin/logwork.mjs mcp",
57
+ "setup": "node bin/logwork.mjs setup",
58
+ "audit:prod": "npm audit --omit=dev --audit-level=moderate",
59
+ "pack:check": "npm pack --dry-run",
60
+ "release:doctor": "node bin/logwork.mjs release doctor --full",
61
+ "release:check": "npm test && npm run audit:prod && npm run pack:check && git diff --check",
62
+ "test:windows-credential": "node --test --test-name-pattern=\"Windows Credential Manager stores, reads, and deletes a test credential\" test/credential-store.test.mjs",
63
+ "test": "node --test"
64
+ },
65
+ "dependencies": {
66
+ "@clack/prompts": "^1.5.0",
67
+ "@modelcontextprotocol/sdk": "^1.29.0",
68
+ "chalk": "^5.6.2",
69
+ "ink": "^7.0.5",
70
+ "ink-select-input": "^6.2.0",
71
+ "ink-text-input": "^6.0.0",
72
+ "react": "^19.2.7",
73
+ "zod": "^4.4.3"
74
+ }
75
+ }
package/setup.sh ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+
6
+ if ! command -v node >/dev/null 2>&1; then
7
+ echo "Node.js 22+ is required. Install Node, then run setup again." >&2
8
+ exit 1
9
+ fi
10
+
11
+ NODE_MAJOR="$(node -p "Number(process.versions.node.split('.')[0])")"
12
+ if [ "$NODE_MAJOR" -lt 22 ]; then
13
+ echo "Node.js 22+ is required. Current version: $(node --version)" >&2
14
+ exit 1
15
+ fi
16
+
17
+ if ! command -v npm >/dev/null 2>&1; then
18
+ echo "npm is required. Install npm, then run setup again." >&2
19
+ exit 1
20
+ fi
21
+
22
+ node "$SCRIPT_DIR/bin/logwork.mjs" setup "$@"
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: logwork
3
+ description: Log work hours to Resource Optimiser (RO) and Jira from the user's own git commits with the logwork MCP tools. Use when the user asks to log work, fill in logwork or timesheets for a day or week, check missing hours, or fix existing RO entries.
4
+ ---
5
+
6
+ # Logwork from commits
7
+
8
+ Goal: every logged line is backed by real work (the user's commits or what the user tells you), uses a valid RO worklog task, has a clear description, and fits the day's booked hours.
9
+
10
+ ## Steps
11
+
12
+ 1. **Find the gaps.** Call `get_logwork` for the period. A day/project still needs time when booked > logged. Skip days that are already full.
13
+ 2. **Collect the evidence.** Call `draft_from_commits` with the same period and `repos` set to every repo the user worked in (the open workspace folders; ask the user for others). It groups the user's commits per day by repo, ticket and kind of work, suggests a worklog task per group, and splits each day's remaining RO hours across the groups. Treat its hours as a starting point, not a fact.
14
+ 3. **Check the worklog tasks.** For each project involved, call `list_tasks(projectId)`. Keep the suggested task when it exists in that catalog; otherwise pick the catalog task whose description matches the work.
15
+ 4. **Write the descriptions.** One line per piece of work: what was done and the outcome, with ticket keys, in English, under about 200 characters, no `|`. Rewrite vague commit subjects ("wip", "fix", "update"); use `get_jira_issue` to read the ticket when the subject alone is unclear.
16
+ 5. **Set the hours.**
17
+ - Stay within the remaining booked hours per day and project unless the user says otherwise.
18
+ - Use 0.5h steps.
19
+ - Do not log weekends or days without a booking unless the user asks.
20
+ - Work without commits (meetings, reviews, support) only comes from the user; ask, then log it as `Other Activities` or `Code Review & Refactoring`.
21
+ 6. **Resolve projects.** Ticket prefixes and mappings pick the project. If a line stays unresolved, ask the user and offer `map_project` so it is automatic next time.
22
+ 7. **Get approval, then write.**
23
+ - Show the final block to the user.
24
+ - On approval, call `log_work(text)` and show its preview summary.
25
+ - Then call `log_work(batchId, confirm: true)`.
26
+ - If the user also logs Jira, do the same with `log_jira_work`. Jira needs exactly one issue key per line.
27
+
28
+ ## Line format
29
+
30
+ ```text
31
+ Friday, 06 Nov 2026
32
+ +2 Feature Development | Implement SSO login with ADFS (STM-4)
33
+ +1 Bug Fix | Fix date picker timezone offset (SAP-42)
34
+ ```
35
+
36
+ `+<hours> <worklog task> | <description>`. The task is an RO catalog name or `#<id>`.
37
+
38
+ ## Rules
39
+
40
+ - Never invent work that is not in the commits or in what the user told you. When unsure, ask.
41
+ - Never set `confirm: true` without the user's explicit approval of that preview.
42
+ - Never ask for or pass passwords, OTPs or tokens. On `authRequired`, call `login` and let the user finish in the terminal.
43
+ - To fix an existing entry, use `edit_entry` (submitted or approved entries) or `resubmit_entry` (rejected entries). Do not log a duplicate.