@usemeno/meno-cli 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -32,13 +32,13 @@ Paste your API key when prompted.
32
32
 
33
33
  ### `meno select`
34
34
 
35
- Select a project to work on. The selected project will be used for `meno start`.
35
+ Select a project first, then select a task to work on. The selected task will be used for `meno start`.
36
36
 
37
37
  ```bash
38
38
  meno select
39
39
  ```
40
40
 
41
- Use arrow keys to select a project, or choose "[Clear Selection]" to unset.
41
+ Use arrow keys to select a project and task, or choose "[Clear Selection]" to unset both.
42
42
 
43
43
  ### `meno start [project-id]`
44
44
 
@@ -92,9 +92,10 @@ meno status
92
92
  ## Workflow Example
93
93
 
94
94
  ```bash
95
- # Morning: Select your project
95
+ # Morning: Select your project and task
96
96
  meno select
97
- > Website Redesign (Acme Corp) • $150/hr
97
+ > Website Redesign (Acme Corp)
98
+ > Build hero section → Website Redesign [InProgress]
98
99
 
99
100
  # Start working
100
101
  meno start
@@ -119,8 +120,8 @@ Config is stored at `~/.config/meno-cli/config.json` (cross-platform).
119
120
 
120
121
  Contains:
121
122
  - `apiKey` - Your Meno API key
122
- - `baseUrl` - API endpoint (default: http://localhost:3000)
123
123
  - `selectedProject` - Currently selected project
124
+ - `selectedTask` - Currently selected task
124
125
  - `activeTimer` - Running timer state (persists across restarts)
125
126
 
126
127
  ## Troubleshooting
@@ -132,7 +133,7 @@ Run `meno login` to authenticate.
132
133
  Run `meno select` to choose a project, or pass `--project <id>` to commands.
133
134
 
134
135
  **Connection errors:**
135
- Check your `baseUrl` in config. For production, use `https://menohq.app`.
136
+ Check your internet connection. CLI uses `https://menohq.app`.
136
137
 
137
138
  ## Development
138
139
 
package/dist/index.js CHANGED
@@ -1,10 +1,4 @@
1
1
  #!/usr/bin/env node
2
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
- }) : x)(function(x) {
5
- if (typeof require !== "undefined") return require.apply(this, arguments);
6
- throw Error('Dynamic require of "' + x + '" is not supported');
7
- });
8
2
 
9
3
  // src/index.ts
10
4
  import { Command } from "commander";
@@ -26,14 +20,17 @@ function setApiKey(key) {
26
20
  config.set("apiKey", key);
27
21
  }
28
22
  function getBaseUrl() {
29
- return config.get("baseUrl") || process.env.MENO_BASE_URL || "https://menohq.app";
30
- }
31
- function setBaseUrl(url) {
32
- config.set("baseUrl", url);
23
+ return "https://menohq.app";
33
24
  }
34
25
  function getSelectedProject() {
35
26
  return config.get("selectedProject");
36
27
  }
28
+ function setSelectedProject(project) {
29
+ config.set("selectedProject", project);
30
+ }
31
+ function clearSelectedProject() {
32
+ config.delete("selectedProject");
33
+ }
37
34
  function getActiveTimer() {
38
35
  return config.get("activeTimer");
39
36
  }
@@ -148,19 +145,9 @@ async function login() {
148
145
  }
149
146
  });
150
147
  const apiKey = apiKeyPrompt.apiKey;
151
- const baseUrlPrompt = await enquirer.prompt({
152
- type: "input",
153
- name: "baseUrl",
154
- message: "Base URL (press Enter for default):",
155
- initial: "http://localhost:3000"
156
- });
157
- const baseUrl = baseUrlPrompt.baseUrl;
158
148
  const spinner = ora("Validating API key...").start();
