@1medium/cli 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1medium/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "CLI and MCP server for 1Medium AI task management",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -127,6 +127,62 @@ async function listOrgs() {
127
127
  return request("GET", "/orgs");
128
128
  }
129
129
 
130
+ /**
131
+ * List spaces (top-level priority groups)
132
+ */
133
+ async function listSpaces() {
134
+ return request("GET", "/spaces");
135
+ }
136
+
137
+ /**
138
+ * Create a space
139
+ */
140
+ async function createSpace(payload) {
141
+ return request("POST", "/spaces", payload);
142
+ }
143
+
144
+ /**
145
+ * Update a space
146
+ */
147
+ async function updateSpace(id, payload) {
148
+ return request("PATCH", `/spaces/${id}`, payload);
149
+ }
150
+
151
+ /**
152
+ * Delete a space
153
+ */
154
+ async function deleteSpace(id) {
155
+ return request("DELETE", `/spaces/${id}`);
156
+ }
157
+
158
+ /**
159
+ * List projects (optionally filtered by space)
160
+ */
161
+ async function listProjects(params = {}) {
162
+ return request("GET", "/projects", null, params);
163
+ }
164
+
165
+ /**
166
+ * Create a project
167
+ */
168
+ async function createProject(payload) {
169
+ return request("POST", "/projects", payload);
170
+ }
171
+
172
+ /**
173
+ * Update a project
174
+ */
175
+ async function updateProject(id, payload) {
176
+ return request("PATCH", `/projects/${id}`, payload);
177
+ }
178
+
179
+ /**
180
+ * Delete a project
181
+ */
182
+ async function deleteProject(id) {
183
+ return request("DELETE", `/projects/${id}`);
184
+ }
185
+
130
186
  module.exports = {
131
187
  whoami,
132
188
  createTask,
@@ -136,4 +192,12 @@ module.exports = {
136
192
  addComment,
137
193
  completeTask,
138
194
  listOrgs,
195
+ listSpaces,
196
+ createSpace,
197
+ updateSpace,
198
+ deleteSpace,
199
+ listProjects,
200
+ createProject,
201
+ updateProject,
202
+ deleteProject,
139
203
  };
package/src/config.js CHANGED
@@ -21,6 +21,22 @@ const config = new Conf({
21
21
  type: "string",
22
22
  default: "",
23
23
  },
24
+ spaceId: {
25
+ type: "string",
26
+ default: "",
27
+ },
28
+ spaceName: {
29
+ type: "string",
30
+ default: "",
31
+ },
32
+ projectId: {
33
+ type: "string",
34
+ default: "",
35
+ },
36
+ projectName: {
37
+ type: "string",
38
+ default: "",
39
+ },
24
40
  },
25
41
  });
26
42
 
@@ -37,4 +53,12 @@ if (process.env.ONEMEDIUM_ORG_ID) {
37
53
  config.set("orgId", process.env.ONEMEDIUM_ORG_ID);
38
54
  }
39
55
 
56
+ if (process.env.ONEMEDIUM_SPACE_ID) {
57
+ config.set("spaceId", process.env.ONEMEDIUM_SPACE_ID);
58
+ }
59
+
60
+ if (process.env.ONEMEDIUM_PROJECT_ID) {
61
+ config.set("projectId", process.env.ONEMEDIUM_PROJECT_ID);
62
+ }
63
+
40
64
  module.exports = config;
package/src/index.js CHANGED
@@ -110,6 +110,7 @@ taskCmd
110
110
  .option("-b, --body <body>", "Task body (markdown)")
111
111
  .option("-p, --priority <priority>", "Priority: P0, P1, P2, P3", "P2")
112
112
  .option("-s, --status <status>", "Status: inbox, open, doing, blocked, done", "inbox")
113
+ .option("--project <project-id>", "Project ID to associate the task with (uses default if not specified)")
113
114
  .option("--tag <tags...>", "Tags to add")
114
115
  .option("--repo <repo>", "Repository context (owner/repo)")
