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.
- package/LICENSE +11 -0
- package/README.md +330 -2
- package/RELEASE.md +89 -0
- package/bin/logwork.mjs +104 -0
- package/commands/auth.mjs +227 -0
- package/commands/commits.mjs +82 -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 +508 -0
- package/commands/skill.mjs +91 -0
- package/commands/tui.mjs +50 -0
- package/commands/update.mjs +161 -0
- package/docs/advanced.md +334 -0
- package/docs/mcp-setup.md +368 -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/agent-skill.mjs +16 -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/commit-workflow.mjs +402 -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 +833 -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 +73 -4
- package/setup.sh +22 -0
- 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
|
+
}
|