kenzoboard 0.1.3 → 0.1.5

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.
Files changed (3) hide show
  1. package/dist/index.js +139 -7
  2. package/dist/mcp.js +21 -57
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -85,7 +85,7 @@ var init_blob_storage = () => {};
85
85
 
86
86
  // src/index.ts
87
87
  import { resolve as resolve4, dirname as dirname4, basename as basename2 } from "path";
88
- import { execSync } from "child_process";
88
+ import { execFileSync, execSync } from "child_process";
89
89
  import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync3, appendFileSync, cpSync, realpathSync } from "fs";
90
90
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
91
91
  import { createInterface } from "readline";
@@ -4595,6 +4595,9 @@ async function ensureKenzoWorkspace() {
4595
4595
  return { fluxDir, projectId, projectName, createdWorkspace, createdProject, gitignoreUpdated };
4596
4596
  }
4597
4597
  function codexCliCommand() {
4598
+ if (process.env.CODEX_CLI_PATH && existsSync7(process.env.CODEX_CLI_PATH)) {
4599
+ return process.env.CODEX_CLI_PATH;
4600
+ }
4598
4601
  const macAppCli = "/Applications/Codex.app/Contents/Resources/codex";
4599
4602
  return process.platform === "darwin" && existsSync7(macAppCli) ? macAppCli : "codex";
4600
4603
  }
@@ -4605,13 +4608,17 @@ function mcpPackageCommand() {
4605
4608
  const localMcpPath = resolve4(process.cwd(), "packages/mcp/dist/index.js");
4606
4609
  return existsSync7(localMcpPath) ? `bun ${quoteShell(localMcpPath)}` : "npx -y --package kenzoboard kenzoboard-mcp";
4607
4610
  }
4611
+ function mcpPackageArgs() {
4612
+ const localMcpPath = resolve4(process.cwd(), "packages/mcp/dist/index.js");
4613
+ return existsSync7(localMcpPath) ? ["bun", localMcpPath] : ["npx", "-y", "--package", "kenzoboard", "kenzoboard-mcp"];
4614
+ }
4608
4615
  function codexMcpCommand(fluxDir) {
4609
4616
  return `${codexCliCommand()} mcp add flux --env FLUX_DIR=${quoteShell(fluxDir)} -- ${mcpPackageCommand()}`;
4610
4617
  }
4611
4618
  function claudeMcpCommand(fluxDir) {
4612
4619
  return `claude mcp add flux --env FLUX_DIR=${quoteShell(fluxDir)} -- ${mcpPackageCommand()}`;
4613
4620
  }