115
116
  .option("--branch <branch>", "Branch context")
@@ -132,6 +133,9 @@ taskCmd
132
133
  if (options.pr) context.pr_url = options.pr;
133
134
  if (options.issue) context.issue_url = options.issue;
134
135
 
136
+ // Use provided project_id or fall back to configured default
137
+ const projectId = options.project || config.get("projectId") || null;
138
+
135
139
  const payload = {
136
140
  title: options.title,
137
141
  body_md: options.body,
@@ -142,6 +146,7 @@ taskCmd
142
146
  source: options.source,
143
147
  estimate_minutes: options.estimate,
144
148
  due_at: options.due,
149
+ project_id: projectId,
145
150
  dedupe: {
146
151
  mode: options.dedupe === false ? "none" : "auto",
147
152
  },
@@ -161,6 +166,9 @@ taskCmd
161
166
  console.log(` Title: ${data.task.title}`);
162
167
  console.log(` Status: ${data.task.status}`);
163
168
  console.log(` Priority: ${data.task.priority}`);
169
+ if (data.task.project_name) {
170
+ console.log(` Project: ${data.task.project_name}`);
171
+ }
164
172
  }
165
173
  } catch (error) {
166
174
  console.error(chalk.red(`Error: ${error.message}`));
@@ -406,6 +414,306 @@ program
406
414
  }
407
415
  });
408
416
 
