@usemeno/meno-cli 0.1.0 → 0.1.1
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/Asset 1.svg +8 -0
- package/README.md +1 -1
- package/dist/index.js +349 -128
- package/package.json +7 -2
- package/src/commands/select.ts +57 -32
- package/src/commands/start.ts +75 -40
- package/src/commands/status.ts +30 -8
- package/src/commands/stop.ts +75 -45
- package/src/config.ts +32 -1
- package/src/index.ts +10 -5
- package/src/logo-ascii.ts +61 -0
- package/src/utils/api.ts +79 -0
- package/src/utils/git.ts +62 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
const SVG_EMBED = `<?xml version="1.0" encoding="UTF-8"?>
|
|
5
|
+
<svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.44 45.4">
|
|
6
|
+
<g id="MenoLogo">
|
|
7
|
+
<path d="M43.78,28.72l-3.22-14.3-13.31,13.4c-2.15-1.65-3.92-3.4-5.43-5.75L42.15.78c.61-.64,3.06-1.02,3.67-.61s1.52,1.79,1.66,2.64l4.05,25.48-7.75.43Z"/>
|
|
8
|
+
<path d="M19.61,20.03l-6.25-5.12-2.48,13.76-8.11-.12L7.13,1.25c.22-1.13,4.18-1.52,5.05-.57l12.62,13.69-5.2,5.66Z"/>
|
|
9
|
+
<path d="M55.44,38.35c0,3.89-3.15,7.04-7.04,7.04-2.53,0-4.77-1.35-6.01-3.37H.36c-.4-1.61-.44-3.89-.23-6.03l41.85-.55c1.1-2.45,3.57-4.14,6.42-4.14,3.9,0,7.04,3.15,7.04,7.04Z"/>
|
|
10
|
+
</g>
|
|
11
|
+
</svg>`;
|
|
12
|
+
|
|
13
|
+
function fallbackAscii() {
|
|
14
|
+
return [
|
|
15
|
+
" ____ __ ",
|
|
16
|
+
" /\\ / __ \\/ /___ _ _ ",
|
|
17
|
+
" / \\ _ __ / / / / / __ \\ | | |",
|
|
18
|
+
" / /\\ \\ | '_ \\ / /_/ / / /_/ / |_| |",
|
|
19
|
+
" / ____ \\| | | |\\____/_/\\____/\\__,_|",
|
|
20
|
+
" /_/ \\_\\_| |_| ",
|
|
21
|
+
"",
|
|
22
|
+
" M E N O (logo) ",
|
|
23
|
+
].join('\n');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function printLogoAscii(width = 64) {
|
|
27
|
+
try {
|
|
28
|
+
const sharpImport = await import("sharp");
|
|
29
|
+
const sharp: any = sharpImport.default ?? sharpImport;
|
|
30
|
+
|
|
31
|
+
const svgBuffer = Buffer.from(SVG_EMBED, "utf8");
|
|
32
|
+
const img = sharp(svgBuffer).resize({ width }).flatten({ background: { r: 255, g: 255, b: 255 } }).grayscale();
|
|
33
|
+
const { data, info } = await img.raw().toBuffer({ resolveWithObject: true });
|
|
34
|
+
|
|
35
|
+
const chars = " .,:;i1tfLCG08@";
|
|
36
|
+
const rows: string[] = [];
|
|
37
|
+
const aspect = 0.5;
|
|
38
|
+
const rowStep = Math.max(1, Math.round(1 / aspect));
|
|
39
|
+
|
|
40
|
+
for (let y = 0; y < info.height; y += rowStep) {
|
|
41
|
+
let line = "";
|
|
42
|
+
for (let x = 0; x < info.width; x++) {
|
|
43
|
+
const idx = y * info.width + x;
|
|
44
|
+
const val = data[idx];
|
|
45
|
+
const charIdx = Math.floor((1 - val / 255) * (chars.length - 1));
|
|
46
|
+
line += chars[charIdx];
|
|
47
|
+
}
|
|
48
|
+
rows.push(line);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(rows.join("\n"));
|
|
52
|
+
return;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.log(fallbackAscii());
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (require.main === module) {
|
|
60
|
+
printLogoAscii().catch(() => {});
|
|
61
|
+
}
|
package/src/utils/api.ts
CHANGED
|
@@ -111,3 +111,82 @@ export interface Stats {
|
|
|
111
111
|
entriesCount: number;
|
|
112
112
|
currency: string;
|
|
113
113
|
}
|
|
114
|
+
|
|
115
|
+
export interface Task {
|
|
116
|
+
id: string;
|
|
117
|
+
title: string;
|
|
118
|
+
description?: string;
|
|
119
|
+
projectId: string;
|
|
120
|
+
status: "Backlog" | "Todo" | "InProgress" | "Review" | "Done";
|
|
121
|
+
estimatedHours?: number;
|
|
122
|
+
priority?: string;
|
|
123
|
+
project: {
|
|
124
|
+
id: string;
|
|
125
|
+
name: string;
|
|
126
|
+
hourlyRate: number;
|
|
127
|
+
client?: {
|
|
128
|
+
name: string;
|
|
129
|
+
company?: string;
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface CliStartResponse {
|
|
135
|
+
success: boolean;
|
|
136
|
+
message: string;
|
|
137
|
+
timer?: {
|
|
138
|
+
id: string;
|
|
139
|
+
taskId: string;
|
|
140
|
+
startTime: string;
|
|
141
|
+
};
|
|
142
|
+
task?: Task;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export interface CliStopResponse {
|
|
146
|
+
success: boolean;
|
|
147
|
+
message: string;
|
|
148
|
+
entry?: {
|
|
149
|
+
id: string;
|
|
150
|
+
duration: number;
|
|
151
|
+
amount: number;
|
|
152
|
+
description: string;
|
|
153
|
+
};
|
|
154
|
+
evidence?: {
|
|
155
|
+
id: string;
|
|
156
|
+
commitHash: string;
|
|
157
|
+
repoUrl: string;
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface CliLogResponse {
|
|
162
|
+
success: boolean;
|
|
163
|
+
message: string;
|
|
164
|
+
entry?: TimeEntry;
|
|
165
|
+
task?: Task;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Fetch all tasks from Kanban board
|
|
169
|
+
export async function getTasks(): Promise<Task[]> {
|
|
170
|
+
const response = await apiRequest<{ tasks: Task[] }>("/api/kanban/tasks");
|
|
171
|
+
return response.tasks || [];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// CLI actions: start, stop, log
|
|
175
|
+
export async function cliAction(
|
|
176
|
+
action: "start" | "stop" | "log",
|
|
177
|
+
payload: {
|
|
178
|
+
taskId?: string;
|
|
179
|
+
description?: string;
|
|
180
|
+
commitHash?: string;
|
|
181
|
+
repoUrl?: string;
|
|
182
|
+
done?: boolean;
|
|
183
|
+
}
|
|
184
|
+
): Promise<CliStartResponse | CliStopResponse | CliLogResponse> {
|
|
185
|
+
return await apiRequest("/api/cli", {
|
|
186
|
+
method: "POST",
|
|
187
|
+
body: JSON.stringify({
|
|
188
|
+
action,
|
|
189
|
+
...payload,
|
|
190
|
+
}),
|
|
191
|
+
});
|
|
192
|
+
}
|
package/src/utils/git.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
|
|
3
|
+
export interface GitInfo {
|
|
4
|
+
commitHash: string;
|
|
5
|
+
repoUrl: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get current git commit hash and repository URL
|
|
10
|
+
* Returns null if not in a git repository or if git commands fail
|
|
11
|
+
*/
|
|
12
|
+
export function getGitInfo(): GitInfo | null {
|
|
13
|
+
try {
|
|
14
|
+
// Get commit hash
|
|
15
|
+
const commitHash = execSync("git rev-parse HEAD", {
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
stdio: ["pipe", "pipe", "ignore"], // Suppress stderr
|
|
18
|
+
}).trim();
|
|
19
|
+
|
|
20
|
+
// Get remote URL
|
|
21
|
+
let repoUrl = execSync("git config --get remote.origin.url", {
|
|
22
|
+
encoding: "utf8",
|
|
23
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
24
|
+
}).trim();
|
|
25
|
+
|
|
26
|
+
// Convert SSH URLs to HTTPS format
|
|
27
|
+
if (repoUrl.startsWith("git@github.com:")) {
|
|
28
|
+
repoUrl = repoUrl
|
|
29
|
+
.replace("git@github.com:", "https://github.com/")
|
|
30
|
+
.replace(/\.git$/, "");
|
|
31
|
+
} else if (repoUrl.endsWith(".git")) {
|
|
32
|
+
repoUrl = repoUrl.replace(/\.git$/, "");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!commitHash || !repoUrl) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
commitHash,
|
|
41
|
+
repoUrl,
|
|
42
|
+
};
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// Not in a git repository or git not available
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if current directory is inside a git repository
|
|
51
|
+
*/
|
|
52
|
+
export function isGitRepository(): boolean {
|
|
53
|
+
try {
|
|
54
|
+
execSync("git rev-parse --is-inside-work-tree", {
|
|
55
|
+
encoding: "utf8",
|
|
56
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
57
|
+
});
|
|
58
|
+
return true;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|