159
149
  try {
160
150
  setApiKey(apiKey);
161
- if (baseUrl) {
162
- setBaseUrl(baseUrl);
163
- }
164
151
  await apiRequest("/api/external/projects");
165
152
  spinner.succeed(chalk.green("\u2713 Logged in successfully!"));
166
153
  console.log(chalk.dim(`
@@ -213,67 +200,119 @@ function formatTaskDisplay(task, isSelected) {
213
200
  return `${prefix}${chalk2.bold(task.title)} ${chalk2.dim("\u2192")} ${task.project.name} ${statusColor(`[${task.status}]`)}${estimateText}`;
214
201
  }
215
202
  async function selectProject() {
216
- console.log(chalk2.bold("\n\u{1F4CB} Select Task\n"));
217
- const spinner = ora2("Loading tasks...").start();
203
+ console.log(chalk2.bold("\n\u{1F4CB} Select Project & Task\n"));
204
+ const spinner = ora2("Loading projects and tasks...").start();
218
205
  try {
219
- const tasks = await getTasks();
206
+ const [tasks, projectsResponse] = await Promise.all([
207
+ getTasks(),
208
+ apiRequest("/api/external/projects")
209
+ ]);
210
+ const projects = projectsResponse.projects || [];
220
211
  spinner.stop();
221
- if (tasks.length === 0) {
222
- console.log(chalk2.yellow("No tasks found."));
223
- console.log(chalk2.dim("Create tasks in the Meno dashboard first.\n"));
212
+ if (projects.length === 0) {
213
+ console.log(chalk2.yellow("No active projects found."));
214
+ console.log(chalk2.dim("Create projects in the Meno dashboard first.\n"));
224
215
  return;
225
216
  }
226
- const currentTask = getSelectedTask();
227
- const choices = [
217
+ const currentSelection = getSelectedTask();
218
+ const projectChoices = [
228
219
  {
229
220
  name: "clear",
230
221
  message: chalk2.dim("[Clear Selection]")
231
222
  },
232
- ...tasks.map((task) => {
233
- const isSelected = currentTask?.id === task.id;
223
+ ...projects.map((project) => {
224
+ const projectTaskCount = tasks.filter((task2) => task2.projectId === project.id).length;
234
225
  return {
235
- name: task.id,
236
- message: formatTaskDisplay(task, isSelected),
237
- value: task
226
+ name: project.id,
227
+ message: `${chalk2.bold(project.name)} ${chalk2.dim("\u2022")} ${project.clientName} ${chalk2.dim(`(${projectTaskCount} task${projectTaskCount === 1 ? "" : "s"})`)}`,
228
+ value: project
238
229
  };
239
230
  })
240
231
  ];
241
- const result = await enquirer2.prompt({
232
+ const projectResult = await enquirer2.prompt({
242
233
  type: "select",
243
- name: "task",
244
- message: "Select a task:",
245
- choices
234
+ name: "project",
235
+ message: "Select a project:",
236
+ choices: projectChoices
246
237
  });
247
- const answer = result.task;
248
- if (answer === "clear") {
238
+ const projectAnswer = projectResult.project;
239
+ if (projectAnswer === "clear") {
249
240
  clearSelectedTask();
241
+ clearSelectedProject();
250
242
  console.log(chalk2.green("\n\u2713 Selection cleared\n"));
251
- } else {
252
- const task = tasks.find((t) => t.id === answer);
253
- if (!task) {
254
- console.log(chalk2.red("\n\u2717 Task not found\n"));
255
- return;
256
- }
257
- setSelectedTask({
258
- id: task.id,
259
- title: task.title,
260
- projectId: task.projectId,
261
- projectName: task.project.name,
262
- status: task.status,
263
- estimatedHours: task.estimatedHours,
264
- hourlyRate: task.project.hourlyRate
265
- });
266
- const statusColor = getStatusColor(task.status);
267
- console.log(
268
- chalk2.green(`
243
+ return;
244
+ }
245
+ const selectedProject = projects.find((project) => project.id === projectAnswer);
246
+ if (!selectedProject) {
247
+ console.log(chalk2.red("\n\u2717 Project not found\n"));
248
+ return;
249
+ }
250
+ setSelectedProject({
251
+ id: selectedProject.id,
252
+ name: selectedProject.name,
253
+ clientName: selectedProject.clientName,
254
+ hourlyRate: selectedProject.hourlyRate
255
+ });
256
+ const projectTasks = tasks.filter((task2) => task2.projectId === selectedProject.id);
257
+ if (projectTasks.length === 0) {
258
+ clearSelectedTask();
259
+ console.log(chalk2.green(`
260
+ \u2713 Selected project: ${chalk2.bold(selectedProject.name)}`));
261
+ console.log(chalk2.dim("No tasks found for this project yet.\n"));
262
+ return;
263
+ }
264
+ const taskChoices = [
265
+ {
266
+ name: "clear_task",
267
+ message: chalk2.dim("[Clear Task Selection]")
268
+ },
269
+ ...projectTasks.map((task2) => {
270
+ const isSelected = currentSelection?.id === task2.id;
271
+ return {
272
+ name: task2.id,
273
+ message: formatTaskDisplay(task2, isSelected),
274
+ value: task2
275
+ };
276
+ })
277
+ ];
278
+ const taskResult = await enquirer2.prompt({
279
+ type: "select",
280
+ name: "task",
281
+ message: `Select a task in ${selectedProject.name}:`,
282
+ choices: taskChoices
283
+ });
284
+ const taskAnswer = taskResult.task;
285
+ if (taskAnswer === "clear_task") {
286
+ clearSelectedTask();
287
+ console.log(chalk2.green(`
288
+ \u2713 Project selected: ${chalk2.bold(selectedProject.name)}`));
289
+ console.log(chalk2.dim("Task selection cleared.\n"));
290
+ return;
291
+ }
292
+ const task = projectTasks.find((t) => t.id === taskAnswer);
293
+ if (!task) {
294
+ console.log(chalk2.red("\n\u2717 Task not found\n"));
295
+ return;
296
+ }
297
+ setSelectedTask({
298
+ id: task.id,
299
+ title: task.title,
300
+ projectId: task.projectId,
301
+ projectName: task.project.name,
302
+ status: task.status,
303
+ estimatedHours: task.estimatedHours,
304
+ hourlyRate: task.project.hourlyRate
305
+ });
306
+ const statusColor = getStatusColor(task.status);
307
+ console.log(
308
+ chalk2.green(`
269
309
  \u2713 Selected: ${chalk2.bold(task.title)}
270
310
  `) + chalk2.dim(` Project: ${task.project.name} \u2022 $${task.project.hourlyRate}/hr
271
311
  `) + chalk2.dim(` Status: `) + statusColor(task.status) + (task.estimatedHours ? chalk2.dim(` \u2022 Estimated: ${task.estimatedHours}h
272
312
  `) : "\n")
273
- );
274
- }
313
+ );
275
314
  } catch (error) {
276
- spinner.fail(chalk2.red("Failed to load tasks"));
315
+ spinner.fail(chalk2.red("Failed to load projects/tasks"));
277
316
  if (error instanceof ApiError) {
278
317
  console.log(chalk2.red(`
279
318
  Error: ${error.message}
@@ -779,67 +818,11 @@ Unexpected error: ${error.message}
779
818
  }
780
819
  }
781
820
 
782
- // src/logo-ascii.ts
783
- var SVG_EMBED = `<?xml version="1.0" encoding="UTF-8"?>
784
- <svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.44 45.4">
785
- <g id="MenoLogo">
786
- <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"/>
787
- <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"/>
788
- <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"/>
789
- </g>
790
- </svg>`;
791
- function fallbackAscii() {
792
- return [
793
- " ____ __ ",
794
- " /\\ / __ \\/ /___ _ _ ",
795
- " / \\ _ __ / / / / / __ \\ | | |",
796
- " / /\\ \\ | '_ \\ / /_/ / / /_/ / |_| |",
797
- " / ____ \\| | | |\\____/_/\\____/\\__,_|",
798
- " /_/ \\_\\_| |_| ",
799
- "",
800
- " M E N O (logo) "
801
- ].join("\n");
802
- }
803
- async function printLogoAscii(width = 64) {
804
- try {
805
- const sharpImport = await import("sharp");
806
- const sharp = sharpImport.default ?? sharpImport;
807
- const svgBuffer = Buffer.from(SVG_EMBED, "utf8");
808
- const img = sharp(svgBuffer).resize({ width }).flatten({ background: { r: 255, g: 255, b: 255 } }).grayscale();
809
- const { data, info } = await img.raw().toBuffer({ resolveWithObject: true });
810
- const chars = " .,:;i1tfLCG08@";
811
- const rows = [];
812
- const aspect = 0.5;
813
- const rowStep = Math.max(1, Math.round(1 / aspect));
814
- for (let y = 0; y < info.height; y += rowStep) {
815
- let line = "";
816
- for (let x = 0; x < info.width; x++) {
817
- const idx = y * info.width + x;
818
- const val = data[idx];
819
- const charIdx = Math.floor((1 - val / 255) * (chars.length - 1));
820
- line += chars[charIdx];
821
- }
822
- rows.push(line);
823
- }
824
- console.log(rows.join("\n"));
825
- return;
826
- } catch (err) {
827
- console.log(fallbackAscii());
828
- return;
829
- }
830
- }
831
- if (__require.main === module) {
832
- printLogoAscii().catch(() => {
833
- });
834
- }
835
-
836
821
  // src/index.ts
837
822
  var program = new Command();
838
823
  program.name("meno").description("CLI for Meno time tracking").version("0.1.0");
839
- printLogoAscii().catch(() => {
840
- });
841
824
  program.command("login").description("Authenticate with your Meno API key").action(login);
842
- program.command("select").description("Select a task to work on").action(selectProject);
825
+ program.command("select").description("Select a project, then select a task to work on").action(selectProject);
843
826
  program.command("start [task-id]").description("Start a timer on selected task or specific task ID").action(startTimer);
844
827
  program.command("stop").description("Stop the running timer and log the entry").option("--discard", "Discard the timer without logging").option("-d, --description <text>", "Entry description (skip prompt)").option("-y, --yes", "Auto-confirm (skip confirmation prompt)").option("--no-confirm", "Skip confirmation prompt").option("--commit <hash>", "Git commit hash for evidence").option("--repo <url>", "Git repository URL for evidence").action(stopTimer);
845
828
  program.command("log <description>").description("Log time manually").option("-d, --duration <duration>", "Duration (e.g., 45m, 1.5h, 90)").option("-p, --project <id>", "Project ID").action(logTime);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usemeno/meno-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Command-line interface for Meno time tracking",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -21,12 +21,13 @@
21
21
  "author": "",
22
22
  "license": "MIT",
23
23
  "dependencies": {
24
+ "chalk": "^5.3.0",
24
25
  "commander": "^12.0.0",
25
26
  "conf": "^13.0.1",
26
- "ora": "^8.0.1",
27
27
  "enquirer": "^2.4.1",
28
- "chalk": "^5.3.0",
29
- "node-fetch": "^3.3.2"
28
+ "node-fetch": "^3.3.2",
29
+ "ora": "^8.0.1",
30
+ "sharp": "^0.34.5"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@types/node": "^20.11.0",
@@ -1,7 +1,7 @@
1
1
  import enquirer from "enquirer";
2
2
  import ora from "ora";
3
3
  import chalk from "chalk";
4
- import { setApiKey, setBaseUrl } from "../config.js";
4
+ import { setApiKey } from "../config.js";
5
5
  import { apiRequest, ApiError} from "../utils/api.js";
6
6
 
7
7
  export async function login() {
@@ -23,25 +23,12 @@ export async function login() {
23
23
 
24
24
  const apiKey = (apiKeyPrompt as any).apiKey;
25
25
 
26
- // Optional: Custom base URL
27
- const baseUrlPrompt = await enquirer.prompt({
28
- type: "input",
29
- name: "baseUrl",
30
- message: "Base URL (press Enter for default):",
31
- initial: "http://localhost:3000",
32
- });
33
-
34
- const baseUrl = (baseUrlPrompt as any).baseUrl;
35
-
36
26
  // Validate API key by making a test request
37
27
  const spinner = ora("Validating API key...").start();
38
28
 
39
29
  try {
40
30
  // Temporarily set the key to test it
41
31
  setApiKey(apiKey as string);
42
- if (baseUrl) {
43
- setBaseUrl(baseUrl as string);
44
- }
45
32
 
46
33
  // Test the key
47
34
  await apiRequest("/api/external/projects");
@@ -1,8 +1,8 @@
1
1
  import enquirer from "enquirer";
2
2
  import ora from "ora";
3
3
  import chalk from "chalk";
4
- import { setSelectedTask, clearSelectedTask, getSelectedTask } from "../config.js";
5
- import { getTasks, ApiError, Task } from "../utils/api.js";
4
+ import { setSelectedTask, clearSelectedTask, getSelectedTask, setSelectedProject, clearSelectedProject } from "../config.js";
5
+ import { getTasks, ApiError, Task, apiRequest, Project } from "../utils/api.js";
6
6
 
7
7
  function getStatusColor(status: string): (text: string) => string {
8
8
  switch (status) {
@@ -24,32 +24,88 @@ function formatTaskDisplay(task: Task, isSelected: boolean): string {
24
24
  }
25
25
 
26
26
  export async function selectProject() {
27
- console.log(chalk.bold("\n📋 Select Task\n"));
27
+ console.log(chalk.bold("\n📋 Select Project & Task\n"));
28
28
 
29
- const spinner = ora("Loading tasks...").start();
29
+ const spinner = ora("Loading projects and tasks...").start();
30
30
 
31
31
  try {
32
- const tasks = await getTasks();
32
+ const [tasks, projectsResponse] = await Promise.all([
33
+ getTasks(),
34
+ apiRequest<{ projects: Project[] }>("/api/external/projects"),
35
+ ]);
36
+ const projects = projectsResponse.projects || [];
33
37
 
34
38
  spinner.stop();
35
39
 
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"));
40
+ if (projects.length === 0) {
41
+ console.log(chalk.yellow("No active projects found."));
42
+ console.log(chalk.dim("Create projects in the Meno dashboard first.\n"));
39
43
  return;
40
44
  }
41
45
 
42
- const currentTask = getSelectedTask();
46
+ const currentSelection = getSelectedTask();
43
47
 
44
- // Build choices - group by status or show all
45
- const choices = [
48
+ const projectChoices = [
46
49
  {
47
50
  name: "clear",
48
51
  message: chalk.dim("[Clear Selection]"),
49
52
  },
50
- ...tasks.map((task) => {
51
- const isSelected = currentTask?.id === task.id;
52
-
53
+ ...projects.map((project) => {
54
+ const projectTaskCount = tasks.filter((task) => task.projectId === project.id).length;
55
+
56
+ return {
57
+ name: project.id,
58
+ message: `${chalk.bold(project.name)} ${chalk.dim("•")} ${project.clientName} ${chalk.dim(`(${projectTaskCount} task${projectTaskCount === 1 ? "" : "s"})`)}`,
59
+ value: project,
60
+ };
61
+ }),
62
+ ];
63
+
64
+ const projectResult = await enquirer.prompt({
65
+ type: "select",
66
+ name: "project",
67
+ message: "Select a project:",
68
+ choices: projectChoices,
69
+ });
70
+
71
+ const projectAnswer = (projectResult as any).project;
72
+
73
+ if (projectAnswer === "clear") {
74
+ clearSelectedTask();
75
+ clearSelectedProject();
76
+ console.log(chalk.green("\n✓ Selection cleared\n"));
77
+ return;
78
+ }
79
+
80
+ const selectedProject = projects.find((project) => project.id === projectAnswer);
81
+ if (!selectedProject) {
82
+ console.log(chalk.red("\n✗ Project not found\n"));
83
+ return;
84
+ }
85
+
86
+ setSelectedProject({
87
+ id: selectedProject.id,
88
+ name: selectedProject.name,
89
+ clientName: selectedProject.clientName,
90
+ hourlyRate: selectedProject.hourlyRate,
91
+ });
92
+
93
+ const projectTasks = tasks.filter((task) => task.projectId === selectedProject.id);
94
+ if (projectTasks.length === 0) {
95
+ clearSelectedTask();
96
+ console.log(chalk.green(`\n✓ Selected project: ${chalk.bold(selectedProject.name)}`));
97
+ console.log(chalk.dim("No tasks found for this project yet.\n"));
98
+ return;
99
+ }
100
+
101
+ const taskChoices = [
102
+ {
103
+ name: "clear_task",
104
+ message: chalk.dim("[Clear Task Selection]"),
105
+ },
106
+ ...projectTasks.map((task) => {
107
+ const isSelected = currentSelection?.id === task.id;
108
+
53
109
  return {
54
110
  name: task.id,
55
111
  message: formatTaskDisplay(task, isSelected),
@@ -58,47 +114,46 @@ export async function selectProject() {
58
114
  }),
59
115
  ];
60
116
 
61
- const result = await enquirer.prompt({
117
+ const taskResult = await enquirer.prompt({
62
118
  type: "select",
63
119
  name: "task",
64
- message: "Select a task:",
65
- choices,
120
+ message: `Select a task in ${selectedProject.name}:`,
121
+ choices: taskChoices,
66
122
  });
67
123
 
68
- const answer = (result as any).task;
69
-
70
- if (answer === "clear") {
124
+ const taskAnswer = (taskResult as any).task;
125
+ if (taskAnswer === "clear_task") {
71
126
  clearSelectedTask();
72
- console.log(chalk.green("\n✓ Selection cleared\n"));
73
- } else {
74
- // Find the full task object by ID
75
- const task = tasks.find((t) => t.id === answer);
76
-
77
- if (!task) {
78
- console.log(chalk.red("\n✗ Task not found\n"));
79
- return;
80
- }
81
-
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,
90
- });
91
-
92
- const statusColor = getStatusColor(task.status);
93
- console.log(
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")
98
- );
127
+ console.log(chalk.green(`\n✓ Project selected: ${chalk.bold(selectedProject.name)}`));
128
+ console.log(chalk.dim("Task selection cleared.\n"));
129
+ return;
99
130
  }
131
+
132
+ const task = projectTasks.find((t) => t.id === taskAnswer);
133
+ if (!task) {
134
+ console.log(chalk.red("\n✗ Task not found\n"));
135
+ return;
136
+ }
137
+
138
+ setSelectedTask({
139
+ id: task.id,
140
+ title: task.title,
141
+ projectId: task.projectId,
142
+ projectName: task.project.name,
143
+ status: task.status,
144
+ estimatedHours: task.estimatedHours,
145
+ hourlyRate: task.project.hourlyRate,
146
+ });
147
+
148
+ const statusColor = getStatusColor(task.status);
149
+ console.log(
150
+ chalk.green(`\n✓ Selected: ${chalk.bold(task.title)}\n`) +
151
+ chalk.dim(` Project: ${task.project.name} • $${task.project.hourlyRate}/hr\n`) +
152
+ chalk.dim(` Status: `) + statusColor(task.status) +
153
+ (task.estimatedHours ? chalk.dim(` • Estimated: ${task.estimatedHours}h\n`) : "\n")
154
+ );
100
155
  } catch (error) {
101
- spinner.fail(chalk.red("Failed to load tasks"));
156
+ spinner.fail(chalk.red("Failed to load projects/tasks"));
102
157
 
103
158
  if (error instanceof ApiError) {
104
159
  console.log(chalk.red(`\nError: ${error.message}\n`));
package/src/config.ts CHANGED
@@ -2,7 +2,6 @@ import Conf from "conf";
2
2
 
3
3
  interface Config {
4
4
  apiKey?: string;
5
- baseUrl?: string;
6
5
  selectedProject?: {
7
6
  id: string;
8
7
  name: string;
@@ -41,11 +40,7 @@ export function setApiKey(key: string): void {
41
40
  }
42
41
 
43
42
  export function getBaseUrl(): string {
44
- return config.get("baseUrl") || process.env.MENO_BASE_URL || "https://menohq.app";
45
- }
46
-
47
- export function setBaseUrl(url: string): void {
48
- config.set("baseUrl", url);
43
+ return "https://menohq.app";
49
44
  }
50
45
 
51
46
  export function getSelectedProject() {
package/src/index.ts CHANGED
@@ -7,7 +7,6 @@ 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";
11
10
 
12
11
  const program = new Command();
13
12
 
@@ -16,9 +15,6 @@ program
16
15
  .description("CLI for Meno time tracking")
17
16
  .version("0.1.0");
18
17
 
19
- // Print ASCII logo at CLI startup (sharp used if installed, otherwise a fallback)
20
- printLogoAscii().catch(() => {});
21
-
22
18
  program
23
19
  .command("login")
24
20
  .description("Authenticate with your Meno API key")
@@ -26,7 +22,7 @@ program
26
22
 
27
23
  program
28
24
  .command("select")
29
- .description("Select a task to work on")
25
+ .description("Select a project, then select a task to work on")
30
26
  .action(selectProject);
31
27
 
32
28
  program
package/src/logo-ascii.ts CHANGED
@@ -1,6 +1,3 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
1
  const SVG_EMBED = `<?xml version="1.0" encoding="UTF-8"?>
5
2
  <svg id="Layer_2" data-name="Layer 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 55.44 45.4">
6
3
  <g id="MenoLogo">
@@ -55,7 +52,3 @@ export async function printLogoAscii(width = 64) {
55
52
  return;
56
53
  }
57
54
  }
58
-
59
- if (require.main === module) {
60
- printLogoAscii().catch(() => {});
61
- }