417
+ // ============================================================================
418
+ // Space Commands
419
+ // ============================================================================
420
+
421
+ const spaceCmd = program.command("space").description("Manage spaces");
422
+
423
+ spaceCmd
424
+ .command("list")
425
+ .description("List your spaces (top-level groups)")
426
+ .option("-j, --json", "Output as JSON")
427
+ .action(async (options) => {
428
+ try {
429
+ const data = await api.listSpaces();
430
+ const currentSpaceId = config.get("spaceId");
431
+
432
+ if (options.json) {
433
+ console.log(JSON.stringify(data, null, 2));
434
+ } else {
435
+ console.log(chalk.bold("\nSpaces:\n"));
436
+ if (data.spaces.length === 0) {
437
+ console.log(" No spaces found.");
438
+ } else {
439
+ for (const space of data.spaces) {
440
+ const current = space.id === currentSpaceId ? chalk.green(" (current)") : "";
441
+ console.log(` ${space.name}${current}`);
442
+ console.log(chalk.gray(` ID: ${space.id}`));
443
+ }
444
+ }
445
+ console.log("");
446
+ if (!currentSpaceId) {
447
+ console.log(chalk.yellow("No default space set. Use: 1m config set space <space-id>"));
448
+ console.log("");
449
+ }
450
+ }
451
+ } catch (error) {
452
+ console.error(chalk.red(`Error: ${error.message}`));
453
+ process.exit(1);
454
+ }
455
+ });
456
+
457
+ spaceCmd
458
+ .command("add")
459
+ .description("Create a new space")
460
+ .requiredOption("-n, --name <name>", "Space name (min 2 characters)")
461
+ .option("-j, --json", "Output as JSON")
462
+ .action(async (options) => {
463
+ try {
464
+ const data = await api.createSpace({ name: options.name });
465
+
466
+ if (options.json) {
467
+ console.log(JSON.stringify(data, null, 2));
468
+ } else {
469
+ console.log(chalk.green("Space created:"));
470
+ console.log(` ID: ${data.space.id}`);
471
+ console.log(` Name: ${data.space.name}`);
472
+ }
473
+ } catch (error) {
474
+ console.error(chalk.red(`Error: ${error.message}`));
475
+ process.exit(1);
476
+ }
477
+ });
478
+
479
+ spaceCmd
480
+ .command("update <id>")
481
+ .description("Update a space")
482
+ .option("-n, --name <name>", "New space name")
483
+ .option("-j, --json", "Output as JSON")
484
+ .action(async (id, options) => {
485
+ try {
486
+ const payload = {};
487
+ if (options.name) payload.name = options.name;
488
+
489
+ const data = await api.updateSpace(id, payload);
490
+
491
+ if (options.json) {
492
+ console.log(JSON.stringify(data, null, 2));
493
+ } else {
494
+ console.log(chalk.green("Space updated:"));
495
+ console.log(` ID: ${data.space.id}`);
496
+ console.log(` Name: ${data.space.name}`);
497
+ }
498
+ } catch (error) {
499
+ console.error(chalk.red(`Error: ${error.message}`));
500
+ process.exit(1);
501
+ }
502
+ });
503
+
504
+ spaceCmd
505
+ .command("delete <id>")
506
+ .description("Delete a space (must be empty)")
507
+ .option("-j, --json", "Output as JSON")
508
+ .action(async (id, options) => {
509
+ try {
510
+ const data = await api.deleteSpace(id);
511
+
512
+ if (options.json) {
513
+ console.log(JSON.stringify(data, null, 2));
514
+ } else {
515
+ console.log(chalk.green(`Space deleted: ${id}`));
516
+ }
517
+ } catch (error) {
518
+ console.error(chalk.red(`Error: ${error.message}`));
519
+ process.exit(1);
520
+ }
521
+ });
522
+
523
+ // Alias: `1m spaces` -> `1m space list`
524
+ program
525
+ .command("spaces")
526
+ .description("List your spaces (alias for 'space list')")
527
+ .option("-j, --json", "Output as JSON")
528
+ .action(async (options) => {
529
+ try {
530
+ const data = await api.listSpaces();
531
+ const currentSpaceId = config.get("spaceId");
532
+
533
+ if (options.json) {
534
+ console.log(JSON.stringify(data, null, 2));
535
+ } else {
536
+ console.log(chalk.bold("\nSpaces:\n"));
537
+ if (data.spaces.length === 0) {
538
+ console.log(" No spaces found.");
539
+ } else {
540
+ for (const space of data.spaces) {
541
+ const current = space.id === currentSpaceId ? chalk.green(" (current)") : "";
542
+ console.log(` ${space.name}${current}`);
543
+ console.log(chalk.gray(` ID: ${space.id}`));
544
+ }
545
+ }
546
+ console.log("");
547
+ }
548
+ } catch (error) {
549
+ console.error(chalk.red(`Error: ${error.message}`));
550
+ process.exit(1);
551
+ }
552
+ });
553
+
554
+ // ============================================================================
555
+ // Project Commands
556
+ // ============================================================================
557
+
558
+ const projectCmd = program.command("project").description("Manage projects");
559
+
560
+ projectCmd
561
+ .command("list")
562
+ .description("List your projects")
563
+ .option("-s, --space <space-id>", "Filter by space ID")
564
+ .option("-j, --json", "Output as JSON")
565
+ .action(async (options) => {
566
+ try {
567
+ const params = {};
568
+ if (options.space) params.space_id = options.space;
569
+
570
+ const data = await api.listProjects(params);
571
+ const currentProjectId = config.get("projectId");
572
+
573
+ if (options.json) {
574
+ console.log(JSON.stringify(data, null, 2));
575
+ } else {
576
+ console.log(chalk.bold("\nProjects:\n"));
577
+ if (data.projects.length === 0) {
578
+ console.log(" No projects found.");
579
+ } else {
580
+ for (const project of data.projects) {
581
+ const current = project.id === currentProjectId ? chalk.green(" (current)") : "";
582
+ console.log(` ${project.name}${current}`);
583
+ console.log(chalk.gray(` ID: ${project.id}`));
584
+ if (project.space_name) {
585
+ console.log(chalk.gray(` Space: ${project.space_name}`));
586
+ }
587
+ }
588
+ }
589
+ console.log("");
590
+ if (!currentProjectId) {
591
+ console.log(chalk.yellow("No default project set. Use: 1m config set project <project-id>"));
592
+ console.log("");
593
+ }
594
+ }
595
+ } catch (error) {
596
+ console.error(chalk.red(`Error: ${error.message}`));
597
+ process.exit(1);
598
+ }
599
+ });
600
+
601
+ projectCmd
602
+ .command("add")
603
+ .description("Create a new project")
604
+ .requiredOption("-n, --name <name>", "Project name (min 2 characters)")
605
+ .option("-s, --space <space-id>", "Space ID to put the project in")
606
+ .option("-j, --json", "Output as JSON")
607
+ .action(async (options) => {
608
+ try {
609
+ const payload = { name: options.name };
610
+ if (options.space) payload.space_id = options.space;
611
+
612
+ const data = await api.createProject(payload);
613
+
614
+ if (options.json) {
615
+ console.log(JSON.stringify(data, null, 2));
616
+ } else {
617
+ console.log(chalk.green("Project created:"));
618
+ console.log(` ID: ${data.project.id}`);
619
+ console.log(` Name: ${data.project.name}`);
620
+ if (data.project.space_name) {
621
+ console.log(` Space: ${data.project.space_name}`);
622
+ }
623
+ }
624
+ } catch (error) {
625
+ console.error(chalk.red(`Error: ${error.message}`));
626
+ process.exit(1);
627
+ }
628
+ });
629
+
630
+ projectCmd
631
+ .command("update <id>")
632
+ .description("Update a project")
633
+ .option("-n, --name <name>", "New project name")
634
+ .option("-s, --space <space-id>", "Move to a different space")
635
+ .option("-j, --json", "Output as JSON")
636
+ .action(async (id, options) => {
637
+ try {
638
+ const payload = {};
639
+ if (options.name) payload.name = options.name;
640
+ if (options.space !== undefined) payload.space_id = options.space;
641
+
642
+ const data = await api.updateProject(id, payload);
643
+
644
+ if (options.json) {
645
+ console.log(JSON.stringify(data, null, 2));
646
+ } else {
647
+ console.log(chalk.green("Project updated:"));
648
+ console.log(` ID: ${data.project.id}`);
649
+ console.log(` Name: ${data.project.name}`);
650
+ if (data.project.space_name) {
651
+ console.log(` Space: ${data.project.space_name}`);
652
+ }
653
+ }
654
+ } catch (error) {
655
+ console.error(chalk.red(`Error: ${error.message}`));
656
+ process.exit(1);
657
+ }
658
+ });
659
+
660
+ projectCmd
661
+ .command("delete <id>")
662
+ .description("Delete a project (must be empty)")
663
+ .option("-j, --json", "Output as JSON")
664
+ .action(async (id, options) => {
665
+ try {
666
+ const data = await api.deleteProject(id);
667
+
668
+ if (options.json) {
669
+ console.log(JSON.stringify(data, null, 2));
670
+ } else {
671
+ console.log(chalk.green(`Project deleted: ${id}`));
672
+ }
673
+ } catch (error) {
674
+ console.error(chalk.red(`Error: ${error.message}`));
675
+ process.exit(1);
676
+ }
677
+ });
678
+
679
+ // Alias: `1m projects` -> `1m project list`
680
+ program
681
+ .command("projects")
682
+ .description("List your projects (alias for 'project list')")
683
+ .option("-s, --space <space-id>", "Filter by space ID")
684
+ .option("-j, --json", "Output as JSON")
685
+ .action(async (options) => {
686
+ try {
687
+ const params = {};
688
+ if (options.space) params.space_id = options.space;
689
+
690
+ const data = await api.listProjects(params);
691
+ const currentProjectId = config.get("projectId");
692
+
693
+ if (options.json) {
694
+ console.log(JSON.stringify(data, null, 2));
695
+ } else {
696
+ console.log(chalk.bold("\nProjects:\n"));
697
+ if (data.projects.length === 0) {
698
+ console.log(" No projects found.");
699
+ } else {
700
+ for (const project of data.projects) {
701
+ const current = project.id === currentProjectId ? chalk.green(" (current)") : "";
702
+ console.log(` ${project.name}${current}`);
703
+ console.log(chalk.gray(` ID: ${project.id}`));
704
+ if (project.space_name) {
705
+ console.log(chalk.gray(` Space: ${project.space_name}`));
706
+ }
707
+ }
708
+ }
709
+ console.log("");
710
+ }
711
+ } catch (error) {
712
+ console.error(chalk.red(`Error: ${error.message}`));
713
+ process.exit(1);
714
+ }
715
+ });
716
+
409
717
  // ============================================================================
