@task-mcp/cli 1.0.17 → 1.0.19

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": "@task-mcp/cli",
3
- "version": "1.0.17",
3
+ "version": "1.0.19",
4
4
  "description": "Zero-dependency CLI for task-mcp with Bun native visualization",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import type { Task, Project, InboxItem } from "../storage.js";
2
+ import type { Task, InboxItem, WorkspaceInfo } from "../storage.js";
3
3
 
4
4
  // Helper to create test tasks
5
5
  function createTask(overrides: Partial<Task> = {}): Task {
@@ -8,21 +8,19 @@ function createTask(overrides: Partial<Task> = {}): Task {
8
8
  title: "Test Task",
9
9
  status: "pending",
10
10
  priority: "medium",
11
- projectId: "proj_test",
11
+ workspace: "test-workspace",
12
12
  createdAt: new Date().toISOString(),
13
13
  updatedAt: new Date().toISOString(),
14
14
  ...overrides,
15
15
  };
16
16
  }
17
17
 
18
- // Helper to create test projects
19
- function createProject(overrides: Partial<Project> = {}): Project {
18
+ // Helper to create test workspaces
19
+ function createWorkspace(overrides: Partial<WorkspaceInfo> = {}): WorkspaceInfo {
20
20
  return {
21
- id: `proj_${Math.random().toString(36).slice(2, 10)}`,
22
- name: "Test Project",
23
- status: "active",
24
- createdAt: new Date().toISOString(),
25
- updatedAt: new Date().toISOString(),
21
+ name: "Test Workspace",
22
+ taskCount: 0,
23
+ completedCount: 0,
26
24
  ...overrides,
27
25
  };
28
26
  }
@@ -40,130 +38,130 @@ function createInboxItem(overrides: Partial<InboxItem> = {}): InboxItem {
40
38
  }
41
39
 
42
40
  describe("dashboard command - unit tests", () => {
43
- describe("project lookup logic", () => {
44
- test("finds project by exact ID", () => {
45
- const projects = [
46
- createProject({ id: "proj_abc123", name: "Project A" }),
47
- createProject({ id: "proj_xyz789", name: "Project B" }),
41
+ describe("workspace lookup logic", () => {
42
+ test("finds workspace by exact name", () => {
43
+ const workspaces = [
44
+ createWorkspace({ name: "workspace-a" }),
45
+ createWorkspace({ name: "workspace-b" }),
48
46
  ];
49
47
 
50
- const projectId = "proj_abc123";
51
- const project = projects.find((p) => p.id === projectId || p.id.startsWith(projectId));
48
+ const workspaceName = "workspace-a";
49
+ const workspace = workspaces.find((w) => w.name === workspaceName || w.name.startsWith(workspaceName));
52
50
 
53
- expect(project).toBeDefined();
54
- expect(project?.name).toBe("Project A");
51
+ expect(workspace).toBeDefined();
52
+ expect(workspace?.name).toBe("workspace-a");
55
53
  });
56
54
 
57
- test("finds project by partial ID prefix", () => {
58
- const projects = [
59
- createProject({ id: "proj_abc123456789", name: "Project A" }),
60
- createProject({ id: "proj_xyz789012345", name: "Project B" }),
55
+ test("finds workspace by partial name prefix", () => {
56
+ const workspaces = [
57
+ createWorkspace({ name: "workspace-abc123" }),
58
+ createWorkspace({ name: "workspace-xyz789" }),
61
59
  ];
62
60
 
63
- const projectId = "proj_abc";
64
- const project = projects.find((p) => p.id === projectId || p.id.startsWith(projectId));
61
+ const workspaceName = "workspace-abc";
62
+ const workspace = workspaces.find((w) => w.name === workspaceName || w.name.startsWith(workspaceName));
65
63
 
66
- expect(project).toBeDefined();
67
- expect(project?.name).toBe("Project A");
64
+ expect(workspace).toBeDefined();
65
+ expect(workspace?.name).toBe("workspace-abc123");
68
66
  });
69
67
 
70
- test("returns undefined for non-matching ID", () => {
71
- const projects = [
72
- createProject({ id: "proj_abc123", name: "Project A" }),
73
- createProject({ id: "proj_xyz789", name: "Project B" }),
68
+ test("returns undefined for non-matching name", () => {
69
+ const workspaces = [
70
+ createWorkspace({ name: "workspace-a" }),
71
+ createWorkspace({ name: "workspace-b" }),
74
72
  ];
75
73
 
76
- const projectId = "proj_nonexistent";
77
- const project = projects.find((p) => p.id === projectId || p.id.startsWith(projectId));
74
+ const workspaceName = "workspace-nonexistent";
75
+ const workspace = workspaces.find((w) => w.name === workspaceName || w.name.startsWith(workspaceName));
78
76
 
79
- expect(project).toBeUndefined();
77
+ expect(workspace).toBeUndefined();
80
78
  });
81
79
 
82
- test("returns first match when multiple projects share prefix", () => {
83
- const projects = [
84
- createProject({ id: "proj_abc111", name: "Project First" }),
85
- createProject({ id: "proj_abc222", name: "Project Second" }),
80
+ test("returns first match when multiple workspaces share prefix", () => {
81
+ const workspaces = [
82
+ createWorkspace({ name: "workspace-abc111" }),
83
+ createWorkspace({ name: "workspace-abc222" }),
86
84
  ];
87
85
 
88
- const projectId = "proj_abc";
89
- const project = projects.find((p) => p.id === projectId || p.id.startsWith(projectId));
86
+ const workspaceName = "workspace-abc";
87
+ const workspace = workspaces.find((w) => w.name === workspaceName || w.name.startsWith(workspaceName));
90
88
 
91
- expect(project).toBeDefined();
92
- expect(project?.name).toBe("Project First");
89
+ expect(workspace).toBeDefined();
90
+ expect(workspace?.name).toBe("workspace-abc111");
93
91
  });
94
92
  });
95
93
 
96
94
  describe("dashboard mode selection", () => {
97
- test("single project mode when only one project exists", () => {
98
- const projects = [createProject({ id: "proj_only", name: "Only Project" })];
95
+ test("single workspace mode when only one workspace exists", () => {
96
+ const workspaces = [createWorkspace({ name: "only-workspace" })];
99
97
 
100
- const hasSingleProject = projects.length === 1;
101
- const shouldShowGlobal = projects.length > 1;
98
+ const hasSingleWorkspace = workspaces.length === 1;
99
+ const shouldShowGlobal = workspaces.length > 1;
102
100
 
103
- expect(hasSingleProject).toBe(true);
101
+ expect(hasSingleWorkspace).toBe(true);
104
102
  expect(shouldShowGlobal).toBe(false);
105
103
  });
106
104
 
107
- test("global mode when multiple projects exist", () => {
108
- const projects = [
109
- createProject({ id: "proj_1", name: "Project 1" }),
110
- createProject({ id: "proj_2", name: "Project 2" }),
105
+ test("global mode when multiple workspaces exist", () => {
106
+ const workspaces = [
107
+ createWorkspace({ name: "workspace-1" }),
108
+ createWorkspace({ name: "workspace-2" }),
111
109
  ];
112
110
 
113
- const hasSingleProject = projects.length === 1;
114
- const shouldShowGlobal = projects.length > 1;
111
+ const hasSingleWorkspace = workspaces.length === 1;
112
+ const shouldShowGlobal = workspaces.length > 1;
115
113
 
116
- expect(hasSingleProject).toBe(false);
114
+ expect(hasSingleWorkspace).toBe(false);
117
115
  expect(shouldShowGlobal).toBe(true);
118
116
  });
119
117
 
120
- test("project mode when specific projectId provided", () => {
121
- const projects = [
122
- createProject({ id: "proj_1", name: "Project 1" }),
123
- createProject({ id: "proj_2", name: "Project 2" }),
118
+ test("workspace mode when specific workspace provided", () => {
119
+ const workspaces = [
120
+ createWorkspace({ name: "workspace-1" }),
121
+ createWorkspace({ name: "workspace-2" }),
124
122
  ];
125
123
 
126
- const projectId = "proj_1";
127
- const targetProject = projects.find((p) => p.id === projectId || p.id.startsWith(projectId));
124
+ const workspaceName = "workspace-1";
125
+ const targetWorkspace = workspaces.find((w) => w.name === workspaceName || w.name.startsWith(workspaceName));
128
126
 
129
- expect(targetProject).toBeDefined();
130
- expect(targetProject?.name).toBe("Project 1");
127
+ expect(targetWorkspace).toBeDefined();
128
+ expect(targetWorkspace?.name).toBe("workspace-1");
131
129
  });
132
130
  });
133
131
 
134
- describe("task grouping by project", () => {
135
- test("builds task map for each project", () => {
136
- const projects = [
137
- createProject({ id: "proj_1" }),
138
- createProject({ id: "proj_2" }),
132
+ describe("task grouping by workspace", () => {
133
+ test("builds task map for each workspace", () => {
134
+ const workspaces = [
135
+ createWorkspace({ name: "workspace-1" }),
136
+ createWorkspace({ name: "workspace-2" }),
139
137
  ];
140
138
 
141
139
  const allTasks = [
142
- createTask({ id: "task_1", projectId: "proj_1" }),
143
- createTask({ id: "task_2", projectId: "proj_1" }),
144
- createTask({ id: "task_3", projectId: "proj_2" }),
140
+ createTask({ id: "task_1", workspace: "workspace-1" }),
141
+ createTask({ id: "task_2", workspace: "workspace-1" }),
142
+ createTask({ id: "task_3", workspace: "workspace-2" }),
145
143
  ];
146
144
 
147
- const tasksByProject = new Map<string, Task[]>();
148
- for (const project of projects) {
149
- tasksByProject.set(
150
- project.id,
151
- allTasks.filter((t) => t.projectId === project.id)
145
+ const tasksByWorkspace = new Map<string, Task[]>();
146
+ for (const ws of workspaces) {
147
+ tasksByWorkspace.set(
148
+ ws.name,
149
+ allTasks.filter((t) => t.workspace === ws.name)
152
150
  );
153
151
  }
154
152
 
155
- expect(tasksByProject.get("proj_1")?.length).toBe(2);
156
- expect(tasksByProject.get("proj_2")?.length).toBe(1);
153
+ expect(tasksByWorkspace.get("workspace-1")?.length).toBe(2);
154
+ expect(tasksByWorkspace.get("workspace-2")?.length).toBe(1);
157
155
  });
158
156
 
159
- test("returns empty array for project with no tasks", () => {
160
- const tasksByProject = new Map<string, Task[]>();
161
- tasksByProject.set("proj_1", []);
157
+ test("returns empty array for workspace with no tasks", () => {
158
+ const tasksByWorkspace = new Map<string, Task[]>();
159
+ tasksByWorkspace.set("workspace-1", []);
162
160
 
163
- const getProjectTasks = (pid: string) => tasksByProject.get(pid) ?? [];
161
+ const getWorkspaceTasks = (name: string) => tasksByWorkspace.get(name) ?? [];
164
162
 
165
- expect(getProjectTasks("proj_1")).toEqual([]);
166
- expect(getProjectTasks("proj_nonexistent")).toEqual([]);
163
+ expect(getWorkspaceTasks("workspace-1")).toEqual([]);
164
+ expect(getWorkspaceTasks("workspace-nonexistent")).toEqual([]);
167
165
  });
168
166
  });
169
167
 
@@ -184,12 +182,12 @@ describe("dashboard command - unit tests", () => {
184
182
  });
185
183
 
186
184
  describe("empty state handling", () => {
187
- test("detects when no projects exist", () => {
188
- const projects: Project[] = [];
185
+ test("detects when no workspaces exist", () => {
186
+ const workspaces: WorkspaceInfo[] = [];
189
187
 
190
- const hasProjects = projects.length > 0;
188
+ const hasWorkspaces = workspaces.length > 0;
191
189
 
192
- expect(hasProjects).toBe(false);
190
+ expect(hasWorkspaces).toBe(false);
193
191
  });
194
192
 
195
193
  test("detects when tasks are empty", () => {
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test, mock, beforeEach, afterEach, spyOn } from "bun:test";
2
- import type { Task, Project } from "../storage.js";
2
+ import type { Task, WorkspaceInfo } from "../storage.js";
3
3
 
4
4
  // Helper to create test tasks
5
5
  function createTask(overrides: Partial<Task> = {}): Task {
@@ -8,21 +8,19 @@ function createTask(overrides: Partial<Task> = {}): Task {
8
8
  title: "Test Task",
9
9
  status: "pending",
10
10
  priority: "medium",
11
- projectId: "proj_test",
11
+ workspace: "test-workspace",
12
12
  createdAt: new Date().toISOString(),
13
13
  updatedAt: new Date().toISOString(),
14
14
  ...overrides,
15
15
  };
16
16
  }
17
17
 
18
- // Helper to create test projects
19
- function createProject(overrides: Partial<Project> = {}): Project {
18
+ // Helper to create test workspaces
19
+ function createWorkspace(overrides: Partial<WorkspaceInfo> = {}): WorkspaceInfo {
20
20
  return {
21
- id: `proj_${Math.random().toString(36).slice(2, 10)}`,
22
- name: "Test Project",
23
- status: "active",
24
- createdAt: new Date().toISOString(),
25
- updatedAt: new Date().toISOString(),
21
+ name: "Test Workspace",
22
+ taskCount: 0,
23
+ completedCount: 0,
26
24
  ...overrides,
27
25
  };
28
26
  }
@@ -317,9 +315,9 @@ describe("list commands - exports", () => {
317
315
  expect(typeof listTasksCmd).toBe("function");
318
316
  });
319
317
 
320
- test("listProjectsCmd is exported", async () => {
321
- const { listProjectsCmd } = await import("../commands/list.js");
322
- expect(typeof listProjectsCmd).toBe("function");
318
+ test("listWorkspacesCmd is exported", async () => {
319
+ const { listWorkspacesCmd } = await import("../commands/list.js");
320
+ expect(typeof listWorkspacesCmd).toBe("function");
323
321
  });
324
322
 
325
323
  test("listTasksCmd accepts options object", async () => {
@@ -327,26 +325,23 @@ describe("list commands - exports", () => {
327
325
 
328
326
  // Verify the function signature accepts the expected options
329
327
  const options = {
330
- projectId: "proj_123",
328
+ workspace: "test-workspace",
331
329
  status: "pending",
332
330
  priority: "high",
333
331
  all: true,
334
332
  };
335
333
 
336
334
  // Type check passes if this compiles
337
- expect(options.projectId).toBeDefined();
335
+ expect(options.workspace).toBeDefined();
338
336
  expect(options.status).toBeDefined();
339
337
  expect(options.priority).toBeDefined();
340
338
  expect(options.all).toBeDefined();
341
339
  });
342
340
 
343
- test("listProjectsCmd accepts options object", async () => {
344
- const { listProjectsCmd } = await import("../commands/list.js");
341
+ test("listWorkspacesCmd accepts no options", async () => {
342
+ const { listWorkspacesCmd } = await import("../commands/list.js");
345
343
 
346
- const options = {
347
- all: true,
348
- };
349
-
350
- expect(options.all).toBeDefined();
344
+ // listWorkspacesCmd takes no options
345
+ expect(typeof listWorkspacesCmd).toBe("function");
351
346
  });
352
347
  });
@@ -13,7 +13,7 @@ function createTask(overrides: Partial<Task> = {}): Task {
13
13
  title: "Test Task",
14
14
  status: "pending",
15
15
  priority: "medium",
16
- projectId: "proj_test",
16
+ workspace: "test-workspace",
17
17
  createdAt: new Date().toISOString(),
18
18
  updatedAt: new Date().toISOString(),
19
19
  ...overrides,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dashboard command - displays project overview with unified widgets
2
+ * Dashboard command - displays workspace overview with unified widgets
3
3
  * Uses shared dashboard renderer from @task-mcp/shared
4
4
  */
5
5
 
@@ -7,81 +7,83 @@ import { VERSION } from "../index.js";
7
7
  import { c } from "../ansi.js";
8
8
  import {
9
9
  renderGlobalDashboard,
10
- renderProjectDashboard,
10
+ renderWorkspaceDashboard,
11
11
  type Task as SharedTask,
12
- type Project as SharedProject,
12
+ type WorkspaceInfo,
13
13
  } from "@task-mcp/shared";
14
14
  import {
15
- listProjects,
15
+ listWorkspaces,
16
16
  listTasks,
17
17
  listAllTasks,
18
18
  listInboxItems,
19
19
  getActiveTag,
20
+ getCurrentWorkspace,
20
21
  type Task,
21
- type Project,
22
22
  } from "../storage.js";
23
23
 
24
24
  // =============================================================================
25
25
  // Main Dashboard
26
26
  // =============================================================================
27
27
 
28
- export async function dashboard(projectId?: string): Promise<void> {
29
- // Get projects, tasks, and active tag
30
- const projects = await listProjects();
28
+ export async function dashboard(workspaceName?: string): Promise<void> {
29
+ // Get workspaces, tasks, and active tag
30
+ const workspaces = await listWorkspaces();
31
31
  const activeTag = await getActiveTag();
32
+ const currentWorkspace = getCurrentWorkspace();
32
33
 
33
- if (projects.length === 0) {
34
- console.log(c.yellow("No projects found. Create a project first using the MCP server."));
34
+ if (workspaces.length === 0) {
35
+ console.log(c.yellow("No tasks found. Create a task first using the MCP server."));
35
36
  return;
36
37
  }
37
38
 
38
- // If projectId specified, show single project. Otherwise show all.
39
- let project: Project | undefined;
39
+ // If workspace specified, show single workspace. Otherwise show all.
40
+ let selectedWorkspace: WorkspaceInfo | undefined;
40
41
  let tasks: Task[];
41
42
 
42
- if (projectId) {
43
- project = projects.find(p => p.id === projectId || p.id.startsWith(projectId));
44
- if (!project) {
45
- console.log(c.error(`Project not found: ${projectId}`));
43
+ if (workspaceName) {
44
+ selectedWorkspace = workspaces.find(w => w.name === workspaceName || w.name.startsWith(workspaceName));
45
+ if (!selectedWorkspace) {
46
+ console.log(c.error(`Workspace not found: ${workspaceName}`));
46
47
  return;
47
48
  }
48
- tasks = await listTasks(project.id);
49
- } else if (projects.length === 1) {
50
- project = projects[0];
51
- tasks = await listTasks(project!.id);
49
+ tasks = await listAllTasks();
50
+ tasks = tasks.filter(t => t.workspace === selectedWorkspace!.name);
51
+ } else if (workspaces.length === 1) {
52
+ selectedWorkspace = workspaces[0];
53
+ tasks = await listTasks();
52
54
  } else {
53
- // Show all tasks across all projects
55
+ // Show all tasks across all workspaces
54
56
  tasks = await listAllTasks();
55
57
  }
56
58
 
57
59
  // Get inbox items
58
60
  const inboxItems = await listInboxItems("pending");
59
61
 
60
- // Create task lookup for projects table (batch query to avoid N+1)
62
+ // Create task lookup for workspaces table (batch query to avoid N+1)
61
63
  const allTasksForLookup = await listAllTasks();
62
- const tasksByProject = new Map<string, Task[]>();
64
+ const tasksByWorkspace = new Map<string, Task[]>();
63
65
  for (const task of allTasksForLookup) {
64
- const projectTasks = tasksByProject.get(task.projectId) ?? [];
65
- projectTasks.push(task);
66
- tasksByProject.set(task.projectId, projectTasks);
66
+ const workspaceTasks = tasksByWorkspace.get(task.workspace) ?? [];
67
+ workspaceTasks.push(task);
68
+ tasksByWorkspace.set(task.workspace, workspaceTasks);
67
69
  }
68
- const getProjectTasks = (pid: string) => tasksByProject.get(pid) ?? [];
70
+ const getWorkspaceTasks = (ws: string) => (tasksByWorkspace.get(ws) ?? []) as SharedTask[];
69
71
 
70
72
  // Render dashboard using shared renderer
71
73
  // Cast types - CLI types are compatible subset of shared types
72
74
  let output: string;
73
- if (project) {
74
- output = renderProjectDashboard(
75
- project as SharedProject,
75
+ if (selectedWorkspace) {
76
+ output = renderWorkspaceDashboard(
77
+ selectedWorkspace.name,
76
78
  tasks as SharedTask[],
77
79
  { version: VERSION, activeTag }
78
80
  );
79
81
  } else {
80
82
  output = renderGlobalDashboard(
81
- projects as SharedProject[],
83
+ workspaces,
82
84
  tasks as SharedTask[],
83
85
  inboxItems,
84
- (pid) => (getProjectTasks(pid) as SharedTask[]),
86
+ getWorkspaceTasks,
85
87
  { version: VERSION, activeTag }
86
88
  );
87
89
  }
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { c, formatStatus, formatPriority } from "../ansi.js";
7
- import { InboxStore, TaskStore, ProjectStore } from "@task-mcp/mcp-server/storage";
7
+ import { InboxStore, TaskStore } from "@task-mcp/mcp-server/storage";
8
8
  import { parseInboxInput } from "@task-mcp/shared";
9
9
  import {
10
10
  LIST_SEPARATOR_WIDTH,
@@ -15,7 +15,6 @@ import {
15
15
 
16
16
  const inboxStore = new InboxStore();
17
17
  const taskStore = new TaskStore();
18
- const projectStore = new ProjectStore();
19
18
 
20
19
  export interface InboxAddOptions {
21
20
  content: string;
@@ -29,7 +28,6 @@ export interface InboxListOptions {
29
28
 
30
29
  export interface InboxPromoteOptions {
31
30
  itemId: string;
32
- projectId?: string;
33
31
  title?: string;
34
32
  priority?: "critical" | "high" | "medium" | "low";
35
33
  }
@@ -77,7 +75,7 @@ export async function inboxListCmd(options: InboxListOptions): Promise<void> {
77
75
  // Header
78
76
  const title = status ? `Inbox (${status})` : "Inbox (all)";
79
77
  console.log(c.bold(c.cyan(title)) + c.dim(` - ${items.length} items`));
80
- console.log(c.dim("─".repeat(50)));
78
+ console.log(c.dim("─".repeat(LIST_SEPARATOR_WIDTH)));
81
79
  console.log();
82
80
 
83
81
  for (const item of items) {
@@ -116,7 +114,7 @@ export async function inboxGetCmd(itemId: string): Promise<void> {
116
114
 
117
115
  console.log();
118
116
  console.log(c.bold(c.cyan("Inbox Item")));
119
- console.log(c.dim("─".repeat(40)));
117
+ console.log(c.dim("─".repeat(MENU_SEPARATOR_WIDTH)));
120
118
  console.log();
121
119
  console.log(" " + c.dim("ID:") + " " + item.id);
122
120
  console.log(" " + c.dim("Status:") + " " + formatStatus(item.status));
@@ -139,7 +137,7 @@ export async function inboxGetCmd(itemId: string): Promise<void> {
139
137
  }
140
138
 
141
139
  /**
142
- * Promote inbox item to task
140
+ * Promote inbox item to task in current workspace
143
141
  */
144
142
  export async function inboxPromoteCmd(options: InboxPromoteOptions): Promise<void> {
145
143
  const item = await inboxStore.get(options.itemId);
@@ -158,31 +156,9 @@ export async function inboxPromoteCmd(options: InboxPromoteOptions): Promise<voi
158
156
  return;
159
157
  }
160
158
 
161
- // Get project
162
- let projectId = options.projectId;
163
- if (!projectId) {
164
- const projects = await projectStore.list();
165
- if (projects.length === 0) {
166
- console.log();
167
- console.log(c.error("No projects found. Create a project first."));
168
- console.log();
169
- return;
170
- }
171
- // Use first project if not specified
172
- projectId = projects[0]!.id;
173
- }
174
-
175
- const project = await projectStore.get(projectId);
176
- if (!project) {
177
- console.log();
178
- console.log(c.error(`Project not found: ${projectId}`));
179
- console.log();
180
- return;
181
- }
182
-
183
- // Create task
184
- const task = await taskStore.create(projectId, {
185
- title: options.title ?? item.content.slice(0, 100),
159
+ // Create task in current workspace (auto-detected)
160
+ const task = await taskStore.create({
161
+ title: options.title ?? item.content.slice(0, INBOX_PROMOTE_TITLE_MAX_LENGTH),
186
162
  description: item.content,
187
163
  priority: options.priority ?? "medium",
188
164
  tags: item.tags,
@@ -196,7 +172,7 @@ export async function inboxPromoteCmd(options: InboxPromoteOptions): Promise<voi
196
172
  console.log();
197
173
  console.log(" " + c.dim("Task ID:") + " " + c.cyan(task.id));
198
174
  console.log(" " + c.dim("Title:") + " " + task.title);
199
- console.log(" " + c.dim("Project:") + " " + project.name);
175
+ console.log(" " + c.dim("Workspace:") + " " + task.workspace);
200
176
  console.log(" " + c.dim("Priority:") + " " + formatPriority(task.priority));
201
177
  console.log();
202
178
  }
@@ -215,7 +191,7 @@ export async function inboxDiscardCmd(itemId: string): Promise<void> {
215
191
  }
216
192
 
217
193
  console.log();
218
- console.log(c.dim("×") + " Discarded: " + item.content.slice(0, 50) + "...");
194
+ console.log(c.dim("×") + " Discarded: " + item.content.slice(0, INBOX_DISCARD_PREVIEW_LENGTH) + "...");
219
195
  console.log();
220
196
  }
221
197
 
@@ -251,4 +227,3 @@ export async function inboxCountCmd(): Promise<void> {
251
227
  }
252
228
  console.log();
253
229
  }
254
-
@@ -1,14 +1,15 @@
1
1
  /**
2
- * List commands - list tasks and projects
2
+ * List commands - list tasks and workspaces
3
3
  */
4
4
 
5
5
  import { c, table, formatStatus, formatPriority, type TableColumn } from "../ansi.js";
6
6
  import {
7
- listProjects,
7
+ listWorkspaces,
8
8
  listTasks,
9
9
  listAllTasks,
10
+ getCurrentWorkspace,
10
11
  type Task,
11
- type Project,
12
+ type WorkspaceInfo,
12
13
  } from "../storage.js";
13
14
  import {
14
15
  COLUMN_WIDTH_ID,
@@ -17,18 +18,9 @@ import {
17
18
  COLUMN_WIDTH_STATUS,
18
19
  COLUMN_WIDTH_PRIORITY,
19
20
  COLUMN_WIDTH_DATE,
21
+ ID_DISPLAY_LENGTH,
20
22
  } from "../constants.js";
21
23
 
22
- function formatProjectStatus(status: Project["status"]): string {
23
- const statusMap: Record<string, string> = {
24
- active: c.green("● active"),
25
- on_hold: c.yellow("◐ on_hold"),
26
- completed: c.blue("✓ completed"),
27
- archived: c.gray("⊘ archived"),
28
- };
29
- return statusMap[status] ?? status;
30
- }
31
-
32
24
  function formatDate(dateStr?: string): string {
33
25
  if (!dateStr) return c.gray("-");
34
26
  const date = new Date(dateStr);
@@ -36,17 +28,20 @@ function formatDate(dateStr?: string): string {
36
28
  }
37
29
 
38
30
  export async function listTasksCmd(options: {
39
- projectId?: string;
31
+ workspace?: string;
40
32
  status?: string;
41
33
  priority?: string;
42
34
  all?: boolean;
43
35
  }): Promise<void> {
44
36
  let tasks: Task[];
45
37
 
46
- if (options.projectId) {
47
- tasks = await listTasks(options.projectId);
38
+ if (options.workspace) {
39
+ // Filter by specific workspace
40
+ const allTasks = await listAllTasks();
41
+ tasks = allTasks.filter(t => t.workspace === options.workspace || t.workspace.startsWith(options.workspace!));
48
42
  } else {
49
- tasks = await listAllTasks();
43
+ // Current workspace
44
+ tasks = await listTasks();
50
45
  }
51
46
 
52
47
  // Apply filters
@@ -68,10 +63,11 @@ export async function listTasksCmd(options: {
68
63
  return;
69
64
  }
70
65
 
71
- console.log(c.bold(`\nTasks (${tasks.length})\n`));
66
+ const currentWorkspace = getCurrentWorkspace();
67
+ console.log(c.bold(`\nTasks (${tasks.length}) - ${c.cyan(currentWorkspace)}\n`));
72
68
 
73
69
  const columns: TableColumn[] = [
74
- { header: "ID", key: "id", width: COLUMN_WIDTH_ID, format: (v) => c.cyan(String(v).slice(0, 4)) },
70
+ { header: "ID", key: "id", width: COLUMN_WIDTH_ID, format: (v) => c.cyan(String(v).slice(0, ID_DISPLAY_LENGTH)) },
75
71
  { header: "Title", key: "title", width: COLUMN_WIDTH_TITLE },
76
72
  { header: "Status", key: "status", width: COLUMN_WIDTH_STATUS, format: (v) => formatStatus(v as Task["status"]) },
77
73
  { header: "Priority", key: "priority", width: COLUMN_WIDTH_PRIORITY, format: (v) => formatPriority(v as Task["priority"]) },
@@ -82,40 +78,29 @@ export async function listTasksCmd(options: {
82
78
  console.log();
83
79
  }
84
80
 
85
- export async function listProjectsCmd(options: {
86
- all?: boolean;
87
- }): Promise<void> {
88
- const projects = await listProjects(options.all);
81
+ export async function listWorkspacesCmd(): Promise<void> {
82
+ const workspaces = await listWorkspaces();
83
+ const currentWorkspace = getCurrentWorkspace();
89
84
 
90
- if (projects.length === 0) {
91
- console.log(c.gray("No projects found."));
85
+ if (workspaces.length === 0) {
86
+ console.log(c.gray("No workspaces found."));
92
87
  return;
93
88
  }
94
89
 
95
- console.log(c.bold(`\nProjects (${projects.length})\n`));
96
-
97
- // Get task counts for each project
98
- const projectsWithStats = await Promise.all(
99
- projects.map(async p => {
100
- const tasks = await listTasks(p.id);
101
- const completed = tasks.filter(t => t.status === "completed").length;
102
- const total = tasks.filter(t => t.status !== "cancelled").length;
103
- return {
104
- ...p,
105
- taskCount: tasks.length,
106
- progress: total > 0 ? `${completed}/${total}` : "0/0",
107
- };
108
- })
109
- );
90
+ console.log(c.bold(`\nWorkspaces (${workspaces.length})\n`));
91
+
92
+ const workspacesWithCurrent = workspaces.map(ws => ({
93
+ ...ws,
94
+ current: ws.name === currentWorkspace ? c.green("●") : "",
95
+ progress: `${ws.completedCount}/${ws.taskCount}`,
96
+ }));
110
97
 
111
98
  const columns: TableColumn[] = [
112
- { header: "ID", key: "id", width: COLUMN_WIDTH_ID, format: (v) => c.cyan(String(v).slice(0, 4)) },
113
- { header: "Name", key: "name", width: COLUMN_WIDTH_PROJECT_NAME },
114
- { header: "Status", key: "status", width: COLUMN_WIDTH_STATUS, format: (v) => formatProjectStatus(v as Project["status"]) },
99
+ { header: "", key: "current", width: 2 },
100
+ { header: "Name", key: "name", width: COLUMN_WIDTH_PROJECT_NAME, format: (v) => c.cyan(String(v)) },
115
101
  { header: "Tasks", key: "progress", width: COLUMN_WIDTH_PRIORITY },
116
- { header: "Updated", key: "updatedAt", width: COLUMN_WIDTH_DATE, format: (v) => formatDate(v as string) },
117
102
  ];
118
103
 
119
- console.log(table(projectsWithStats, columns));
104
+ console.log(table(workspacesWithCurrent, columns));
120
105
  console.log();
121
106
  }
package/src/constants.ts CHANGED
@@ -54,3 +54,6 @@ export const INBOX_PROMOTE_TITLE_MAX_LENGTH = 100;
54
54
 
55
55
  /** Maximum length for content preview when discarding inbox item */
56
56
  export const INBOX_DISCARD_PREVIEW_LENGTH = 50;
57
+
58
+ /** Number of characters to show for truncated task/project IDs */
59
+ export const ID_DISPLAY_LENGTH = 4;
package/src/index.ts CHANGED
@@ -4,18 +4,18 @@
4
4
  *
5
5
  * Usage:
6
6
  * task # Show dashboard (default)
7
- * task dashboard [id] # Show project dashboard
8
- * task d [id] # Alias for dashboard
7
+ * task dashboard [ws] # Show workspace dashboard
8
+ * task d [ws] # Alias for dashboard
9
9
  * task list # List tasks
10
10
  * task ls # Alias for list
11
- * task projects # List projects
12
- * task p # Alias for projects
11
+ * task workspaces # List workspaces
12
+ * task ws # Alias for workspaces
13
13
  * task help # Show help
14
14
  */
15
15
 
16
16
  import { c, banner } from "./ansi.js";
17
17
  import { dashboard } from "./commands/dashboard.js";
18
- import { listTasksCmd, listProjectsCmd } from "./commands/list.js";
18
+ import { listTasksCmd, listWorkspacesCmd } from "./commands/list.js";
19
19
  import { startInteractive } from "./interactive.js";
20
20
  import {
21
21
  inboxAddCmd,
@@ -167,12 +167,11 @@ async function handleInboxCommand(
167
167
 
168
168
  case "promote":
169
169
  if (!args[1]) {
170
- console.log(c.error("Usage: task inbox promote <item-id> [--project <id>] [--priority <p>]"));
170
+ console.log(c.error("Usage: task inbox promote <item-id> [--priority <p>]"));
171
171
  return;
172
172
  }
173
173
  await inboxPromoteCmd({
174
174
  itemId: args[1],
175
- projectId: flags.project as string | undefined,
176
175
  title: flags.title as string | undefined,
177
176
  priority: flags.priority as "critical" | "high" | "medium" | "low" | undefined,
178
177
  });
@@ -224,18 +223,16 @@ async function main(): Promise<void> {
224
223
  case "list":
225
224
  case "ls":
226
225
  await listTasksCmd({
227
- projectId: args[0],
226
+ workspace: args[0],
228
227
  status: flags.status as string | undefined,
229
228
  priority: flags.priority as string | undefined,
230
229
  all: flags.all === true,
231
230
  });
232
231
  break;
233
232
 
234
- case "projects":
235
- case "p":
236
- await listProjectsCmd({
237
- all: flags.all === true,
238
- });
233
+ case "workspaces":
234
+ case "ws":
235
+ await listWorkspacesCmd();
239
236
  break;
240
237
 
241
238
  case "interactive":
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { c, banner } from "./ansi.js";
7
7
  import { dashboard } from "./commands/dashboard.js";
8
- import { listTasksCmd, listProjectsCmd } from "./commands/list.js";
8
+ import { listTasksCmd, listWorkspacesCmd } from "./commands/list.js";
9
9
  import * as readline from "node:readline";
10
10
  import {
11
11
  MENU_SEPARATOR_WIDTH,
@@ -87,17 +87,17 @@ async function mainMenu(): Promise<boolean> {
87
87
  {
88
88
  key: "d",
89
89
  label: "Dashboard",
90
- description: "View project dashboard",
90
+ description: "View workspace dashboard",
91
91
  action: async () => {
92
92
  await dashboardMenu();
93
93
  },
94
94
  },
95
95
  {
96
- key: "p",
97
- label: "Projects",
98
- description: "List and manage projects",
96
+ key: "w",
97
+ label: "Workspaces",
98
+ description: "List workspaces",
99
99
  action: async () => {
100
- await projectsMenu();
100
+ await workspacesMenu();
101
101
  },
102
102
  },
103
103
  {
@@ -132,35 +132,25 @@ async function dashboardMenu(): Promise<void> {
132
132
  console.log(c.dim("─".repeat(MENU_SEPARATOR_WIDTH)));
133
133
  console.log();
134
134
 
135
- const projectId = await prompt(c.cyan("Project ID (Enter for all): "));
135
+ const workspace = await prompt(c.cyan("Workspace name (Enter for current): "));
136
136
 
137
137
  console.log();
138
- await dashboard(projectId || undefined);
138
+ await dashboard(workspace || undefined);
139
139
  console.log();
140
140
  await waitForKey();
141
141
  }
142
142
 
143
143
  /**
144
- * Projects Menu
144
+ * Workspaces Menu
145
145
  */
146
- async function projectsMenu(): Promise<void> {
146
+ async function workspacesMenu(): Promise<void> {
147
147
  const options: MenuOption[] = [
148
148
  {
149
149
  key: "l",
150
- label: "List Projects",
151
- action: async () => {
152
- console.clear();
153
- await listProjectsCmd({ all: false });
154
- console.log();
155
- await waitForKey();
156
- },
157
- },
158
- {
159
- key: "a",
160
- label: "List All (include archived)",
150
+ label: "List Workspaces",
161
151
  action: async () => {
162
152
  console.clear();
163
- await listProjectsCmd({ all: true });
153
+ await listWorkspacesCmd();
164
154
  console.log();
165
155
  await waitForKey();
166
156
  },
@@ -172,7 +162,7 @@ async function projectsMenu(): Promise<void> {
172
162
  },
173
163
  ];
174
164
 
175
- await showMenu("Projects", options);
165
+ await showMenu("Workspaces", options);
176
166
  }
177
167
 
178
168
  /**
package/src/storage.ts CHANGED
@@ -3,20 +3,19 @@
3
3
  * Wrapper functions for store access and pure functions for task statistics
4
4
  */
5
5
 
6
- import type { Task, Project, InboxItem } from "@task-mcp/shared";
6
+ import type { Task, InboxItem } from "@task-mcp/shared";
7
+ import type { WorkspaceInfo } from "@task-mcp/shared";
7
8
  import {
8
9
  TaskStore,
9
- ProjectStore,
10
10
  InboxStore,
11
11
  StateStore,
12
12
  } from "@task-mcp/mcp-server/storage";
13
13
 
14
14
  // Re-export types from shared for consumers of this module
15
- export type { Task, Project, InboxItem };
15
+ export type { Task, InboxItem, WorkspaceInfo };
16
16
 
17
17
  // Initialize stores
18
18
  const taskStore = new TaskStore();
19
- const projectStore = new ProjectStore();
20
19
  const inboxStore = new InboxStore();
21
20
  const stateStore = new StateStore();
22
21
 
@@ -25,25 +24,40 @@ const stateStore = new StateStore();
25
24
  // =============================================================================
26
25
 
27
26
  /**
28
- * List all projects
27
+ * Get current workspace name
29
28
  */
30
- export async function listProjects(includeArchived = false): Promise<Project[]> {
31
- const projects = await projectStore.list();
32
- if (includeArchived) {
33
- return projects;
29
+ export function getCurrentWorkspace(): string {
30
+ return taskStore.currentWorkspace;
31
+ }
32
+
33
+ /**
34
+ * List all workspaces
35
+ */
36
+ export async function listWorkspaces(): Promise<WorkspaceInfo[]> {
37
+ const workspaceNames = await taskStore.listWorkspaces();
38
+ const workspaces: WorkspaceInfo[] = [];
39
+
40
+ for (const name of workspaceNames) {
41
+ const tasks = await taskStore.list({ workspaces: [name] });
42
+ workspaces.push({
43
+ name,
44
+ taskCount: tasks.length,
45
+ completedCount: tasks.filter(t => t.status === "completed").length,
46
+ });
34
47
  }
35
- return projects.filter((p) => p.status !== "archived");
48
+
49
+ return workspaces;
36
50
  }
37
51
 
38
52
  /**
39
- * List tasks for a specific project
53
+ * List tasks for current workspace
40
54
  */
41
- export async function listTasks(projectId: string): Promise<Task[]> {
42
- return taskStore.list(projectId);
55
+ export async function listTasks(): Promise<Task[]> {
56
+ return taskStore.list();
43
57
  }
44
58
 
45
59
  /**
46
- * List all tasks across all projects
60
+ * List all tasks across all workspaces
47
61
  */
48
62
  export async function listAllTasks(): Promise<Task[]> {
49
63
  return taskStore.listAll();
@@ -66,9 +80,9 @@ export async function getActiveTag(): Promise<string> {
66
80
  }
67
81
 
68
82
  /**
69
- * Get project statistics
83
+ * Get workspace statistics
70
84
  */
71
- export interface ProjectStats {
85
+ export interface WorkspaceStats {
72
86
  total: number;
73
87
  completed: number;
74
88
  inProgress: number;
@@ -79,8 +93,8 @@ export interface ProjectStats {
79
93
  completionPercent: number;
80
94
  }
81
95
 
82
- export function calculateStats(tasks: Task[]): ProjectStats {
83
- const stats: ProjectStats = {
96
+ export function calculateStats(tasks: Task[]): WorkspaceStats {
97
+ const stats: WorkspaceStats = {
84
98
  total: tasks.length,
85
99
  completed: 0,
86
100
  inProgress: 0,