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,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,74 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "logwork-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.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
|
+
"docs/",
|
|
42
|
+
"examples/mcp/",
|
|
43
|
+
"setup.sh",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"RELEASE.md"
|
|
47
|
+
],
|
|
48
|
+
"bin": {
|
|
49
|
+
"logwork": "bin/logwork.mjs",
|
|
50
|
+
"logwork-cli": "bin/logwork.mjs"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"start": "node bin/logwork.mjs",
|
|
54
|
+
"dry-run": "LOGWORK_DRY_RUN=1 node bin/logwork.mjs",
|
|
55
|
+
"mcp": "node bin/logwork.mjs mcp",
|
|
56
|
+
"setup": "node bin/logwork.mjs setup",
|
|
57
|
+
"audit:prod": "npm audit --omit=dev --audit-level=moderate",
|
|
58
|
+
"pack:check": "npm pack --dry-run",
|
|
59
|
+
"release:doctor": "node bin/logwork.mjs release doctor --full",
|
|
60
|
+
"release:check": "npm test && npm run audit:prod && npm run pack:check && git diff --check",
|
|
61
|
+
"test:windows-credential": "node --test --test-name-pattern=\"Windows Credential Manager stores, reads, and deletes a test credential\" test/credential-store.test.mjs",
|
|
62
|
+
"test": "node --test"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"@clack/prompts": "^1.5.0",
|
|
66
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
67
|
+
"chalk": "^5.6.2",
|
|
68
|
+
"ink": "^7.0.5",
|
|
69
|
+
"ink-select-input": "^6.2.0",
|
|
70
|
+
"ink-text-input": "^6.0.0",
|
|
71
|
+
"react": "^19.2.7",
|
|
72
|
+
"zod": "^4.4.3"
|
|
73
|
+
}
|
|
74
|
+
}
|
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 "$@"
|