410
718
  // Config Commands
411
719
  // ============================================================================
@@ -420,11 +728,17 @@ configCmd
420
728
  const apiUrl = config.get("apiUrl");
421
729
  const orgId = config.get("orgId");
422
730
  const orgName = config.get("orgName");
731
+ const spaceId = config.get("spaceId");
732
+ const spaceName = config.get("spaceName");
733
+ const projectId = config.get("projectId");
734
+ const projectName = config.get("projectName");
423
735
 
424
736
  console.log(chalk.bold("\nConfiguration:\n"));
425
737
  console.log(` API URL: ${apiUrl || "https://1medium.ai/api (default)"}`);
426
738
  console.log(` Token: ${token ? token.substring(0, 12) + "..." : chalk.yellow("Not set")}`);
427
739
  console.log(` Org: ${orgName || chalk.yellow("Not set")}${orgId ? chalk.gray(` (${orgId})`) : ""}`);
740
+ console.log(` Space: ${spaceName || chalk.yellow("Not set")}${spaceId ? chalk.gray(` (${spaceId})`) : ""}`);
741
+ console.log(` Project: ${projectName || chalk.yellow("Not set")}${projectId ? chalk.gray(` (${projectId})`) : ""}`);
428
742
  console.log(` Config: ${config.path}`);
