@virsanghavi/axis-server 1.0.0 → 1.0.2

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
@@ -1,6 +1,6 @@
1
1
  # @virsanghavi/axis-server
2
2
 
3
- The official Axis Shared Context MCP Server. This server mirrors your project structure and metadata to AI agents via the Model Context Protocol (MCP).
3
+ The official Axis Parallel Orchestration MCP Server. This server enables distributed coordination and shared memory for parallel agent workflows via the Model Context Protocol (MCP).
4
4
 
5
5
  ## Installation
6
6
 
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // ../../src/local/mcp-server.ts
2
9
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
10
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -13,7 +20,16 @@ import dotenv2 from "dotenv";
13
20
  import fs from "fs/promises";
14
21
  import path from "path";
15
22
  import { Mutex } from "async-mutex";
16
- var INSTRUCTIONS_DIR = path.resolve(process.cwd(), "agent-instructions");
23
+ var LEGACY_INSTRUCTIONS_DIR = path.resolve(process.cwd(), "agent-instructions");
24
+ var AXIS_DIR = path.resolve(process.cwd(), ".axis");
25
+ var INSTRUCTIONS_DIR = path.resolve(AXIS_DIR, "instructions");
26
+ function getEffectiveInstructionsDir() {
27
+ try {
28
+ if (__require("fs").existsSync(INSTRUCTIONS_DIR)) return INSTRUCTIONS_DIR;
29
+ } catch {
30
+ }
31
+ return LEGACY_INSTRUCTIONS_DIR;
32
+ }
17
33
  var ContextManager = class {
18
34
  mutex;
19
35
  apiUrl;
@@ -27,15 +43,22 @@ var ContextManager = class {
27
43
  if (!filename || filename.includes("\0")) {
28
44
  throw new Error("Invalid filename");
29
45
  }
30
- const resolved = path.resolve(INSTRUCTIONS_DIR, filename);
31
- if (!resolved.startsWith(INSTRUCTIONS_DIR + path.sep)) {
46
+ const resolved = path.resolve(getEffectiveInstructionsDir(), filename);
47
+ const effectiveDir = getEffectiveInstructionsDir();
48
+ if (!resolved.startsWith(effectiveDir + path.sep)) {
32
49
  throw new Error("Invalid file path");
33
50
  }
34
51
  return resolved;
35
52
  }
36
53
  async listFiles() {
37
54
  try {
38
- const files = await fs.readdir(INSTRUCTIONS_DIR);
55
+ const dir = getEffectiveInstructionsDir();
56
+ try {
57
+ await fs.access(dir);
58
+ } catch {
59
+ return [];
60
+ }
61
+ const files = await fs.readdir(dir);
39
62
  const docFiles = await this.listDocs();
40
63
  const instructionFiles = files.filter((f) => f.endsWith(".md")).map((f) => ({
41
64
  uri: `context://local/${f}`,
@@ -214,10 +237,23 @@ var NerveCenter = class {
214
237
  }
215
238
  async init() {
216
239
  await this.loadState();
240
+ await this.detectProjectName();
217
241
  if (this.useSupabase) {
218
242
  await this.ensureProjectId();
219
243
  }
220
244
  }
245
+ async detectProjectName() {
246
+ try {
247
+ const axisConfigPath = path2.join(process.cwd(), ".axis", "axis.json");
248
+ const configData = await fs2.readFile(axisConfigPath, "utf-8");
249
+ const config = JSON.parse(configData);
250
+ if (config.project) {
251
+ this.projectName = config.project;
252
+ logger.info(`Detected project name from .axis/axis.json: ${this.projectName}`);
253
+ }
254
+ } catch (e) {
255
+ }
256
+ }
221
257
  async ensureProjectId() {
222
258
  if (!this.supabase) return;
223
259
  const { data: project, error } = await this.supabase.from("projects").select("id").eq("name", this.projectName).maybeSingle();
@@ -284,6 +320,32 @@ var NerveCenter = class {
284
320
  return Object.values(this.state.locks);
285
321
  }
286
322
  }
323
+ async getNotepad() {
324
+ if (!this.useSupabase || !this.supabase || !this._projectId) {
325
+ return this.state.liveNotepad;
326
+ }
327
+ const { data, error } = await this.supabase.from("projects").select("live_notepad").eq("id", this._projectId).single();
328
+ if (error || !data) {
329
+ logger.error("Failed to fetch notepad", error);
330
+ return this.state.liveNotepad;
331
+ }
332
+ return data.live_notepad || "";
333
+ }
334
+ async appendToNotepad(text) {
335
+ if (!this.useSupabase || !this.supabase || !this._projectId) {
336
+ this.state.liveNotepad += text;
337
+ await this.saveState();
338
+ return;
339
+ }
340
+ const { error } = await this.supabase.rpc("append_to_project_notepad", {
341
+ p_project_id: this._projectId,
342
+ p_text: text
343
+ });
344
+ if (error) {
345
+ const current = await this.getNotepad();
346
+ await this.supabase.from("projects").update({ live_notepad: current + text }).eq("id", this._projectId);
347
+ }
348
+ }
287
349
  async saveState() {
288
350
  try {
289
351
  await fs2.mkdir(path2.dirname(this.stateFilePath), { recursive: true });
@@ -335,10 +397,10 @@ var NerveCenter = class {
335
397
  };
336
398
  }
337
399
  const depText = dependencies.length ? ` (Depends on: ${dependencies.join(", ")})` : "";
338
- this.state.liveNotepad += `
400
+ const logEntry = `
339
401
  - [JOB POSTED] [${priority.toUpperCase()}] ${title} (ID: ${id})${depText}`;
402
+ await this.appendToNotepad(logEntry);
340
403
  logger.info(`Job posted: ${title}`, { jobId: id, priority });
341
- await this.saveState();
342
404
  return { jobId: id, status: "POSTED" };
343
405
  });
344
406
  }
@@ -373,10 +435,9 @@ var NerveCenter = class {
373
435
  }
374
436
  if (data && data.length > 0) {
375
437
  const job2 = this.jobFromRecord(data[0]);
376
- this.state.liveNotepad += `
377
- - [JOB CLAIMED] Agent '${agentId}' picked up: ${job2.title}`;
438
+ await this.appendToNotepad(`
439
+ - [JOB CLAIMED] Agent '${agentId}' picked up: ${job2.title}`);
378
440
  logger.info(`Job claimed`, { jobId: job2.id, agentId });
379
- await this.saveState();
380
441
  return { status: "CLAIMED", job: job2 };
381
442
  }
382
443
  }
@@ -442,10 +503,9 @@ var NerveCenter = class {
442
503
  if (data.assigned_to !== agentId) return { error: "You don't own this job." };
443
504
  const { error: updateError } = await this.supabase.from("jobs").update({ status: "done", updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", jobId).eq("assigned_to", agentId);
444
505
  if (updateError) return { error: "Failed to complete job" };
445
- this.state.liveNotepad += `
446
- - [JOB DONE] ${data.title} by ${agentId}. Outcome: ${outcome}`;
506
+ await this.appendToNotepad(`
507
+ - [JOB DONE] ${data.title} by ${agentId}. Outcome: ${outcome}`);
447
508
  logger.info(`Job completed`, { jobId, agentId });
448
- await this.saveState();
449
509
  return { status: "COMPLETED" };
450
510
  }
451
511
  const job = this.state.jobs[jobId];
@@ -472,6 +532,7 @@ var NerveCenter = class {
472
532
  (j) => `- [${j.status.toUpperCase()}] ${j.title} ${j.assignedTo ? "(" + j.assignedTo + ")" : "(Open)"}
473
533
  ID: ${j.id}`
474
534
  ).join("\n");
535
+ const notepad = await this.getNotepad();
475
536
  return `# Active Session Context
476
537
 
477
538
  ## Job Board (Active Orchestration)
@@ -481,7 +542,7 @@ ${jobSummary || "No active jobs."}
481
542
  ${lockSummary || "No active locks."}
482
543
 
483
544
  ## Live Notepad
484
- ${this.state.liveNotepad}`;
545
+ ${notepad}`;
485
546
  }
486
547
  // --- Decision & Orchestration ---
487
548
  async proposeFileAccess(agentId, filePath, intent, userPrompt) {
@@ -515,35 +576,40 @@ ${this.state.liveNotepad}`;
515
576
  logger.error("Lock upsert failed", error);
516
577
  return { status: "ERROR", message: "Database lock failed." };
517
578
  }
518
- this.state.liveNotepad += `
579
+ await this.appendToNotepad(`
519
580
 
520
581
  ### [${agentId}] Locked '${filePath}'
521
582
  **Intent:** ${intent}
522
- **Prompt:** "${userPrompt}"`;
523
- await this.saveState();
583
+ **Prompt:** "${userPrompt}"`);
524
584
  return { status: "GRANTED", message: `Access granted for ${filePath}` };
525
585
  });
526
586
  }
527
587
  async updateSharedContext(text, agentId) {
528
588
  return await this.mutex.runExclusive(async () => {
529
- this.state.liveNotepad += `
530
- - [${agentId}] ${text}`;
531
- await this.saveState();
589
+ await this.appendToNotepad(`
590
+ - [${agentId}] ${text}`);
532
591
  return "Notepad updated.";
533
592
  });
534
593
  }
535
594
  async finalizeSession() {
536
595
  return await this.mutex.runExclusive(async () => {
537
- const content = this.state.liveNotepad;
596
+ const content = await this.getNotepad();
538
597
  const filename = `session-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.md`;
539
598
  const historyPath = path2.join(process.cwd(), "history", filename);
540
599
  await fs2.writeFile(historyPath, content);
541
- this.state.liveNotepad = "Session Start: " + (/* @__PURE__ */ new Date()).toISOString() + "\n";
542
- this.state.locks = {};
543
600
  if (this.useSupabase && this.supabase && this._projectId) {
601
+ await this.supabase.from("sessions").insert({
602
+ project_id: this._projectId,
603
+ title: `Session ${(/* @__PURE__ */ new Date()).toLocaleDateString()}`,
604
+ summary: content.substring(0, 500) + "...",
605
+ metadata: { full_content: content }
606
+ });
607
+ await this.supabase.from("projects").update({ live_notepad: "Session Start: " + (/* @__PURE__ */ new Date()).toISOString() + "\n" }).eq("id", this._projectId);
544
608
  await this.supabase.from("jobs").delete().eq("project_id", this._projectId).in("status", ["done", "cancelled"]);
545
609
  await this.supabase.from("locks").delete().eq("project_id", this._projectId);
546
610
  } else {
611
+ this.state.liveNotepad = "Session Start: " + (/* @__PURE__ */ new Date()).toISOString() + "\n";
612
+ this.state.locks = {};
547
613
  this.state.jobs = Object.fromEntries(
548
614
  Object.entries(this.state.jobs).filter(([_, j]) => j.status !== "done" && j.status !== "cancelled")
549
615
  );
@@ -682,8 +748,8 @@ if (!process.env.NEXT_PUBLIC_SUPABASE_URL || !process.env.SUPABASE_SERVICE_ROLE_
682
748
  process.exit(1);
683
749
  }
684
750
  var manager = new ContextManager(
685
- process.env.SHARED_CONTEXT_API_URL,
686
- process.env.SHARED_CONTEXT_API_SECRET
751
+ process.env.SHARED_CONTEXT_API_URL || "https://aicontext.vercel.app/api/v1",
752
+ process.env.AXIS_API_KEY || process.env.SHARED_CONTEXT_API_SECRET
687
753
  );
688
754
  var nerveCenter = new NerveCenter(manager, {
689
755
  supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
@@ -697,21 +763,34 @@ var ragEngine = new RagEngine(
697
763
  // Project ID is loaded async by NerveCenter... tricky dependency.
698
764
  // We'll let NerveCenter expose it or pass it later.
699
765
  );
700
- var REQUIRED_DIRS = ["agent-instructions", "history"];
701
766
  async function ensureFileSystem() {
702
767
  const fs3 = await import("fs/promises");
703
768
  const path3 = await import("path");
704
- for (const d of REQUIRED_DIRS) {
705
- const dirPath = path3.join(process.cwd(), d);
706
- try {
707
- await fs3.access(dirPath);
708
- } catch {
709
- logger.info("Creating required directory", { dir: d });
710
- await fs3.mkdir(dirPath, { recursive: true });
711
- if (d === "agent-instructions") {
712
- await fs3.writeFile(path3.join(dirPath, "context.md"), "# Project Context\n\n");
713
- await fs3.writeFile(path3.join(dirPath, "conventions.md"), "# Coding Conventions\n\n");
714
- await fs3.writeFile(path3.join(dirPath, "activity.md"), "# Activity Log\n\n");
769
+ const fsSync = await import("fs");
770
+ const cwd = process.cwd();
771
+ const historyDir = path3.join(cwd, "history");
772
+ await fs3.mkdir(historyDir, { recursive: true }).catch(() => {
773
+ });
774
+ const axisDir = path3.join(cwd, ".axis");
775
+ const axisInstructions = path3.join(axisDir, "instructions");
776
+ const legacyInstructions = path3.join(cwd, "agent-instructions");
777
+ if (fsSync.existsSync(legacyInstructions) && !fsSync.existsSync(axisDir)) {
778
+ logger.info("Using legacy agent-instructions directory");
779
+ } else {
780
+ await fs3.mkdir(axisInstructions, { recursive: true }).catch(() => {
781
+ });
782
+ const defaults = [
783
+ ["context.md", "# Project Context\n\n"],
784
+ ["conventions.md", "# Coding Conventions\n\n"],
785
+ ["activity.md", "# Activity Log\n\n"]
786
+ ];
787
+ for (const [file, content] of defaults) {
788
+ const p = path3.join(axisInstructions, file);
789
+ try {
790
+ await fs3.access(p);
791
+ } catch {
792
+ await fs3.writeFile(p, content);
793
+ logger.info(`Created default context file: ${file}`);
715
794
  }
716
795
  }
717
796
  }
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@virsanghavi/axis-server",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Axis MCP Server CLI",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
- "axis-server": "./dist/cli.js"
7
+ "axis-server": "dist/cli.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node dist/cli.js",