droid-mode 0.0.8 → 0.0.10

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
@@ -67,10 +67,14 @@ Droid Mode eliminates schema overhead by keeping tool definitions out of the LLM
67
67
 
68
68
  | Metric | Native MCP | Droid Mode |
69
69
  |--------|------------|------------|
70
- | Schema overhead (3 tools) | 329 tokens | **0 tokens** |
70
+ | Schema overhead (3 tools used) | 329 tokens | **0 tokens** |
71
+ | Full server loaded (22 tools) | 2,400 tokens | **0 tokens** |
72
+ | Typical setup (5 servers, ~100 tools) | ~11,000 tokens | **0 tokens** |
71
73
  | Total tokens (3-tool session) | 1,072 | **897** |
72
74
  | **Savings** | — | **16%** |
73
75
 
76
+ Industry reports show MCP tools consuming 20-30% of context windows before any work begins. With Droid Mode, that overhead drops to zero — tools are loaded only when called.
77
+
74
78
  Schema cost scales with tool count:
75
79
 
76
80
  | Tools Enabled | Native MCP Cost | Droid Mode Cost |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "droid-mode",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "Progressive Code-Mode MCP integration for Factory.ai Droid - access MCP tools without context bloat",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import fs from "node:fs";
3
- import { ensureDir, nowIsoCompact, safeIdentifier, writeJson, getDroidModeDataDir } from "./util.mjs";
3
+ import { ensureDir, nowIsoCompact, safeIdentifier, writeJson, getDroidModeDataDir, serverNameToDirName } from "./util.mjs";
4
4
 
5
5
  /** @param {string} s */
6
6
  function toPascalCase(s) {
@@ -89,9 +89,10 @@ function schemaToTs(schema, depth = 0) {
89
89
  */
90
90
  export function hydrateTools(opts) {
91
91
  const ts = nowIsoCompact();
92
+ const serverDir = serverNameToDirName(opts.serverName);
92
93
  const baseOut =
93
94
  opts.outDir ||
94
- path.join(getDroidModeDataDir(), "hydrated", opts.serverName, ts);
95
+ path.join(getDroidModeDataDir(), "hydrated", serverDir, ts);
95
96
 
96
97
  ensureDir(baseOut);
97
98
 
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import vm from "node:vm";
4
- import { nowIsoCompact, sha256Hex, safeIdentifier, ensureDir, getDroidModeDataDir, writeJson } from "./util.mjs";
4
+ import { nowIsoCompact, sha256Hex, safeIdentifier, ensureDir, getDroidModeDataDir, writeJson, serverNameToDirName } from "./util.mjs";
5
5
 
6
6
  /**
7
7
  * Static, best-effort disallow list for sandboxed workflows.
@@ -200,10 +200,12 @@ export async function executeWorkflow(opts) {
200
200
  export function writeRunArtifact(opts) {
201
201
  const dataDir = getDroidModeDataDir();
202
202
  const ts = nowIsoCompact();
203
- const outDir = path.join(dataDir, "runs", opts.serverName, ts);
203
+ const serverDir = serverNameToDirName(opts.serverName);
204
+ const outDir = path.join(dataDir, "runs", serverDir, ts);
204
205
  ensureDir(outDir);
205
206
  const payload = {
206
207
  serverName: opts.serverName,
208
+ serverDir,
207
209
  workflowPath: opts.workflowPath,
208
210
  tools: opts.tools,
209
211
  finishedAt: new Date().toISOString(),
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { getDroidModeDataDir, readJsonFileIfExists, writeJson } from "./util.mjs";
2
+ import { getDroidModeDataDir, readJsonFileIfExists, writeJson, serverNameToDirName } from "./util.mjs";
3
3
 
4
4
  /**
5
5
  * @param {import("./mcp_client.mjs").McpClient} client
@@ -27,7 +27,8 @@ export async function fetchAllTools(client) {
27
27
  */
28
28
  export async function getToolsCached(opts) {
29
29
  const dataDir = getDroidModeDataDir();
30
- const cacheFile = path.join(dataDir, "cache", opts.serverName, "tools.json");
30
+ const serverDir = serverNameToDirName(opts.serverName);
31
+ const cacheFile = path.join(dataDir, "cache", serverDir, "tools.json");
31
32
  const maxAgeMs = typeof opts.maxAgeMs === "number" ? opts.maxAgeMs : 30 * 60 * 1000; // 30 minutes
32
33
 
33
34
  if (!opts.refresh) {
@@ -46,6 +47,7 @@ export async function getToolsCached(opts) {
46
47
  const payload = {
47
48
  fetchedAt: new Date().toISOString(),
48
49
  serverName: opts.serverName,
50
+ serverDir,
49
51
  protocolVersion: opts.client.negotiatedProtocolVersion,
50
52
  serverInfo: opts.client.serverInfo,
51
53
  capabilities: opts.client.serverCapabilities,
@@ -17,6 +17,25 @@ export function sha256Hex(s) {
17
17
  return crypto.createHash("sha256").update(s).digest("hex");
18
18
  }
19
19
 
20
+ /**
21
+ * Convert an arbitrary server name into a safe directory segment.
22
+ * Always sanitizes and appends hash suffix for safety.
23
+ * @param {string} serverName
24
+ * @returns {string}
25
+ */
26
+ export function serverNameToDirName(serverName) {
27
+ const raw = String(serverName ?? "");
28
+ const hash = sha256Hex(raw).slice(0, 8);
29
+ const base = raw
30
+ .replace(/[/\\]/g, "_") // block path separators
31
+ .replace(/\.\./g, "_") // block traversal
32
+ .replace(/[^a-zA-Z0-9._-]/g, "_") // whitelist safe chars
33
+ .replace(/^\.+/g, "") // avoid dotfiles
34
+ .slice(0, 40);
35
+
36
+ return `${base || "server"}-${hash}`;
37
+ }
38
+
20
39
  /**
21
40
  * Convert a tool name (often snake_case or kebab-case) to a safe camelCase identifier.
22
41
  * @param {string} toolName