429
743
  console.log("");
430
744
  });
@@ -432,12 +746,12 @@ configCmd
432
746
  configCmd
433
747
  .command("set")
434
748
  .description("Set a configuration value")
435
- .argument("<key>", "Configuration key (org, url)")
749
+ .argument("<key>", "Configuration key (org, space, project, url)")
436
750
  .argument("<value>", "Configuration value")
437
751
  .action(async (key, value) => {
438
752
  try {
439
753
  switch (key) {
440
- case "org":
754
+ case "org": {
441
755
  // Validate org exists and user has access
442
756
  const data = await api.listOrgs();
443
757
  const org = data.orgs.find((o) => o.id === value || o.name.toLowerCase() === value.toLowerCase());
@@ -453,13 +767,48 @@ configCmd
453
767
  config.set("orgName", org.name);
454
768
  console.log(chalk.green(`Default org set to: ${org.name}`));
455
769
  break;
770
+ }
771
+ case "space": {
772
+ // Validate space exists and user has access
773
+ const spaceData = await api.listSpaces();
774
+ const space = spaceData.spaces.find((s) => s.id === value || s.name.toLowerCase() === value.toLowerCase());
775
+ if (!space) {
776
+ console.error(chalk.red(`Error: Space not found: ${value}`));
777
+ console.log("\nAvailable spaces:");
778
+ for (const s of spaceData.spaces) {
779
+ console.log(` ${s.name} (${s.id})`);
780
+ }
781
+ process.exit(1);
782
+ }
783
+ config.set("spaceId", space.id);
784
+ config.set("spaceName", space.name);
785
+ console.log(chalk.green(`Default space set to: ${space.name}`));
786
+ break;
787
+ }
788
+ case "project": {
789
+ // Validate project exists and user has access
790
+ const projectData = await api.listProjects();
791
+ const project = projectData.projects.find((p) => p.id === value || p.name.toLowerCase() === value.toLowerCase());
792
+ if (!project) {
793
+ console.error(chalk.red(`Error: Project not found: ${value}`));
794
+ console.log("\nAvailable projects:");
795
+ for (const p of projectData.projects) {
796
+ console.log(` ${p.name} (${p.id})`);
797
+ }
798
+ process.exit(1);
799
+ }
800
+ config.set("projectId", project.id);
801
+ config.set("projectName", project.name);
802
+ console.log(chalk.green(`Default project set to: ${project.name}`));
803
+ break;
804
+ }
456
805
  case "url":
457
806
  config.set("apiUrl", value);
458
807
  console.log(chalk.green(`API URL set to: ${value}`));
459
808
  break;
460
809
  default:
461
810
  console.error(chalk.red(`Unknown config key: ${key}`));
462
- console.log("Available keys: org, url");
811
+ console.log("Available keys: org, space, project, url");
463
812
  process.exit(1);
464
813
  }
465
814
  } catch (error) {
@@ -474,11 +823,17 @@ configCmd.action(() => {
474
823
  const apiUrl = config.get("apiUrl");
475
824
  const orgId = config.get("orgId");
476
825
  const orgName = config.get("orgName");
826
+ const spaceId = config.get("spaceId");
827
+ const spaceName = config.get("spaceName");
828
+ const projectId = config.get("projectId");
829
+ const projectName = config.get("projectName");
477
830
 
478
831
  console.log(chalk.bold("\nConfiguration:\n"));
479
832
  console.log(` API URL: ${apiUrl || "https://1medium.ai/api (default)"}`);
480
833
  console.log(` Token: ${token ? token.substring(0, 12) + "..." : chalk.yellow("Not set")}`);
481
834
  console.log(` Org: ${orgName || chalk.yellow("Not set")}${orgId ? chalk.gray(` (${orgId})`) : ""}`);
835
+ console.log(` Space: ${spaceName || chalk.yellow("Not set")}${spaceId ? chalk.gray(` (${spaceId})`) : ""}`);
836
+ console.log(` Project: ${projectName || chalk.yellow("Not set")}${projectId ? chalk.gray(` (${projectId})`) : ""}`);
482
837
  console.log(` Config: ${config.path}`);
483
838
  console.log("");
484
839
  });
package/src/mcp-server.js CHANGED
@@ -45,6 +45,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
45
45
  enum: ["P1", "P2", "P3", "P4"],
46
46
  description: "Task priority (P1=urgent, P4=low)",
47
47
  },
48
+ project_id: {
49
+ type: "string",
50
+ description: "Project ID to associate the task with. Uses configured default if not specified.",
51
+ },
48
52
  source: {
49
53
  type: "string",
50
54
  description: "Source identifier (e.g., 'claude-code', 'github')",
@@ -157,6 +161,127 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
157
161
  required: ["id", "message"],
158
162
  },
159
163
  },
164
+ {
165
+ name: "space_list",
166
+ description: "List available spaces (top-level groups) in 1Medium",
167
+ inputSchema: {
168
+ type: "object",
169
+ properties: {},
170
+ },
171
+ },
172
+ {
173
+ name: "space_create",
174
+ description: "Create a new space (top-level group) in 1Medium",
175
+ inputSchema: {
176
+ type: "object",
177
+ properties: {
178
+ name: {
179
+ type: "string",
180
+ description: "Name for the new space (min 2 characters)",
181
+ },
182
+ },
183
+ required: ["name"],
184
+ },
185
+ },
186
+ {
187
+ name: "space_update",
188
+ description: "Update an existing space",
189
+ inputSchema: {
190
+ type: "object",
191
+ properties: {
192
+ id: {
193
+ type: "string",
194
+ description: "Space ID to update",
195
+ },
196
+ name: {
197
+ type: "string",
198
+ description: "New name for the space",
199
+ },
200
+ },
201
+ required: ["id"],
202
+ },
203
+ },
204
+ {
205
+ name: "space_delete",
206
+ description: "Delete a space (must be empty)",
207
+ inputSchema: {
208
+ type: "object",
209
+ properties: {
210
+ id: {
211
+ type: "string",
212
+ description: "Space ID to delete",
213
+ },
214
+ },
215
+ required: ["id"],
216
+ },
217
+ },
218
+ {
219
+ name: "project_list",
220
+ description: "List available projects in 1Medium, optionally filtered by space",
221
+ inputSchema: {
222
+ type: "object",
223
+ properties: {
224
+ space_id: {
225
+ type: "string",
226
+ description: "Optional space ID to filter projects",
227
+ },
228
+ },
229
+ },
230
+ },
231
+ {
232
+ name: "project_create",
233
+ description: "Create a new project in 1Medium",
234
+ inputSchema: {
235
+ type: "object",
236
+ properties: {
237
+ name: {
238
+ type: "string",
239
+ description: "Name for the new project (min 2 characters)",
240
+ },
241
+ space_id: {
242
+ type: "string",
243
+ description: "Optional space ID to put the project in",
244
+ },
245
+ },
246
+ required: ["name"],
247
+ },
248
+ },
249
+ {
250
+ name: "project_update",
251
+ description: "Update an existing project",
252
+ inputSchema: {
253
+ type: "object",
254
+ properties: {
255
+ id: {
256
+ type: "string",
257
+ description: "Project ID to update",
258
+ },
259
+ name: {
260
+ type: "string",
261
+ description: "New name for the project",
262
+ },
263
+ space_id: {
264
+ type: "string",
265
+ description: "Move project to a different space (or null to remove from space)",
266
+ },
267
+ },
268
+ required: ["id"],
269
+ },
270
+ },
271
+ {
272
+ name: "project_delete",
273
+ description: "Delete a project (must be empty)",
274
+ inputSchema: {
275
+ type: "object",
276
+ properties: {
277
+ id: {
278
+ type: "string",
279
+ description: "Project ID to delete",
280
+ },
281
+ },
282
+ required: ["id"],
283
+ },
284
+ },
160
285
  ],