4614
- function printLaunchSummary(workspace, command = publicCommandName()) {
4621
+ function printLaunchSummary(workspace) {
4615
4622
  const projectLabel = workspace.projectName && workspace.projectId ? `${workspace.projectName} (${workspace.projectId})` : "No project selected";
4616
4623
  console.log(`${c2.green}${c2.bold}Kenzo is ready.${c2.reset}`);
4617
4624
  console.log(`Project: ${projectLabel}`);
@@ -4625,13 +4632,13 @@ function printLaunchSummary(workspace, command = publicCommandName()) {
4625
4632
  }
4626
4633
  console.log("");
4627
4634
  console.log(`${c2.bold}Connect Codex:${c2.reset}`);
4628
- console.log(` ${codexMcpCommand(workspace.fluxDir)}`);
4635
+ console.log(" npx kenzoboard connect codex");
4629
4636
  console.log("");
4630
4637
  console.log(`${c2.bold}Then ask Codex:${c2.reset}`);
4631
4638
  console.log(" Pick the next ready Kenzo task, implement it, and mark it done.");
4632
4639
  console.log("");
4633
- console.log(`CLI fallback: ${c2.cyan}${command} ready${c2.reset}`);
4634
- console.log(`More setup: ${c2.cyan}${command} mcp${c2.reset}`);
4640
+ console.log(`CLI fallback: ${c2.cyan}npx kenzoboard ready${c2.reset}`);
4641
+ console.log(`More setup: ${c2.cyan}npx kenzoboard mcp${c2.reset}`);
4635
4642
  }
4636
4643
  function printMcpSetup(command = publicCommandName(), fluxDir = findFluxDir()) {
4637
4644
  const installedMcpCommand = "kenzoboard-mcp";
@@ -4639,6 +4646,9 @@ function printMcpSetup(command = publicCommandName(), fluxDir = findFluxDir()) {
4639
4646
  console.log(`${c2.bold}Agent setup${c2.reset} ${c2.dim}(MCP resources remain flux:// for compatibility)${c2.reset}`);
4640
4647
  console.log(`${c2.dim}Using Kenzo workspace: ${fluxDir}${c2.reset}
4641
4648
  `);
4649
+ console.log(`${c2.bold}Codex one-step setup:${c2.reset}`);
4650
+ console.log(" npx kenzoboard connect codex");
4651
+ console.log("");
4642
4652
  console.log(`${c2.bold}Codex:${c2.reset}`);
4643
4653
  console.log(` ${codexMcpCommand(fluxDir)}`);
4644
4654
  console.log(`${c2.bold}Claude Code:${c2.reset}`);
@@ -4655,10 +4665,123 @@ function printMcpSetup(command = publicCommandName(), fluxDir = findFluxDir()) {
4655
4665
  console.log(" 2. Start one task, implement it, add notes as needed, then mark it done.");
4656
4666
  console.log(" 3. If MCP is not connected yet, Codex can still use the CLI commands from AGENTS.md.");
4657
4667
  }
4668
+ function codexCliLooksValid(command) {
4669
+ try {
4670
+ const output2 = execFileSync(command, ["mcp", "--help"], {
4671
+ encoding: "utf-8",
4672
+ stdio: ["ignore", "pipe", "pipe"]
4673
+ });
4674
+ return output2.includes("Manage external MCP servers for Codex");
4675
+ } catch {
4676
+ return false;
4677
+ }
4678
+ }
4679
+ function resolveCodexCli() {
4680
+ const candidates = [
4681
+ process.env.CODEX_CLI_PATH,
4682
+ process.platform === "darwin" ? "/Applications/Codex.app/Contents/Resources/codex" : undefined,
4683
+ "codex"
4684
+ ].filter((candidate) => Boolean(candidate));
4685
+ for (const candidate of candidates) {
4686
+ if (candidate.includes("/") && !existsSync7(candidate))
4687
+ continue;
4688
+ if (codexCliLooksValid(candidate))
4689
+ return candidate;
4690
+ }
4691
+ return null;
4692
+ }
4693
+ function runCodex(cli, args) {
4694
+ return execFileSync(cli, args, {
4695
+ encoding: "utf-8",
4696
+ stdio: ["ignore", "pipe", "pipe"]
4697
+ });
4698
+ }
4699
+ function verifyMcpServer(args, fluxDir) {
4700
+ const input = [
4701
+ JSON.stringify({
4702
+ jsonrpc: "2.0",
4703
+ id: 1,
4704
+ method: "initialize",
4705
+ params: {
4706
+ protocolVersion: "2024-11-05",
4707
+ capabilities: {},
4708
+ clientInfo: { name: "kenzoboard-connect", version: "0.0.0" }
4709
+ }
4710
+ }),
4711
+ JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized", params: {} }),
4712
+ JSON.stringify({ jsonrpc: "2.0", id: 2, method: "tools/list", params: {} })
4713
+ ].join(`
4714
+ `) + `
4715
+ `;
4716
+ const output2 = execFileSync(args[0], args.slice(1), {
4717
+ input,
4718
+ encoding: "utf-8",
4719
+ stdio: ["pipe", "pipe", "pipe"],
4720
+ timeout: 30000,
4721
+ env: {
4722
+ ...process.env,
4723
+ FLUX_DIR: fluxDir
4724
+ }
4725
+ });
4726
+ const responses = output2.split(`
4727
+ `).map((line) => line.trim()).filter(Boolean).map((line) => JSON.parse(line));
4728
+ const tools = responses.find((response) => response.id === 2)?.result?.tools;
4729
+ if (!tools?.some((tool) => tool.name === "list_ready_tasks")) {
4730
+ throw new Error("MCP server started, but did not expose the expected Kenzo task tools.");
4731
+ }
4732
+ }
4733
+ async function connectCodexCommand(flags) {
4734
+ const workspace = await ensureKenzoWorkspace();
4735
+ const cli = resolveCodexCli();
4736
+ if (!cli) {
4737
+ console.error("OpenAI Codex CLI was not found.");
4738
+ console.error("Install or open Codex Desktop, then run this again.");
4739
+ console.error("");
4740
+ console.error("Manual command:");
4741
+ console.error(` ${codexMcpCommand(workspace.fluxDir)}`);
4742
+ process.exit(1);
4743
+ }
4744
+ const serverName = typeof flags.name === "string" ? flags.name : "flux";
4745
+ const packageArgs = mcpPackageArgs();
4746
+ const addArgs = [
4747
+ "mcp",
4748
+ "add",
4749
+ serverName,
4750
+ "--env",
4751
+ `FLUX_DIR=${workspace.fluxDir}`,
4752
+ "--",
4753
+ ...packageArgs
4754
+ ];
4755
+ try {
4756
+ runCodex(cli, ["mcp", "get", serverName]);
4757
+ runCodex(cli, ["mcp", "remove", serverName]);
4758
+ } catch {}
4759
+ try {
4760
+ runCodex(cli, addArgs);
4761
+ const result = runCodex(cli, ["mcp", "get", serverName]);
4762
+ if (!result.includes(serverName) || !result.includes(packageArgs[0])) {
4763
+ throw new Error("Codex did not report the expected MCP server after setup.");
4764
+ }
4765
+ verifyMcpServer(packageArgs, workspace.fluxDir);
4766
+ console.log(`${c2.green}${c2.bold}Codex is connected to Kenzo.${c2.reset}`);
4767
+ console.log(`Server: ${serverName}`);
4768
+ console.log(`Workspace: ${workspace.fluxDir}`);
4769
+ console.log("MCP tools: verified");
4770
+ console.log("");
4771
+ console.log(`${c2.bold}Try this in Codex:${c2.reset}`);
4772
+ console.log(" Use Kenzo to pick the next ready task, implement it, and mark it done.");
4773
+ } catch (e) {
4774
+ console.error("Failed to configure Codex MCP.");
4775
+ console.error(e?.message || String(e));
4776
+ console.error("");
4777
+ console.error("Manual command:");
4778
+ console.error(` ${codexMcpCommand(workspace.fluxDir)}`);
4779
+ process.exit(1);
4780
+ }
4781
+ }
4658
4782
  async function launchKenzoApp() {
4659
- const command = publicCommandName();
4660
4783
  const workspace = await ensureKenzoWorkspace();
4661
- printLaunchSummary(workspace, command);
4784
+ printLaunchSummary(workspace);
4662
4785
  await serveCommand([], { port: String(defaultServePort()) }, { defaultPort: defaultServePort(), open: true, compact: true });
4663
4786
  }
4664
4787
  var FLUX_INSTRUCTIONS = `<!-- FLUX:START -->
@@ -5221,6 +5344,14 @@ Select a project:`);
5221
5344
  printMcpSetup();
5222
5345
  return;
5223
5346
  }
5347
+ if (parsed.command === "connect") {
5348
+ if (parsed.subcommand === "codex") {
5349
+ await connectCodexCommand(parsed.flags);
5350
+ return;
5351
+ }
5352
+ console.error(`Usage: ${publicCommandName()} connect codex [--name flux]`);
5353
+ process.exit(1);
5354
+ }
5224
5355
  if (parsed.command === "dev") {
5225
5356
  await launchKenzoApp();
5226
5357
  return;
@@ -5332,6 +5463,7 @@ ${c2.bold}Start:${c2.reset}
5332
5463
  ${c2.cyan}${command} init${c2.reset} ${c2.green}[--server URL] [--api-key KEY] [--sqlite] [--git] [--force]${c2.reset}
5333
5464
  ${c2.cyan}${command} dev${c2.reset} ${c2.green}[--open]${c2.reset} Start the local Kenzo app on port ${defaultServePort()}
5334
5465
  ${c2.cyan}${command} serve${c2.reset} ${c2.green}[-p port] [--data file] [--open]${c2.reset}
5466
+ ${c2.cyan}${command} connect codex${c2.reset} ${c2.green}[--name flux]${c2.reset} Configure Codex MCP for this workspace
5335
5467
  ${c2.cyan}${command} mcp${c2.reset} Show Codex/Claude MCP setup commands
5336
5468
 
5337
5469
  ${c2.bold}Ready Work:${c2.reset}
package/dist/mcp.js CHANGED
@@ -15119,6 +15119,11 @@ var server = new Server({
15119
15119
  tools: {}
15120
15120
  }
15121
15121
  });
15122
+ function jsonContent(value) {
15123
+ return {
15124
+ content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
15125
+ };
15126
+ }
15122
15127
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
15123
15128
  const projects = await getProjects2();
15124
15129
  const resources = [
@@ -15621,11 +15626,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15621
15626
  }
15622
15627
  case "create_project": {
15623
15628
  const project = await createProject2(args2?.name, args2?.description);
15624
- return {
15625
- content: [
15626
- { type: "text", text: `Created project "${project.name}" with ID: ${project.id}` }
15627
- ]
15628
- };
15629
+ return jsonContent({ action: "created_project", project });
15629
15630
  }
15630
15631
  case "update_project": {
15631
15632
  const updates = {};
@@ -15637,15 +15638,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15637
15638
  if (!project) {
15638
15639
  return { content: [{ type: "text", text: "Project not found" }], isError: true };
15639
15640
  }
15640
- return {
15641
- content: [{ type: "text", text: `Updated project: ${JSON.stringify(project, null, 2)}` }]
15642
- };
15641
+ return jsonContent({ action: "updated_project", project });
15643
15642
  }
15644
15643
  case "delete_project": {
15645
- await deleteProject2(args2?.project_id);
15646
- return {
15647
- content: [{ type: "text", text: `Deleted project ${args2?.project_id}` }]
15648
- };
15644
+ const deleted = await deleteProject2(args2?.project_id);
15645
+ return jsonContent({ action: "deleted_project", project_id: args2?.project_id, deleted });
15649
15646
  }
15650
15647
  case "list_epics": {
15651
15648
  const epics = await getEpics2(args2?.project_id);
@@ -15655,9 +15652,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15655
15652
  }
15656
15653
  case "create_epic": {
15657
15654
  const epic = await createEpic2(args2?.project_id, args2?.title, args2?.notes, args2?.auto);
15658
- return {
15659
- content: [{ type: "text", text: `Created epic "${epic.title}" with ID: ${epic.id}` }]
15660
- };
15655
+ return jsonContent({ action: "created_epic", epic });
15661
15656
  }
15662
15657
  case "update_epic": {
15663
15658
  const updates = {};
@@ -15675,18 +15670,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15675
15670
  if (!epic) {
15676
15671
  return { content: [{ type: "text", text: "Epic not found" }], isError: true };
15677
15672
  }
15678
- return {
15679
- content: [{ type: "text", text: `Updated epic: ${JSON.stringify(epic, null, 2)}` }]
15680
- };
15673
+ return jsonContent({ action: "updated_epic", epic });
15681
15674
  }
15682
15675
  case "delete_epic": {
15683
15676
  const success = await deleteEpic2(args2?.epic_id);
15684
15677
  if (!success) {
15685
15678
  return { content: [{ type: "text", text: "Epic not found" }], isError: true };
15686
15679
  }
15687
- return {
15688
- content: [{ type: "text", text: `Deleted epic ${args2?.epic_id}` }]
15689
- };
15680
+ return jsonContent({ action: "deleted_epic", epic_id: args2?.epic_id, deleted: true });
15690
15681
  }
15691
15682
  case "list_tasks": {
15692
15683
  const taskList = await getTasks2(args2?.project_id);
@@ -15715,9 +15706,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15715
15706
  acceptance_criteria: args2?.acceptance_criteria,
15716
15707
  guardrails: args2?.guardrails
15717
15708
  });
