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,154 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { atomicWriteFile } from './atomic-file.mjs';
4
+ import { withFileLock } from './file-lock.mjs';
5
+ import { manualDraftsPath } from './paths.mjs';
6
+
7
+ export async function loadManualDrafts({ path = manualDraftsPath(), cwd } = {}) {
8
+ let parsed;
9
+ try {
10
+ parsed = JSON.parse(await readFile(path, 'utf8'));
11
+ } catch (error) {
12
+ if (error.code === 'ENOENT') {
13
+ return [];
14
+ }
15
+ return [];
16
+ }
17
+
18
+ const drafts = Array.isArray(parsed) ? parsed : parsed?.drafts;
19
+ if (!Array.isArray(drafts)) {
20
+ return [];
21
+ }
22
+
23
+ const normalizedCwd = normalizeCwd(cwd);
24
+ return drafts
25
+ .map((draft) => sanitizeManualDraft(draft))
26
+ .filter(Boolean)
27
+ .filter((draft) => !normalizedCwd || normalizeCwd(draft.cwd) === normalizedCwd)
28
+ .sort(sortNewestFirst);
29
+ }
30
+
31
+ export async function saveManualDraft(draft, {
32
+ path = manualDraftsPath(),
33
+ cwd = process.cwd(),
34
+ lockOptions = {}
35
+ } = {}) {
36
+ return withFileLock(path, async () => {
37
+ const drafts = await loadManualDrafts({ path });
38
+ const now = new Date().toISOString();
39
+ const existing = draft.id ? drafts.find((candidate) => candidate.id === draft.id) : null;
40
+ const sanitized = sanitizeManualDraft({
41
+ ...draft,
42
+ id: draft.id || createDraftId(),
43
+ cwd: draft.cwd || cwd,
44
+ createdAt: existing?.createdAt || draft.createdAt || now,
45
+ updatedAt: now
46
+ });
47
+
48
+ if (!sanitized) {
49
+ throw new Error('Cannot save empty manual draft.');
50
+ }
51
+
52
+ const nextDrafts = [
53
+ sanitized,
54
+ ...drafts.filter((candidate) => candidate.id !== sanitized.id)
55
+ ].sort(sortNewestFirst);
56
+
57
+ await writeDrafts(nextDrafts, path);
58
+ return sanitized;
59
+ }, lockOptions);
60
+ }
61
+
62
+ export async function deleteManualDraft(id, {
63
+ path = manualDraftsPath(),
64
+ lockOptions = {}
65
+ } = {}) {
66
+ return withFileLock(path, async () => {
67
+ const drafts = await loadManualDrafts({ path });
68
+ const nextDrafts = drafts.filter((draft) => draft.id !== id);
69
+ await writeDrafts(nextDrafts, path);
70
+ return drafts.length !== nextDrafts.length;
71
+ }, lockOptions);
72
+ }
73
+
74
+ export function sanitizeManualDraft(draft = {}) {
75
+ if (!draft || typeof draft !== 'object') {
76
+ return null;
77
+ }
78
+
79
+ const tasks = Array.isArray(draft.tasks)
80
+ ? draft.tasks
81
+ .map((task) => ({
82
+ hours: Number(task.hours),
83
+ taskName: String(task.taskName || '').trim(),
84
+ line: task.line ? String(task.line) : undefined
85
+ }))
86
+ .filter((task) => Number.isFinite(task.hours) && task.hours > 0 && task.taskName)
87
+ : [];
88
+
89
+ const project = sanitizeProject(draft.project || draft.selectedProject);
90
+ const date = String(draft.date || '').trim();
91
+ if (!date || !project || !tasks.length) {
92
+ return null;
93
+ }
94
+
95
+ return {
96
+ id: String(draft.id || createDraftId()),
97
+ createdAt: String(draft.createdAt || new Date().toISOString()),
98
+ updatedAt: String(draft.updatedAt || draft.createdAt || new Date().toISOString()),
99
+ cwd: String(draft.cwd || process.cwd()),
100
+ date,
101
+ project,
102
+ tasks,
103
+ latestPreviewStatus: draft.latestPreviewStatus ? String(draft.latestPreviewStatus) : undefined
104
+ };
105
+ }
106
+
107
+ export function formatManualDraftLabel(draft) {
108
+ const taskCount = draft.tasks?.length || 0;
109
+ const updated = draft.updatedAt ? ` · updated ${draft.updatedAt.slice(0, 10)}` : '';
110
+ return `${draft.date} · ${draft.project?.projectName || 'Unknown project'} · ${taskCount} task${taskCount === 1 ? '' : 's'}${updated}`;
111
+ }
112
+
113
+ async function writeDrafts(drafts, path) {
114
+ await atomicWriteFile(path, `${JSON.stringify({ drafts }, null, 2)}\n`);
115
+ }
116
+
117
+ function sanitizeProject(project = {}) {
118
+ const projectName = String(project.projectName || '').trim();
119
+ const projectMemberId = normalizeOptionalId(project.projectMemberId);
120
+ const projectId = normalizeOptionalId(project.projectId);
121
+ if (!projectName && projectMemberId === undefined && projectId === undefined) {
122
+ return null;
123
+ }
124
+
125
+ return {
126
+ projectMemberId,
127
+ projectId,
128
+ projectName: projectName || 'Unknown project',
129
+ booked: project.booked === false ? false : project.booked === true ? true : undefined,
130
+ bookedHours: Number.isFinite(Number(project.bookedHours)) ? Number(project.bookedHours) : undefined,
131
+ loggedHours: Number.isFinite(Number(project.loggedHours)) ? Number(project.loggedHours) : undefined
132
+ };
133
+ }
134
+
135
+ function normalizeOptionalId(value) {
136
+ if (value === undefined || value === null || value === '') {
137
+ return undefined;
138
+ }
139
+ const number = Number(value);
140
+ return Number.isFinite(number) ? number : String(value);
141
+ }
142
+
143
+ function createDraftId() {
144
+ return `draft_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
145
+ }
146
+
147
+ function sortNewestFirst(left, right) {
148
+ return String(right.updatedAt || '').localeCompare(String(left.updatedAt || ''));
149
+ }
150
+
151
+ function normalizeCwd(value) {
152
+ const text = String(value || '').trim();
153
+ return text ? resolve(text) : '';
154
+ }