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
|
-
|
|
69
|
-
|
|
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
|
-
|
|
92
|
-
console.log(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
console.log(" droid-mode --
|
|
96
|
-
console.log("
|
|
97
|
-
console.log("
|
|
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
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
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,
|
|
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
|
-
//
|
|
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> }.
|