@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usemeno/meno-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Command-line interface for Meno time tracking",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -12,7 +12,12 @@
12
12
  "dev": "tsup src/index.ts --format esm --watch",
13
13
  "prepublishOnly": "npm run build"
14
14
  },
15
- "keywords": ["meno", "time-tracking", "cli", "productivity"],
15
+ "keywords": [
16
+ "meno",
17
+ "time-tracking",
18
+ "cli",
19
+ "productivity"
20
+ ],
16
21
  "author": "",
17
22
  "license": "MIT",
18
23
  "dependencies": {
@@ -1,79 +1,104 @@
1
1
  import enquirer from "enquirer";
2
2
  import ora from "ora";
3
3
  import chalk from "chalk";
4
- import { setSelectedProject, clearSelectedProject, getSelectedProject } from "../config.js";
5
- import { apiRequest, ApiError, Project as ApiProject } from "../utils/api.js";
4
+ import { setSelectedTask, clearSelectedTask, getSelectedTask } from "../config.js";
5
+ import { getTasks, ApiError, Task } from "../utils/api.js";
6
+
7
+ function getStatusColor(status: string): (text: string) => string {
8
+ switch (status) {
9
+ case "Backlog": return chalk.gray;
10
+ case "Todo": return chalk.yellow;
11
+ case "InProgress": return chalk.cyan;
12
+ case "Review": return chalk.magenta;
13
+ case "Done": return chalk.green;
14
+ default: return chalk.white;
15
+ }
16
+ }
17
+
18
+ function formatTaskDisplay(task: Task, isSelected: boolean): string {
19
+ const prefix = isSelected ? "● " : " ";
20
+ const statusColor = getStatusColor(task.status);
21
+ const estimateText = task.estimatedHours ? ` • ${task.estimatedHours}h` : "";
22
+
23
+ return `${prefix}${chalk.bold(task.title)} ${chalk.dim("→")} ${task.project.name} ${statusColor(`[${task.status}]`)}${estimateText}`;
24
+ }
6
25
 
7
26
  export async function selectProject() {
8
- console.log(chalk.bold("\n📂 Select Project\n"));
27
+ console.log(chalk.bold("\n📋 Select Task\n"));
9
28
 
10
- const spinner = ora("Loading projects...").start();
29
+ const spinner = ora("Loading tasks...").start();
11
30
 
12
31
  try {
13
- const { projects } = await apiRequest<{ projects: ApiProject[] }>("/api/external/projects");
32
+ const tasks = await getTasks();
14
33
 
15
34
  spinner.stop();
16
35
 
17
- if (projects.length === 0) {
18
- console.log(chalk.yellow("No active projects found."));
19
- console.log(chalk.dim("Create a project in the web app first.\n"));
36
+ if (tasks.length === 0) {
37
+ console.log(chalk.yellow("No tasks found."));
38
+ console.log(chalk.dim("Create tasks in the Meno dashboard first.\n"));
20
39
  return;
21
40
  }
22
41
 
23
- const currentProject = getSelectedProject();
42
+ const currentTask = getSelectedTask();
24
43
 
25
- // Build choices
44
+ // Build choices - group by status or show all
26
45
  const choices = [
27
46
  {
28
47
  name: "clear",
29
48
  message: chalk.dim("[Clear Selection]"),
30
49
  },
31
- ...projects.map((project) => {
32
- const isSelected = currentProject?.id === project.id;
33
- const prefix = isSelected ? "● " : " ";
34
- const displayName = `${project.name} (${project.clientName}) • $${project.hourlyRate}/hr`;
50
+ ...tasks.map((task) => {
51
+ const isSelected = currentTask?.id === task.id;
35
52
 
36
53
  return {
37
- name: project.id,
38
- message: prefix + displayName,
39
- value: project,
54
+ name: task.id,
55
+ message: formatTaskDisplay(task, isSelected),
56
+ value: task,
40
57
  };
41
58
  }),
42
59
  ];
43
60
 
44
61
  const result = await enquirer.prompt({
45
62
  type: "select",
46
- name: "project",
47
- message: "Select a project:",
63
+ name: "task",
64
+ message: "Select a task:",
48
65
  choices,
49
66
  });
50
67
 
51
- const answer = (result as any).project;
68
+ const answer = (result as any).task;
52
69
 
53
70
  if (answer === "clear") {
54
- clearSelectedProject();
71
+ clearSelectedTask();
55
72
  console.log(chalk.green("\n✓ Selection cleared\n"));
56
73
  } else {
57
- // Find the full project object by ID
58
- const project = projects.find((p) => p.id === answer);
74
+ // Find the full task object by ID
75
+ const task = tasks.find((t) => t.id === answer);
59
76
 
60
- if (!project) {
61
- console.log(chalk.red("\n✗ Project not found\n"));
77
+ if (!task) {
78
+ console.log(chalk.red("\n✗ Task not found\n"));
62
79
  return;
63
80
  }
64
81
 
65
- setSelectedProject({
66
- id: project.id,
67
- name: project.name,
68
- clientName: project.clientName,
69
- hourlyRate: project.hourlyRate,
82
+ setSelectedTask({
83
+ id: task.id,
84
+ title: task.title,
85
+ projectId: task.projectId,
86
+ projectName: task.project.name,
87
+ status: task.status,
88
+ estimatedHours: task.estimatedHours,
89
+ hourlyRate: task.project.hourlyRate,
70
90
  });
91
+
92
+ const statusColor = getStatusColor(task.status);
71
93
  console.log(
72
- chalk.green(`\n✓ Selected: ${chalk.bold(project.name)} (${project.clientName}) • $${project.hourlyRate}/hr\n`)
94
+ chalk.green(`\n✓ Selected: ${chalk.bold(task.title)}\n`) +
95
+ chalk.dim(` Project: ${task.project.name} • $${task.project.hourlyRate}/hr\n`) +
96
+ chalk.dim(` Status: `) + statusColor(task.status) +
97
+ (task.estimatedHours ? chalk.dim(` • Estimated: ${task.estimatedHours}h\n`) : "\n")
73
98
  );
74
99
  }
75
100
  } catch (error) {
76
- spinner.fail(chalk.red("Failed to load projects"));
101
+ spinner.fail(chalk.red("Failed to load tasks"));
77
102
 
78
103
  if (error instanceof ApiError) {
79
104
  console.log(chalk.red(`\nError: ${error.message}\n`));
@@ -1,77 +1,112 @@
1
1
  import chalk from "chalk";
2
2
  import ora from "ora";
3
- import { getSelectedProject, getActiveTimer, setActiveTimer } from "../config.js";
4
- import { apiRequest, ApiError, Project } from "../utils/api.js";
3
+ import { getSelectedTask, getActiveTimer, setActiveTimer } from "../config.js";
4
+ import { getTasks, cliAction, ApiError, Task, CliStartResponse } from "../utils/api.js";
5
5
  import { formatDuration } from "../utils/timer.js";
6
6
 
7
- export async function startTimer(projectId?: string) {
7
+ export async function startTimer(taskId?: string) {
8
8
  // Check if timer is already running
9
9
  const existingTimer = getActiveTimer();
10
10
  if (existingTimer) {
11
11
  const elapsed = formatDuration(existingTimer.startTime);
12
+ const taskInfo = existingTimer.taskTitle || existingTimer.projectName;
12
13
  console.log(
13
14
  chalk.yellow(
14
- `\n⚠ Timer already running for ${chalk.bold(existingTimer.projectName)} (${elapsed})`
15
+ `\n⚠ Timer already running for ${chalk.bold(taskInfo)} (${elapsed})`
15
16
  )
16
17
  );
17
18
  console.log(chalk.dim(`Stop it first with ${chalk.bold("meno stop")} or discard with ${chalk.bold("meno stop --discard")}\n`));
18
19
  return;
19
20
  }
20
21
 
21
- let project: Project | undefined;
22
+ let task: Task | undefined;
22
23
 
23
- if (projectId) {
24
- // Start with explicit project ID
25
- const spinner = ora("Loading project...").start();
24
+ if (taskId) {
25
+ // Start with explicit task ID
26
+ const spinner = ora("Loading task...").start();
26
27
  try {
27
- const { projects } = await apiRequest<{ projects: Project[] }>("/api/external/projects");
28
- project = projects.find((p) => p.id === projectId);
28
+ const tasks = await getTasks();
29
+ task = tasks.find((t) => t.id === taskId);
29
30
  spinner.stop();
30
31
 
31
- if (!project) {
32
- console.log(chalk.red(`\n✗ Project not found: ${projectId}\n`));
32
+ if (!task) {
33
+ console.log(chalk.red(`\n✗ Task not found: ${taskId}\n`));
33
34
  return;
34
35
  }
35
36
  } catch (error) {
36
- spinner.fail(chalk.red("Failed to load project"));
37
+ spinner.fail(chalk.red("Failed to load task"));
37
38
  if (error instanceof ApiError) {
38
39
  console.log(chalk.red(`\nError: ${error.message}\n`));
39
40
  }
40
41
  return;
41
42
  }
42
43
  } else {
43
- // Use selected project
44
- const selected = getSelectedProject();
44
+ // Use selected task
45
+ const selected = getSelectedTask();
45
46
  if (!selected) {
46
- console.log(chalk.yellow(`\n⚠ No project selected.`));
47
- console.log(chalk.dim(`Run ${chalk.bold("meno select")} first or provide a project ID: ${chalk.bold("meno start <project-id>")}\n`));
47
+ console.log(chalk.yellow(`\n⚠ No task selected.`));
48
+ console.log(chalk.dim(`Run ${chalk.bold("meno select")} first or provide a task ID: ${chalk.bold("meno start <task-id>")}\n`));
48
49
  return;
49
50
  }
50
51
 
51
- project = {
52
- id: selected.id,
53
- name: selected.name,
54
- clientName: selected.clientName,
55
- clientCompany: "",
56
- hourlyRate: selected.hourlyRate,
57
- taxRate: 0,
58
- weeklyHourLimit: 0,
59
- hoursUsed: 0,
60
- };
52
+ // Fetch full task details to ensure we have latest status
53
+ const spinner = ora("Starting timer...").start();
54
+ try {
55
+ const tasks = await getTasks();
56
+ task = tasks.find((t) => t.id === selected.id);
57
+ spinner.stop();
58
+
59
+ if (!task) {
60
+ console.log(chalk.red(`\n✗ Selected task not found. It may have been deleted.\n`));
61
+ return;
62
+ }
63
+ } catch (error) {
64
+ spinner.fail(chalk.red("Failed to load task"));
65
+ if (error instanceof ApiError) {
66
+ console.log(chalk.red(`\nError: ${error.message}\n`));
67
+ }
68
+ return;
69
+ }
61
70
  }
62
71
 
63
- // Start the timer
64
- const startTime = new Date().toISOString();
65
- setActiveTimer({
66
- projectId: project.id,
67
- projectName: project.name,
68
- startTime,
69
- });
72
+ // Call API to start timer (API will auto-stop any running timer)
73
+ const spinner = ora("Starting timer...").start();
74
+
75
+ try {
76
+ const response = await cliAction("start", { taskId: task.id }) as CliStartResponse;
77
+
78
+ spinner.succeed(chalk.green("Timer started"));
79
+
80
+ // Store timer info locally for offline reference
81
+ const startTime = response.timer?.startTime || new Date().toISOString();
82
+ setActiveTimer({
83
+ projectId: task.projectId,
84
+ projectName: task.project.name,
85
+ startTime,
86
+ taskId: task.id,
87
+ taskTitle: task.title,
88
+ });
70
89
 
71
- const timestamp = new Date().toLocaleTimeString();
72
- console.log(
73
- chalk.green(
74
- `\n⏱️ Started: ${chalk.bold(project.name)} • $${project.hourlyRate}/hr • [${timestamp}]\n`
75
- )
76
- );
90
+ const timestamp = new Date(startTime).toLocaleTimeString();
91
+ console.log(
92
+ chalk.green(
93
+ `\n⏱️ ${chalk.bold(task.title)}\n` +
94
+ chalk.dim(` ${task.project.name} • $${task.project.hourlyRate}/hr • [${timestamp}]`)
95
+ )
96
+ );
97
+
98
+ if (task.estimatedHours) {
99
+ console.log(chalk.dim(` Estimated: ${task.estimatedHours}h\n`));
100
+ } else {
101
+ console.log();
102
+ }
103
+ } catch (error) {
104
+ spinner.fail(chalk.red("Failed to start timer"));
105
+
106
+ if (error instanceof ApiError) {
107
+ console.log(chalk.red(`\nError: ${error.message}\n`));
108
+ } else {
109
+ console.log(chalk.red(`\nUnexpected error: ${(error as Error).message}\n`));
110
+ }
111
+ }
77
112
  }
@@ -1,36 +1,58 @@
1
1
  import chalk from "chalk";
2
2
  import ora from "ora";
3
- import { getSelectedProject, getActiveTimer } from "../config.js";
3
+ import { getSelectedTask, getActiveTimer } from "../config.js";
4
4
  import { apiRequest, ApiError, Stats } from "../utils/api.js";
5
5
  import { formatDuration, calculateTimerValue } from "../utils/timer.js";
6
6
 
7
+ function getStatusColor(status: string): (text: string) => string {
8
+ switch (status) {
9
+ case "Backlog": return chalk.gray;
10
+ case "Todo": return chalk.yellow;
11
+ case "InProgress": return chalk.cyan;
12
+ case "Review": return chalk.magenta;
13
+ case "Done": return chalk.green;
14
+ default: return chalk.white;
15
+ }
16
+ }
17
+
7
18
  export async function showStatus() {
8
19
  console.log(chalk.bold("\n📊 Meno Status\n"));
9
20
 
10
- const selected = getSelectedProject();
21
+ const selectedTask = getSelectedTask();
11
22
  const timer = getActiveTimer();
12
23
 
13
- // Show selected project
14
- if (selected) {
24
+ // Show selected task
25
+ if (selectedTask) {
26
+ const statusColor = getStatusColor(selectedTask.status);
15
27
  console.log(
16
28
  chalk.cyan(
17
- `📌 Selected: ${chalk.bold(selected.name)} (${selected.clientName}) • $${selected.hourlyRate}/hr`
29
+ `📌 Selected: ${chalk.bold(selectedTask.title)}`
18
30
  )
19
31
  );
32
+ console.log(
33
+ chalk.dim(
34
+ ` ${selectedTask.projectName} • $${selectedTask.hourlyRate}/hr • `
35
+ ) + statusColor(selectedTask.status)
36
+ );
37
+ if (selectedTask.estimatedHours) {
38
+ console.log(chalk.dim(` Estimated: ${selectedTask.estimatedHours}h`));
39
+ }
20
40
  }
21
41
 
22
42
  // Show active timer
23
43
  if (timer) {
24
44
  const elapsed = formatDuration(timer.startTime);
25
- const value = calculateTimerValue(timer.startTime, selected?.hourlyRate || 0);
45
+ const taskInfo = timer.taskTitle || timer.projectName;
46
+ const rate = selectedTask?.hourlyRate || 0;
47
+ const value = calculateTimerValue(timer.startTime, rate);
26
48
  console.log(
27
49
  chalk.green(
28
- `⏱️ Running: ${chalk.bold(timer.projectName)} • ${elapsed} • $${value.toFixed(2)}`
50
+ `⏱️ Running: ${chalk.bold(taskInfo)} • ${elapsed} • $${value.toFixed(2)}`
29
51
  )
30
52
  );
31
53
  }
32
54
 
33
- if (selected || timer) {
55
+ if (selectedTask || timer) {
34
56
  console.log(); // Add spacing
35
57
  }
36
58
 
@@ -1,16 +1,18 @@
1
1
  import enquirer from "enquirer";
2
2
  import chalk from "chalk";
3
3
  import ora from "ora";
4
- import { getActiveTimer, clearActiveTimer, getSelectedProject } from "../config.js";
5
- import { apiRequest, ApiError } from "../utils/api.js";
6
- import { formatDuration, calculateElapsedHours, parseDuration } from "../utils/timer.js";
4
+ import { getActiveTimer, clearActiveTimer } from "../config.js";
5
+ import { cliAction, ApiError, CliStopResponse } from "../utils/api.js";
6
+ import { formatDuration } from "../utils/timer.js";
7
+ import { getGitInfo, isGitRepository } from "../utils/git.js";
7
8
 
8
9
  interface StopOptions {
9
10
  discard?: boolean;
10
11
  description?: string;
11
12
  noConfirm?: boolean;
12
13
  yes?: boolean;
13
- adjust?: string;
14
+ commit?: string;
15
+ repo?: string;
14
16
  }
15
17
 
16
18
  export async function stopTimer(options: StopOptions) {
@@ -29,42 +31,22 @@ export async function stopTimer(options: StopOptions) {
29
31
  return;
30
32
  }
31
33
 
32
- // Calculate duration (or use adjusted duration)
33
- let duration: number;
34
- let formatted: string;
34
+ const taskInfo = timer.taskTitle || timer.projectName;
35
+ const elapsed = formatDuration(timer.startTime);
35
36
 
36
- if (options.adjust) {
37
- // Parse adjusted duration
38
- duration = parseDuration(options.adjust);
39
- if (duration === 0) {
40
- console.log(chalk.red(`\n✗ Invalid duration format: ${options.adjust}\n`));
41
- console.log(chalk.dim(`Use format like: 1.5h, 90m, 45\n`));
42
- return;
43
- }
44
- const hours = Math.floor(duration);
45
- const minutes = Math.round((duration - hours) * 60);
46
- formatted = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
47
- console.log(chalk.yellow(`\n⚠ Adjusted duration from timer\n`));
48
- } else {
49
- duration = calculateElapsedHours(timer.startTime);
50
- formatted = formatDuration(timer.startTime);
51
- }
52
-
53
- // Get project details for rate calculation
54
- const project = getSelectedProject();
55
- const hourlyRate = project?.hourlyRate || 0;
56
- const amount = duration * hourlyRate;
57
-
58
- console.log(chalk.bold(`\n⏱️ Duration: ${formatted} • $${amount.toFixed(2)}\n`));
37
+ console.log(chalk.bold(`\n⏱️ Stopping: ${taskInfo}`));
38
+ console.log(chalk.dim(`Duration: ${elapsed}\n`));
59
39
 
60
40
  let description: string;
61
41
  let shouldLog = true;
42
+ let commitHash: string | undefined;
43
+ let repoUrl: string | undefined;
62
44
 
63
45
  try {
64
46
  // Get description (from flag or prompt)
65
47
  if (options.description) {
66
48
  description = options.description;
67
- console.log(chalk.dim(`Description: ${description}\n`));
49
+ console.log(chalk.dim(`Description: ${description}`));
68
50
  } else {
69
51
  const descResult = await enquirer.prompt({
70
52
  type: "input",
@@ -81,6 +63,40 @@ export async function stopTimer(options: StopOptions) {
81
63
  description = (descResult as any).description;
82
64
  }
83
65
 
66
+ // Handle git integration
67
+ if (options.commit && options.repo) {
68
+ // Manual git info provided
69
+ commitHash = options.commit;
70
+ repoUrl = options.repo;
71
+ console.log(chalk.dim(`\n🔗 Evidence: ${commitHash.substring(0, 7)} @ ${repoUrl}`));
72
+ } else if (isGitRepository()) {
73
+ // Auto-detect git info and prompt
74
+ const gitInfo = getGitInfo();
75
+
76
+ if (gitInfo) {
77
+ const skipConfirm = options.noConfirm || options.yes;
78
+
79
+ if (skipConfirm) {
80
+ // Auto-include if -y flag
81
+ commitHash = gitInfo.commitHash;
82
+ repoUrl = gitInfo.repoUrl;
83
+ console.log(chalk.dim(`\n🔗 Evidence: ${commitHash.substring(0, 7)} @ ${repoUrl}`));
84
+ } else {
85
+ const gitResult = await enquirer.prompt({
86
+ type: "confirm",
87
+ name: "includeGit",
88
+ message: `Include latest commit (${gitInfo.commitHash.substring(0, 7)}) as evidence?`,
89
+ initial: true,
90
+ });
91
+
92
+ if ((gitResult as any).includeGit) {
93
+ commitHash = gitInfo.commitHash;
94
+ repoUrl = gitInfo.repoUrl;
95
+ }
96
+ }
97
+ }
98
+ }
99
+
84
100
  // Confirm logging (unless --no-confirm or -y flag)
85
101
  const skipConfirm = options.noConfirm || options.yes;
86
102
  if (!skipConfirm) {
@@ -94,36 +110,50 @@ export async function stopTimer(options: StopOptions) {
94
110
 
95
111
  if (!shouldLog) {
96
112
  console.log(chalk.yellow("\n✗ Entry not logged\n"));
113
+ clearActiveTimer();
97
114
  return;
98
115
  }
99
116
  }
100
117
 
101
- // Log the entry
118
+ // Call API to stop timer
102
119
  const spinner = ora("Logging entry...").start();
103
120
 
104
121
  try {
105
- const response = await apiRequest("/api/external/time-entries", {
106
- method: "POST",
107
- body: JSON.stringify({
108
- projectId: timer.projectId,
109
- description: description,
110
- duration: Math.round(duration * 100) / 100, // Round to 2 decimals
111
- date: new Date().toISOString().split("T")[0], // Today
112
- billable: true,
113
- }),
114
- });
122
+ const response = await cliAction("stop", {
123
+ description,
124
+ commitHash,
125
+ repoUrl,
126
+ }) as CliStopResponse;
115
127
 
116
128
  clearActiveTimer();
117
- spinner.succeed(chalk.green(`✓ Logged ${duration.toFixed(2)} hours to ${timer.projectName}`));
118
- console.log(chalk.dim(`\nEntry created successfully\n`));
129
+
130
+ if (response.entry) {
131
+ const hours = response.entry.duration.toFixed(2);
132
+ const amount = response.entry.amount.toFixed(2);
133
+
134
+ spinner.succeed(chalk.green(`✓ Logged ${hours} hours ($${amount})`));
135
+
136
+ if (response.evidence) {
137
+ console.log(chalk.dim(`📎 Evidence: ${response.evidence.commitHash.substring(0, 7)}`));
138
+ }
139
+
140
+ console.log();
141
+ } else {
142
+ spinner.succeed(chalk.green("✓ Timer stopped"));
143
+ console.log();
144
+ }
119
145
  } catch (error) {
120
- spinner.fail(chalk.red("Failed to log entry"));
146
+ spinner.fail(chalk.red("Failed to stop timer"));
121
147
 
122
148
  if (error instanceof ApiError) {
123
149
  console.log(chalk.red(`\nError: ${error.message}\n`));
150
+ console.log(chalk.yellow(`Timer cleared locally. You may need to check the dashboard.\n`));
124
151
  } else {
125
152
  console.log(chalk.red(`\nUnexpected error: ${(error as Error).message}\n`));
126
153
  }
154
+
155
+ // Clear local timer even on error to avoid stuck state
156
+ clearActiveTimer();
127
157
  }
128
158
  } catch (error) {
129
159
  // User cancelled
package/src/config.ts CHANGED
@@ -9,11 +9,22 @@ interface Config {
9
9
  clientName: string;
10
10
  hourlyRate: number;
11
11
  };
12
+ selectedTask?: {
13
+ id: string;
14
+ title: string;
15
+ projectId: string;
16
+ projectName: string;
17
+ status: string;
18
+ estimatedHours?: number;
19
+ hourlyRate: number;
20
+ };
12
21
  activeTimer?: {
13
22
  projectId: string;
14
23
  projectName: string;
15
24
  startTime: string;
16
25
  description?: string;
26
+ taskId?: string;
27
+ taskTitle?: string;
17
28
  };
18
29
  }
19
30
 
@@ -30,7 +41,7 @@ export function setApiKey(key: string): void {
30
41
  }
31
42
 
32
43
  export function getBaseUrl(): string {
33
- return config.get("baseUrl") || process.env.MENO_BASE_URL || "http://localhost:3000";
44
+ return config.get("baseUrl") || process.env.MENO_BASE_URL || "https://menohq.app";
34
45
  }
35
46
 
36
47
  export function setBaseUrl(url: string): void {
@@ -71,6 +82,26 @@ export function clearActiveTimer(): void {
71
82
  config.delete("activeTimer");
72
83
  }
73
84
 
85
+ export function getSelectedTask() {
86
+ return config.get("selectedTask");
87
+ }
88
+
89
+ export function setSelectedTask(task: {
90
+ id: string;
91
+ title: string;
92
+ projectId: string;
93
+ projectName: string;
94
+ status: string;
95
+ estimatedHours?: number;
96
+ hourlyRate: number;
97
+ }): void {
98
+ config.set("selectedTask", task);
99
+ }
100
+
101
+ export function clearSelectedTask(): void {
102
+ config.delete("selectedTask");
103
+ }
104
+
74
105
  export function clearConfig(): void {
75
106
  config.clear();
76
107
  }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import { startTimer } from "./commands/start.js";
7
7
  import { stopTimer } from "./commands/stop.js";
8
8
  import { logTime } from "./commands/log.js";
9
9
  import { showStatus } from "./commands/status.js";
10
+ import { printLogoAscii } from "./logo-ascii.js";
10
11
 
11
12
  const program = new Command();
12
13
 
@@ -15,6 +16,9 @@ program
15
16
  .description("CLI for Meno time tracking")
16
17
  .version("0.1.0");
17
18
 
19
+ // Print ASCII logo at CLI startup (sharp used if installed, otherwise a fallback)
20
+ printLogoAscii().catch(() => {});
21
+
18
22
  program
19
23
  .command("login")
20
24
  .description("Authenticate with your Meno API key")
@@ -22,12 +26,12 @@ program
22
26
 
23
27
  program
24
28
  .command("select")
25
- .description("Select a project to work on")
29
+ .description("Select a task to work on")
26
30
  .action(selectProject);
27
31
 
28
32
  program
29
- .command("start [project-id]")
30
- .description("Start a timer on selected project or specific project ID")
33
+ .command("start [task-id]")
34
+ .description("Start a timer on selected task or specific task ID")
31
35
  .action(startTimer);
32
36
 
33
37
  program
@@ -37,7 +41,8 @@ program
37
41
  .option("-d, --description <text>", "Entry description (skip prompt)")
38
42
  .option("-y, --yes", "Auto-confirm (skip confirmation prompt)")
39
43
  .option("--no-confirm", "Skip confirmation prompt")
40
- .option("--adjust <duration>", "Override calculated duration (e.g., 1.5h, 45m)")
44
+ .option("--commit <hash>", "Git commit hash for evidence")
45
+ .option("--repo <url>", "Git repository URL for evidence")
41
46
  .action(stopTimer);
42
47
 
43
48
  program
@@ -49,7 +54,7 @@ program
49
54
 
50
55
  program
51
56
  .command("status")
52
- .description("Show selected project, timer status, and unbilled stats")
57
+ .description("Show selected task, timer status, and unbilled stats")
53
58
  .action(showStatus);
54
59
 
55
60
  program.parse();