15718
- return {
15719
- content: [{ type: "text", text: `Created task "${task.title}" with ID: ${task.id}` }]
15720
- };
15709
+ return jsonContent({ action: "created_task", task });
15721
15710
  }
15722
15711
  case "update_task": {
15723
15712
  if (args2?.status === "in_progress") {
@@ -15759,23 +15748,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15759
15748
  if (!task) {
15760
15749
  return { content: [{ type: "text", text: "Task not found" }], isError: true };
15761
15750
  }
15762
- return {
15763
- content: [
15764
- {
15765
- type: "text",
15766
- text: `Updated task: ${JSON.stringify({ ...task, blocked: await isTaskBlocked2(task.id) }, null, 2)}`
15767
- }
15768
- ]
15769
- };
15751
+ return jsonContent({ action: "updated_task", task: { ...task, blocked: await isTaskBlocked2(task.id) } });
15770
15752
  }
15771
15753
  case "delete_task": {
15772
15754
  const success = await deleteTask2(args2?.task_id);
15773
15755
  if (!success) {
15774
15756
  return { content: [{ type: "text", text: "Task not found" }], isError: true };
15775
15757
  }
15776
- return {
15777
- content: [{ type: "text", text: `Deleted task ${args2?.task_id}` }]
15778
- };
15758
+ return jsonContent({ action: "deleted_task", task_id: args2?.task_id, deleted: true });
15779
15759
  }
15780
15760
  case "move_task_status": {
15781
15761
  if (args2?.status === "in_progress") {
@@ -15802,11 +15782,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15802
15782
  if (!task) {
15803
15783
  return { content: [{ type: "text", text: "Task not found" }], isError: true };
15804
15784
  }
15805
- return {
15806
- content: [
15807
- { type: "text", text: `Moved task "${task.title}" to ${args2?.status}` }
15808
- ]
15809
- };
15785
+ return jsonContent({ action: "moved_task_status", task });
15810
15786
  }
15811
15787
  case "add_task_comment": {
15812
15788
  const body = args2?.body?.trim();
@@ -15819,18 +15795,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15819
15795
  if (!comment) {
15820
15796
  return { content: [{ type: "text", text: "Task not found" }], isError: true };
15821
15797
  }
15822
- return {
15823
- content: [{ type: "text", text: `Added comment ${comment.id}` }]
15824
- };
15798
+ return jsonContent({ action: "added_task_comment", comment });
15825
15799
  }
15826
15800
  case "delete_task_comment": {
15827
15801
  const success = await deleteTaskComment2(args2?.task_id, args2?.comment_id);
15828
15802
  if (!success) {
15829
15803
  return { content: [{ type: "text", text: "Comment not found" }], isError: true };
15830
15804
  }
15831
- return {
15832
- content: [{ type: "text", text: `Deleted comment ${args2?.comment_id}` }]
15833
- };
15805
+ return jsonContent({ action: "deleted_task_comment", task_id: args2?.task_id, comment_id: args2?.comment_id, deleted: true });
15834
15806
  }
15835
15807
  case "list_webhooks": {
15836
15808
  const webhooks = await getWebhooks2();
@@ -15843,11 +15815,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15843
15815
  secret: args2?.secret,
15844
15816
  project_id: args2?.project_id
15845
15817
  });
15846
- return {
15847
- content: [
15848
- { type: "text", text: `Created webhook "${webhook.name}" with ID: ${webhook.id}` }
15849
- ]
15850
- };
15818
+ return jsonContent({ action: "created_webhook", webhook });
15851
15819
  }
