droid-mode 0.0.10 → 0.0.12

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/dist/cli.js CHANGED
@@ -5,8 +5,58 @@ import { defineCommand, runMain } from "citty";
5
5
  import { copy, pathExists, ensureDir } from "fs-extra";
6
6
  import { resolve, join, dirname } from "path";
7
7
  import { fileURLToPath } from "url";
8
- import { stat } from "fs/promises";
8
+ import { stat, readFile } from "fs/promises";
9
9
  var __dirname = dirname(fileURLToPath(import.meta.url));
10
+ var DROID_MODE_ASCII = `
11
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
12
+ \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
13
+ \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557
14
+ \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D
15
+ \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
16
+ \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D`;
17
+ var DIVIDER = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
18
+ var DIVIDER_THIN = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500";
19
+ async function getPackageVersion() {
20
+ try {
21
+ const pkgPath = resolve(__dirname, "..", "package.json");
22
+ const content = await readFile(pkgPath, "utf-8");
23
+ const pkg = JSON.parse(content);
24
+ return pkg.version || "0.0.0";
25
+ } catch {
26
+ return "0.0.0";
27
+ }
28
+ }
29
+ function printSuccessScreen(copied, skipped, version) {
30
+ console.log(`
31
+ ${DIVIDER}
32
+ `);
33
+ console.log(DROID_MODE_ASCII);
34
+ console.log(`${"".padStart(72)}v${version}
35
+ `);
36
+ console.log(" Progressive MCP for AI Agents\n");
37
+ console.log(DIVIDER);
38
+ const status = skipped > 0 ? ` \u2713 Updated successfully ${copied} created \xB7 ${skipped} skipped (existing)` : ` \u2713 Initialized successfully ${copied} files created`;
39
+ console.log(`
40
+ ${status}
41
+ `);
42
+ console.log(DIVIDER_THIN);
43
+ console.log("\n QUICK START\n");
44
+ console.log(" 1. Discover MCP servers dm servers");
45
+ console.log(" 2. Index tools from server dm index --server <name>");
46
+ console.log(' 3. Search for tools dm search "query" --server <name>');
47
+ console.log(" 4. Run a workflow dm run --server <name> --tools a,b --workflow file.js\n");
48
+ console.log(DIVIDER_THIN);
49
+ console.log("\n \u26A1 PRO TIP\n");
50
+ console.log(' Set "disabled": true for MCP servers in your mcp.json config.');
51
+ console.log(" Droid Mode accesses them directly \u2014 faster startup, zero token bloat.\n");
52
+ console.log(DIVIDER_THIN);
53
+ console.log("\n RESOURCES\n");
54
+ console.log(" \u{1F4D6} Docs https://droidmode.dev");
55
+ console.log(" \u{1F4E6} NPM https://npmjs.com/package/droid-mode");
56
+ console.log(" \u{1F419} GitHub https://github.com/Gitmaxd/droid-mode\n");
57
+ console.log(DIVIDER);
58
+ console.log("");
59
+ }
10
60
  var init = defineCommand({
11
61
  meta: {
12
62
  name: "init",
@@ -65,17 +115,8 @@ var init = defineCommand({
65
115
  console.error("\u274C Error during scaffolding:", error);
66
116
  process.exit(1);
67
117
  }
68
- console.log("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
69
- console.log("\u2705 Droid Mode initialized successfully!");
70
- console.log(` \u{1F4C1} Files created: ${copied}`);
71
- console.log(` \u23ED\uFE0F Files skipped: ${skipped}`);
72
- console.log("\n\u{1F4D6} Quick Start:");
73
- console.log(" dm servers # Discover MCP servers");
74
- console.log(" dm index --server <name> # List tools");
75
- console.log(' dm search "query" --server <name>');
76
- console.log(" dm run --server <name> --tools <a,b> --workflow file.js");
77
- console.log('\n\u{1F4A1} Tip: Keep MCP servers "disabled: true" in mcp.json');
78
- console.log(" Droid Mode accesses them directly without context bloat!\n");
118
+ const version = await getPackageVersion();
119
+ printSuccessScreen(copied, skipped, version);
79
120
  }
80
121
  });
81
122
  var main = defineCommand({
@@ -88,16 +129,13 @@ var main = defineCommand({
88
129
  init
89
130
  },
90
131
  async run() {
91
- console.log("\n\u{1F916} Droid Mode - Progressive MCP for AI Agents\n");
92
- console.log("Usage:");
93
- console.log(" droid-mode init [options] Initialize Droid Mode in your project");
94
- console.log(" droid-mode --help Show help");
95
- console.log(" droid-mode --version Show version\n");
96
- console.log("Quick start:");
97
- console.log(" npx droid-mode init\n");
98
- console.log("What is Droid Mode?");
99
- console.log(" Access MCP tools without loading them into context.");
100
- console.log(" Progressive discovery: servers \u2192 tools \u2192 schemas \u2192 execute\n");
132
+ const version = await getPackageVersion();
133
+ console.log(`
134
+ \u{1F916} Droid Mode v${version}
135
+ `);
136
+ console.log("Usage: npx droid-mode init [--path <dir>]\n");
137
+ console.log("Scaffold MCP integration for AI agents.");
138
+ console.log("Learn more: https://droidmode.dev\n");
101
139
  }
102
140
  });
103
141
  runMain(main);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "droid-mode",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
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, serverNameToDirName } from "./util.mjs";
3
+ import { ensureDir, nowIsoCompact, safeIdentifier, uniqueSafeToolMap, writeJson, getDroidModeDataDir, serverNameToDirName } from "./util.mjs";
4
4
 
5
5
  /** @param {string} s */
6
6
  function toPascalCase(s) {
@@ -104,11 +104,9 @@ export function hydrateTools(opts) {
104
104
  if (t) selected.push(t);
105
105
  }
106
106
 
107
- const toolmap = {};
108
- for (const t of selected) {
109
- const safe = safeIdentifier(t?.name || "tool");
110
- toolmap[safe] = t?.name || safe;
111
- }
107
+ // Build collision-free toolmap using shared function
108
+ const selectedNames = selected.map(t => t?.name || "tool");
109
+ const toolmap = uniqueSafeToolMap(selectedNames);
112
110
 
113
111
  writeJson(path.join(baseOut, "tools.json"), selected);
114
112
  writeJson(path.join(baseOut, "toolmap.json"), toolmap);
@@ -117,15 +115,22 @@ export function hydrateTools(opts) {
117
115
  const toolmapModule = `// Auto-generated by droid-mode.\nexport default ${JSON.stringify(toolmap, null, 2)};\n`;
118
116
  fs.writeFileSync(path.join(baseOut, "toolmap.mjs"), toolmapModule, "utf-8");
119
117
 
120
- // types.d.ts
118
+ // types.d.ts - use toolmap keys (already collision-free)
121
119
  const typeLines = [
122
120
  `// Auto-generated by droid-mode (best-effort).`,
123
121
  `// This file exists to improve IDE autocomplete; it is NOT a contract.`,
124
122
  ``,
125
123
  ];
126
124
 
125
+ // Build reverse map: toolName -> safeName for lookup
126
+ const nameToSafe = Object.create(null);
127
+ for (const [safe, name] of Object.entries(toolmap)) {
128
+ nameToSafe[name] = safe;
129
+ }
130
+
127
131
  for (const t of selected) {
128
- const safe = safeIdentifier(t?.name || "tool");
132
+ const toolName = t?.name || "tool";
133
+ const safe = nameToSafe[toolName] || safeIdentifier(toolName);
129
134
  const pascal = toPascalCase(safe);
130
135
  const inSchema = t?.inputSchema || t?.parameters || null;
131
136
  const outSchema = t?.outputSchema || null;
@@ -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, serverNameToDirName } from "./util.mjs";
4
+ import { nowIsoCompact, sha256Hex, uniqueSafeToolMap, ensureDir, getDroidModeDataDir, writeJson, serverNameToDirName } from "./util.mjs";
5
5
 
6
6
  /**
7
7
  * Static, best-effort disallow list for sandboxed workflows.
@@ -100,13 +100,10 @@ export function createToolApi(opts) {
100
100
  throw lastError;
101
101
  };
102
102
 
103
- // safe-name wrapper map
103
+ // Build collision-free toolmap using shared function
104
+ const toolmap = uniqueSafeToolMap(opts.toolNames ?? []);
104
105
  const t = {};
105
- const toolmap = {};
106
-
107
- for (const toolName of opts.toolNames) {
108
- const safe = safeIdentifier(toolName);
109
- toolmap[safe] = toolName;
106
+ for (const [safe, toolName] of Object.entries(toolmap)) {
110
107
  t[safe] = (args) => call(toolName, args);
111
108
  }
112
109
 
@@ -56,6 +56,55 @@ export function safeIdentifier(toolName) {
56
56
  return "_" + camel;
57
57
  }
58
58
 
59
+ /**
60
+ * Build a collision-free safeName -> toolName map.
61
+ * All colliding tools get a hash suffix (not just the "losers").
62
+ * Guarantees deterministic output regardless of input order.
63
+ *
64
+ * @param {string[]} toolNames
65
+ * @returns {Record<string, string>} safeName -> originalToolName
66
+ */
67
+ export function uniqueSafeToolMap(toolNames) {
68
+ const map = Object.create(null);
69
+
70
+ // Phase 1: Group by base identifier, caching base names
71
+ // Handle undefined/null names with fallback (matches prior behavior)
72
+ const groups = new Map(); // base -> [{name, base}, ...]
73
+ for (const rawName of toolNames) {
74
+ // Matches exact prior semantics: t?.name || "tool"
75
+ // Only null/undefined/empty-string fall back; whitespace-only IS kept
76
+ const toolName = rawName || "tool";
77
+ const base = safeIdentifier(toolName);
78
+ if (!groups.has(base)) groups.set(base, []);
79
+ groups.get(base).push({ name: toolName, base });
80
+ }
81
+
82
+ // Phase 2: Assign safe names (sort colliding groups for determinism)
83
+ for (const [base, tools] of groups) {
84
+ if (tools.length === 1) {
85
+ // No collision: use base name directly
86
+ map[base] = tools[0].name;
87
+ } else {
88
+ // Collision: sort by string comparison for determinism
89
+ // (localeCompare varies by OS/locale; relational operators are consistent)
90
+ tools.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
91
+ for (const { name: toolName, base: cachedBase } of tools) {
92
+ const suffix = sha256Hex(toolName).slice(0, 8); // 8 chars = 4B combinations
93
+ let safe = `${cachedBase}_${suffix}`;
94
+ // Guarantee uniqueness even on hash collision (extremely rare)
95
+ let counter = 0;
96
+ while (map[safe] !== undefined) {
97
+ counter++;
98
+ safe = `${cachedBase}_${suffix}_${counter}`;
99
+ }
100
+ map[safe] = toolName;
101
+ }
102
+ }
103
+ }
104
+
105
+ return map;
106
+ }
107
+
59
108
  /**
60
109
  * Lightweight CLI args parser (supports: --k=v, --k v, flags, positional args).
61
110
  * Returns { _: string[], flags: Record<string, string|boolean> }.