161
286
  };
162
287
  });
@@ -184,19 +309,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
184
309
 
185
310
  switch (name) {
186
311
  case "task_create": {
312
+ // Use provided project_id or fall back to configured default
313
+ const projectId = args.project_id || config.get("projectId") || null;
187
314
  const payload = {
188
315
  title: args.title,
189
316
  body_md: args.body,
190
317
  priority: args.priority || "P2",
191
318
  source: args.source || "claude-code",
192
319
  context: args.context || {},
320
+ project_id: projectId,
193
321
  };
194
322
  result = await api.createTask(payload);
323
+ let text = `Task created:\n ID: ${result.task.id}\n Title: ${result.task.title}\n Priority: ${result.task.priority}`;
324
+ if (result.task.project_name) {
325
+ text += `\n Project: ${result.task.project_name}`;
326
+ }
195
327
  return {
196
328
  content: [
197
329
  {
198
330
  type: "text",
199
- text: `Task created:\n ID: ${result.task.id}\n Title: ${result.task.title}\n Priority: ${result.task.priority}`,
331
+ text,
200
332
  },
201
333
  ],
202
334
  };
@@ -276,7 +408,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
276
408
 
277
409
  case "task_complete": {
278
410
  const payload = {};
279
- if (args.summary) payload.completion_summary = args.summary;
411
+ if (args.summary) payload.summary_md = args.summary;
280
412
 
281
413
  result = await api.completeTask(args.id, payload);
282
414
  return {
@@ -290,7 +422,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
290
422
  }
291
423
 
292
424
  case "task_comment": {
293
- result = await api.addComment(args.id, { body_md: args.message });
425
+ result = await api.addComment(args.id, { comment_md: args.message });
294
426
  return {
295
427
  content: [
296
428
  {
@@ -301,6 +433,150 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
301
433
  };
302
434
  }
303
435
 
436
+ case "space_list": {
437
+ result = await api.listSpaces();
438
+ const spaces = result.spaces || [];
439
+
440
+ if (spaces.length === 0) {
441
+ return {
442
+ content: [{ type: "text", text: "No spaces found." }],
443
+ };
444
+ }
445
+
446
+ const spaceList = spaces
447
+ .map((s) => `- ${s.name}\n ID: ${s.id}`)
448
+ .join("\n\n");
449
+
450
+ return {
451
+ content: [
452
+ {
453
+ type: "text",
454
+ text: `Spaces:\n\n${spaceList}`,
455
+ },
456
+ ],
457
+ };
458
+ }
459
+
460
+ case "project_list": {
461
+ const params = {};
462
+ if (args.space_id) params.space_id = args.space_id;
463
+
464
+ result = await api.listProjects(params);
465
+ const projects = result.projects || [];
466
+
467
+ if (projects.length === 0) {
468
+ return {
469
+ content: [{ type: "text", text: "No projects found." }],
470
+ };
471
+ }
472
+
473
+ const projectList = projects
474
+ .map((p) => {
475
+ let text = `- ${p.name}\n ID: ${p.id}`;
476
+ if (p.space_name) {
477
+ text += `\n Space: ${p.space_name}`;
478
+ }
479
+ return text;
480
+ })
481
+ .join("\n\n");
482
+
483
+ return {
484
+ content: [
485
+ {
486
+ type: "text",
487
+ text: `Projects:\n\n${projectList}`,
488
+ },
489
+ ],
490
+ };
491
+ }
492
+
493
+ case "space_create": {
494
+ result = await api.createSpace({ name: args.name });
495
+ return {
496
+ content: [
497
+ {
498
+ type: "text",
499
+ text: `Space created:\n ID: ${result.space.id}\n Name: ${result.space.name}`,
500
+ },
501
+ ],
502
+ };
503
+ }
504
+
505
+ case "space_update": {
506
+ const payload = {};
507
+ if (args.name) payload.name = args.name;
508
+ result = await api.updateSpace(args.id, payload);
509
+ return {
510
+ content: [
511
+ {
512
+ type: "text",
513
+ text: `Space updated:\n ID: ${result.space.id}\n Name: ${result.space.name}`,
514
+ },
515
+ ],
516
+ };
517
+ }
518
+
519
+ case "space_delete": {
520
+ result = await api.deleteSpace(args.id);
521
+ return {
522
+ content: [
523
+ {
524
+ type: "text",
525
+ text: `Space deleted: ${args.id}`,
526
+ },
527
+ ],
528
+ };
529
+ }
530
+
531
+ case "project_create": {
532
+ const payload = { name: args.name };
533
+ if (args.space_id) payload.space_id = args.space_id;
534
+ result = await api.createProject(payload);
535
+ let text = `Project created:\n ID: ${result.project.id}\n Name: ${result.project.name}`;
536
+ if (result.project.space_name) {
537
+ text += `\n Space: ${result.project.space_name}`;
538
+ }
539
+ return {
540
+ content: [
541
+ {
542
+ type: "text",
543
+ text,
544
+ },
545
+ ],
546
+ };
547
+ }
548
+
549
+ case "project_update": {
550
+ const payload = {};
551
+ if (args.name) payload.name = args.name;
552
+ if (args.space_id !== undefined) payload.space_id = args.space_id;
553
+ result = await api.updateProject(args.id, payload);
554
+ let text = `Project updated:\n ID: ${result.project.id}\n Name: ${result.project.name}`;
555
+ if (result.project.space_name) {
556
+ text += `\n Space: ${result.project.space_name}`;
557
+ }
558
+ return {
559
+ content: [
560
+ {
561
+ type: "text",
562
+ text,
563
+ },
564
+ ],
565
+ };
566
+ }
567
+
568
+ case "project_delete": {
569
+ result = await api.deleteProject(args.id);
570
+ return {
571
+ content: [
572
+ {
573
+ type: "text",
574
+ text: `Project deleted: ${args.id}`,
575
+ },
576
+ ],
577
+ };
578
+ }
579
+
304
580
  default:
305
581
  return {
306
582
  content: [{ type: "text", text: `Unknown tool: ${name}` }],