15852
15820
  case "update_webhook": {
15853
15821
  const updates = {};
@@ -15867,18 +15835,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
15867
15835
  if (!webhook) {
15868
15836
  return { content: [{ type: "text", text: "Webhook not found" }], isError: true };
15869
15837
  }
15870
- return {
15871
- content: [{ type: "text", text: `Updated webhook: ${JSON.stringify(webhook, null, 2)}` }]
15872
- };
15838
+ return jsonContent({ action: "updated_webhook", webhook });
15873
15839
  }
15874
15840
  case "delete_webhook": {
15875
15841
  const success = await deleteWebhook2(args2?.webhook_id);
15876
15842
  if (!success) {
15877
15843
  return { content: [{ type: "text", text: "Webhook not found" }], isError: true };
15878
15844
  }
15879
- return {
15880
- content: [{ type: "text", text: `Deleted webhook ${args2?.webhook_id}` }]
15881
- };
15845
+ return jsonContent({ action: "deleted_webhook", webhook_id: args2?.webhook_id, deleted: true });
15882
15846
  }
15883
15847
  case "list_webhook_deliveries": {
15884
15848
  const limit = args2?.limit || 20;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kenzoboard",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Kenzo board for human-led AI work, powered by the Flux engine",
5
5
  "type": "module",
6
6
  "bin": {