signet-agent 0.1.0

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.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Framework detection utility — detects installed MCP hosts and frameworks.
3
+ */
4
+ export interface DetectedHost {
5
+ name: string;
6
+ configPath: string;
7
+ exists: boolean;
8
+ }
9
+ export declare function detectMcpHosts(): DetectedHost[];
10
+ export declare function detectInstalledHosts(): DetectedHost[];
11
+ export declare function detectPythonVersion(): string | null;
12
+ export type Framework = "openclaw" | "mcp" | "python" | "rest" | "unknown";
13
+ export declare function detectFramework(): Framework;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Framework detection utility — detects installed MCP hosts and frameworks.
3
+ */
4
+ import { existsSync } from "fs";
5
+ import { join } from "path";
6
+ import { homedir } from "os";
7
+ export function detectMcpHosts() {
8
+ const home = homedir();
9
+ const hosts = [
10
+ {
11
+ name: "Claude Code",
12
+ configPath: join(home, ".claude", "mcp_config.json"),
13
+ exists: existsSync(join(home, ".claude", "mcp_config.json")),
14
+ },
15
+ {
16
+ name: "Cursor (global)",
17
+ configPath: join(home, ".cursor", "mcp.json"),
18
+ exists: existsSync(join(home, ".cursor", "mcp.json")),
19
+ },
20
+ {
21
+ name: "Cursor (project)",
22
+ configPath: join(process.cwd(), ".cursor", "mcp.json"),
23
+ exists: existsSync(join(process.cwd(), ".cursor", "mcp.json")),
24
+ },
25
+ {
26
+ name: "Windsurf",
27
+ configPath: join(home, ".windsurf", "mcp.json"),
28
+ exists: existsSync(join(home, ".windsurf", "mcp.json")),
29
+ },
30
+ {
31
+ name: "Cline",
32
+ configPath: join(home, ".cline", "mcp_config.json"),
33
+ exists: existsSync(join(home, ".cline", "mcp_config.json")),
34
+ },
35
+ ];
36
+ return hosts;
37
+ }
38
+ export function detectInstalledHosts() {
39
+ return detectMcpHosts().filter((h) => {
40
+ if (h.exists)
41
+ return true;
42
+ const dir = h.configPath.replace(/[/\\][^/\\]+$/, "");
43
+ return existsSync(dir);
44
+ });
45
+ }
46
+ export function detectPythonVersion() {
47
+ const { execSync } = require("child_process");
48
+ try {
49
+ const version = execSync("python3 --version 2>/dev/null || python --version 2>/dev/null", {
50
+ encoding: "utf8",
51
+ }).trim();
52
+ return version.replace("Python ", "");
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ export function detectFramework() {
59
+ const hosts = detectMcpHosts();
60
+ const hasMcpHost = hosts.some((h) => h.exists);
61
+ if (existsSync(join(process.cwd(), ".openclaw")) || existsSync(join(process.cwd(), "openclaw.json"))) {
62
+ return "openclaw";
63
+ }
64
+ if (hasMcpHost) {
65
+ return "mcp";
66
+ }
67
+ const pyVersion = detectPythonVersion();
68
+ if (pyVersion) {
69
+ const [major, minor] = pyVersion.split(".").map(Number);
70
+ if (major >= 3 && minor >= 10)
71
+ return "python";
72
+ }
73
+ return "unknown";
74
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Signet CLI: init, start, stop, status, update, setup-mcp, setup-python, setup-openclaw.
4
+ * Usage: npx signet <command> [options]
5
+ */
6
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,356 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Signet CLI: init, start, stop, status, update, setup-mcp, setup-python, setup-openclaw.
4
+ * Usage: npx signet <command> [options]
5
+ */
6
+ import { createInterface } from "readline";
7
+ import { spawn, execSync } from "child_process";
8
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
9
+ import { join } from "path";
10
+ import { homedir } from "os";
11
+ import { createRequire } from "module";
12
+ const require = createRequire(import.meta.url);
13
+ const DEFAULT_DAEMON_URL = "http://127.0.0.1:8766";
14
+ const DAEMON_URL = process.env.SIGNET_DAEMON_URL ?? DEFAULT_DAEMON_URL;
15
+ const DEFAULT_DATA_DIR = join(homedir(), ".Signet");
16
+ const DATA_DIR = process.env.SIGNET_DATA_DIR ?? DEFAULT_DATA_DIR;
17
+ const PID_FILE = join(DATA_DIR, "signet.pid");
18
+ const CONFIG_FILE = join(DATA_DIR, "config.json");
19
+ const DIRECTORY_API_URL = process.env.DIRECTORY_API_URL ?? "https://api.onsignet.com";
20
+ const DIRECTORY_WEB_URL = process.env.DIRECTORY_WEB_URL ?? "https://onsignet.com";
21
+ function loadConfig() {
22
+ if (!existsSync(CONFIG_FILE))
23
+ return {};
24
+ try {
25
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf8"));
26
+ }
27
+ catch {
28
+ return {};
29
+ }
30
+ }
31
+ function saveConfig(cfg) {
32
+ if (!existsSync(DATA_DIR))
33
+ mkdirSync(DATA_DIR, { recursive: true });
34
+ writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", "utf8");
35
+ }
36
+ function question(rl, prompt) {
37
+ return new Promise((resolve) => {
38
+ rl.question(prompt, (answer) => resolve(answer.trim()));
39
+ });
40
+ }
41
+ async function checkDaemon() {
42
+ try {
43
+ const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/status`, { signal: AbortSignal.timeout(5000) });
44
+ if (!res.ok)
45
+ return { error: `Daemon returned ${res.status}` };
46
+ const data = (await res.json());
47
+ if (!data.connected)
48
+ return { error: "Daemon is not connected to the relay. Connect to the relay first." };
49
+ return { nodeId: data.nodeId, version: data.version };
50
+ }
51
+ catch (e) {
52
+ const msg = e instanceof Error ? e.message : String(e);
53
+ return { error: `Cannot reach Signet daemon at ${DAEMON_URL}: ${msg}\n\nStart the daemon first:\n cd packages/daemon && npm start\n\nOr install it:\n npm install -g @signet/daemon` };
54
+ }
55
+ }
56
+ async function registerProfile(body, authToken) {
57
+ try {
58
+ const headers = { "Content-Type": "application/json" };
59
+ if (authToken)
60
+ headers["Authorization"] = `Bearer ${authToken}`;
61
+ const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/directory/agents`, {
62
+ method: "POST",
63
+ headers,
64
+ body: JSON.stringify(body),
65
+ signal: AbortSignal.timeout(15000),
66
+ });
67
+ const data = (await res.json());
68
+ if (!res.ok) {
69
+ if (res.status === 403 && data.upgrade_url) {
70
+ return { error: `${data.error}\n\nPurchase extra slots at: ${DIRECTORY_WEB_URL}${data.upgrade_url}` };
71
+ }
72
+ return { error: data.error ?? `HTTP ${res.status}` };
73
+ }
74
+ return { profileUrl: data.profileUrl, agent: data.agent };
75
+ }
76
+ catch (e) {
77
+ const msg = e instanceof Error ? e.message : String(e);
78
+ return { error: msg };
79
+ }
80
+ }
81
+ async function cmdStatus() {
82
+ const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/status`, { signal: AbortSignal.timeout(5000) });
83
+ if (!res.ok) {
84
+ console.error("Daemon returned", res.status);
85
+ process.exit(1);
86
+ }
87
+ const data = (await res.json());
88
+ console.log("Relay:", data.connected ? "connected" : "disconnected");
89
+ console.log("NodeId:", data.nodeId ?? "—");
90
+ if (data.version)
91
+ console.log("Version:", data.version);
92
+ if (data.agentStatus != null) {
93
+ console.log("Agent:", data.agentStatus, data.agentPid != null ? `(pid ${data.agentPid})` : "");
94
+ if (data.agentRestartCount != null && data.agentRestartCount > 0) {
95
+ console.log("Agent restarts:", data.agentRestartCount);
96
+ }
97
+ }
98
+ const config = loadConfig();
99
+ if (config.profileUrl)
100
+ console.log("Profile:", config.profileUrl);
101
+ if (config.framework)
102
+ console.log("Framework:", config.framework);
103
+ }
104
+ function cmdStop() {
105
+ if (!existsSync(PID_FILE)) {
106
+ console.error("No PID file found. Is the daemon running?");
107
+ process.exit(1);
108
+ }
109
+ const pid = parseInt(readFileSync(PID_FILE, "utf8").trim(), 10);
110
+ if (!Number.isInteger(pid)) {
111
+ console.error("Invalid PID file.");
112
+ process.exit(1);
113
+ }
114
+ try {
115
+ process.kill(pid, "SIGTERM");
116
+ console.log("Sent SIGTERM to daemon (pid", pid + ").");
117
+ }
118
+ catch (e) {
119
+ console.error("Failed to stop daemon:", e instanceof Error ? e.message : e);
120
+ process.exit(1);
121
+ }
122
+ }
123
+ function cmdStart() {
124
+ let daemonMain;
125
+ try {
126
+ daemonMain = require.resolve("@signet/daemon");
127
+ }
128
+ catch {
129
+ console.error("Signet daemon not found. Install @signet/daemon or run from the Signet repo (packages/daemon).");
130
+ process.exit(1);
131
+ }
132
+ const child = spawn(process.execPath, [daemonMain], {
133
+ stdio: "inherit",
134
+ env: process.env,
135
+ });
136
+ child.on("exit", (code, signal) => {
137
+ process.exit(code ?? (signal === "SIGTERM" ? 0 : 1));
138
+ });
139
+ }
140
+ async function cmdUpdate() {
141
+ console.log("Checking for updates...\n");
142
+ let currentVersion = "unknown";
143
+ try {
144
+ const daemonStatus = await checkDaemon();
145
+ if (daemonStatus.version)
146
+ currentVersion = daemonStatus.version;
147
+ }
148
+ catch {
149
+ // Daemon may not be running
150
+ }
151
+ try {
152
+ const latestRes = await fetch(`${DIRECTORY_API_URL.replace(/\/$/, "")}/version`, { signal: AbortSignal.timeout(10000) });
153
+ if (latestRes.ok) {
154
+ const { version: latest, min_version: minVersion } = (await latestRes.json());
155
+ if (latest) {
156
+ console.log(` Current: ${currentVersion}`);
157
+ console.log(` Latest: ${latest}`);
158
+ if (minVersion)
159
+ console.log(` Minimum: ${minVersion}`);
160
+ if (currentVersion === latest) {
161
+ console.log("\nAlready up to date.");
162
+ return;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ catch {
168
+ console.log(" Could not check latest version; updating anyway.");
169
+ }
170
+ console.log("\nUpdating @signet/daemon...");
171
+ try {
172
+ execSync("npm install -g @signet/daemon@latest", { stdio: "inherit" });
173
+ console.log("\nUpdate complete. Restart the daemon to apply:");
174
+ console.log(" signet stop && signet start");
175
+ }
176
+ catch (e) {
177
+ console.error("Update failed:", e instanceof Error ? e.message : e);
178
+ console.error("\nTry manually: npm install -g @signet/daemon@latest");
179
+ process.exit(1);
180
+ }
181
+ }
182
+ async function cmdInit() {
183
+ const nodeVersion = process.version.slice(1).split(".").map(Number)[0];
184
+ if (nodeVersion < 18) {
185
+ console.error("Signet CLI requires Node.js 18 or later.");
186
+ process.exit(1);
187
+ }
188
+ // Parse --framework flag
189
+ const frameworkArg = process.argv.find((a) => a.startsWith("--framework"));
190
+ let framework;
191
+ if (frameworkArg) {
192
+ const value = frameworkArg.includes("=") ? frameworkArg.split("=")[1] : process.argv[process.argv.indexOf(frameworkArg) + 1];
193
+ if (["openclaw", "mcp", "python", "rest"].includes(value)) {
194
+ framework = value;
195
+ }
196
+ }
197
+ console.log("Signet — register your agent on the directory\n");
198
+ const daemonStatus = await checkDaemon();
199
+ if (daemonStatus.error) {
200
+ console.error(daemonStatus.error);
201
+ process.exit(1);
202
+ }
203
+ console.log(` Daemon is running`);
204
+ console.log(` Node ID: ${daemonStatus.nodeId}`);
205
+ if (daemonStatus.version)
206
+ console.log(` Version: ${daemonStatus.version}`);
207
+ console.log();
208
+ const config = loadConfig();
209
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
210
+ let authToken = config.authToken;
211
+ if (!authToken) {
212
+ const tokenInput = await question(rl, "Auth token (paste from dashboard, or press Enter to skip): ");
213
+ if (tokenInput)
214
+ authToken = tokenInput;
215
+ }
216
+ const name = await question(rl, "Agent name (e.g. My Research Assistant): ") || "My Agent";
217
+ const description = await question(rl, "Short description (optional): ");
218
+ const domainsInput = await question(rl, "Capability domains, comma-separated (e.g. research, scheduling) (optional): ");
219
+ const capabilities = domainsInput
220
+ ? domainsInput.split(",").map((d) => ({ domain: d.trim() })).filter((c) => c.domain.length > 0)
221
+ : undefined;
222
+ // Framework selection if not provided via flag
223
+ if (!framework) {
224
+ const frameworkInput = await question(rl, "Framework (openclaw / mcp / python / rest) [press Enter to auto-detect]: ");
225
+ if (frameworkInput && ["openclaw", "mcp", "python", "rest"].includes(frameworkInput)) {
226
+ framework = frameworkInput;
227
+ }
228
+ }
229
+ rl.close();
230
+ console.log("\nRegistering with the directory...");
231
+ const result = await registerProfile({ name, description: description || undefined, capabilities, framework }, authToken);
232
+ if (result.error) {
233
+ console.error("\nRegistration failed:", result.error);
234
+ process.exit(1);
235
+ }
236
+ const profilePath = result.profileUrl ?? `/agents/${result.agent?.node_id ?? "your-agent"}`;
237
+ const profileUrl = profilePath.startsWith("http") ? profilePath : `${DIRECTORY_WEB_URL}${profilePath.startsWith("/") ? "" : "/"}${profilePath}`;
238
+ saveConfig({
239
+ ...config,
240
+ nodeId: daemonStatus.nodeId,
241
+ authToken: authToken || config.authToken,
242
+ registeredAt: new Date().toISOString(),
243
+ profileUrl,
244
+ framework: framework || config.framework,
245
+ });
246
+ console.log("\n Done! Your agent is live on the directory.");
247
+ console.log(` Profile: ${profileUrl}`);
248
+ console.log(` Daemon UI: ${DAEMON_URL}`);
249
+ // Run framework-specific setup if --framework was specified
250
+ if (framework === "mcp") {
251
+ console.log("\n Running MCP setup...");
252
+ const { cmdSetupMcp } = await import("./setup-mcp.js");
253
+ await cmdSetupMcp();
254
+ }
255
+ else if (framework === "python") {
256
+ console.log("\n Running Python setup...");
257
+ const { cmdSetupPython } = await import("./setup-python.js");
258
+ await cmdSetupPython();
259
+ }
260
+ else if (framework === "openclaw") {
261
+ console.log("\n Running OpenClaw setup...");
262
+ await cmdSetupOpenclaw();
263
+ }
264
+ console.log("\n Share the profile link so others can find and connect with your agent.");
265
+ }
266
+ async function cmdSetupOpenclaw() {
267
+ console.log("Signet OpenClaw Setup\n");
268
+ try {
269
+ const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/status`, { signal: AbortSignal.timeout(5000) });
270
+ if (!res.ok) {
271
+ console.error("Daemon not responding. Start it first: npx signet start");
272
+ process.exit(1);
273
+ }
274
+ console.log(" Daemon running ✓");
275
+ }
276
+ catch {
277
+ console.error(`Cannot reach daemon at ${DAEMON_URL}. Start it first: npx signet start`);
278
+ process.exit(1);
279
+ }
280
+ console.log(" OpenClaw skill files are available at:");
281
+ console.log(` Skill JSON: ${DAEMON_URL}/adapters/openclaw/skill.json`);
282
+ console.log(` SKILL.md: ${DAEMON_URL}/adapters/openclaw/skill.md`);
283
+ console.log("\n To install the skill, add the Signet skill to your OpenClaw configuration.");
284
+ console.log(" The daemon bridge will forward messages between OpenClaw and the Signet network.");
285
+ const config = loadConfig();
286
+ saveConfig({ ...config, framework: "openclaw" });
287
+ console.log("\n Framework set to: openclaw");
288
+ }
289
+ async function main() {
290
+ const cmd = process.argv[2] ?? "init";
291
+ switch (cmd) {
292
+ case "start":
293
+ cmdStart();
294
+ return;
295
+ case "stop":
296
+ cmdStop();
297
+ return;
298
+ case "status":
299
+ await cmdStatus();
300
+ return;
301
+ case "init":
302
+ await cmdInit();
303
+ return;
304
+ case "update":
305
+ await cmdUpdate();
306
+ return;
307
+ case "setup-mcp": {
308
+ const { cmdSetupMcp } = await import("./setup-mcp.js");
309
+ await cmdSetupMcp();
310
+ return;
311
+ }
312
+ case "setup-python": {
313
+ const { cmdSetupPython } = await import("./setup-python.js");
314
+ await cmdSetupPython();
315
+ return;
316
+ }
317
+ case "setup-openclaw":
318
+ await cmdSetupOpenclaw();
319
+ return;
320
+ case "serve-mcp": {
321
+ const { execSync: exec } = await import("child_process");
322
+ try {
323
+ exec("node adapters/mcp/dist/index.js", { stdio: "inherit", cwd: join(__dirname, "..", "..") });
324
+ }
325
+ catch {
326
+ console.error("Failed to start MCP server. Build it first: cd adapters/mcp && npm run build");
327
+ }
328
+ return;
329
+ }
330
+ case "--help":
331
+ case "-h":
332
+ case "help":
333
+ console.log("Usage: signet <command>\n");
334
+ console.log("Commands:");
335
+ console.log(" init [--framework <name>] Register your agent on the directory");
336
+ console.log(" start Start the Signet daemon");
337
+ console.log(" stop Stop the Signet daemon");
338
+ console.log(" status Show daemon and agent status");
339
+ console.log(" update Update the daemon to the latest version");
340
+ console.log(" setup-mcp Configure MCP hosts (Cursor, Claude Code, etc.)");
341
+ console.log(" setup-python Set up Python client (pip install + quickstart)");
342
+ console.log(" setup-openclaw Configure OpenClaw skill adapter");
343
+ console.log(" serve-mcp Start the MCP server process");
344
+ console.log(" help Show this help message");
345
+ console.log("\nFrameworks: openclaw, mcp, python, rest");
346
+ return;
347
+ default:
348
+ console.error("Unknown command:", cmd);
349
+ console.error("Run 'signet help' for usage.");
350
+ process.exit(1);
351
+ }
352
+ }
353
+ main().catch((e) => {
354
+ console.error(e);
355
+ process.exit(1);
356
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * `signet setup-mcp` — auto-configure MCP hosts to use Signet tools.
3
+ */
4
+ export declare function cmdSetupMcp(): Promise<void>;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * `signet setup-mcp` — auto-configure MCP hosts to use Signet tools.
3
+ */
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
5
+ import { join, resolve } from "path";
6
+ import { detectMcpHosts } from "./detect-framework.js";
7
+ const DAEMON_URL = process.env.SIGNET_DAEMON_URL ?? "http://127.0.0.1:8766";
8
+ function findMcpServerPath() {
9
+ const candidates = [
10
+ join(__dirname, "..", "..", "adapters", "mcp", "dist", "index.js"),
11
+ join(process.cwd(), "adapters", "mcp", "dist", "index.js"),
12
+ ];
13
+ for (const c of candidates) {
14
+ const resolved = resolve(c);
15
+ if (existsSync(resolved))
16
+ return resolved;
17
+ }
18
+ // Fall back: try require resolution
19
+ try {
20
+ const path = require.resolve("@signet/mcp-server");
21
+ return path;
22
+ }
23
+ catch {
24
+ // Assume it's in the standard monorepo location relative to CLI
25
+ }
26
+ return resolve(join(__dirname, "..", "..", "adapters", "mcp", "dist", "index.js"));
27
+ }
28
+ function addToMcpConfig(configPath, serverPath) {
29
+ let config = {};
30
+ if (existsSync(configPath)) {
31
+ try {
32
+ config = JSON.parse(readFileSync(configPath, "utf8"));
33
+ }
34
+ catch {
35
+ config = {};
36
+ }
37
+ }
38
+ const servers = (config.mcpServers ?? {});
39
+ if (servers.signet) {
40
+ return false; // Already configured
41
+ }
42
+ servers.signet = {
43
+ command: "node",
44
+ args: [serverPath],
45
+ env: {},
46
+ };
47
+ config.mcpServers = servers;
48
+ const dir = configPath.replace(/[/\\][^/\\]+$/, "");
49
+ if (!existsSync(dir))
50
+ mkdirSync(dir, { recursive: true });
51
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
52
+ return true;
53
+ }
54
+ export async function cmdSetupMcp() {
55
+ console.log("Signet MCP Setup\n");
56
+ // 1. Check daemon
57
+ try {
58
+ const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/status`, { signal: AbortSignal.timeout(5000) });
59
+ if (!res.ok) {
60
+ console.error(`Daemon returned HTTP ${res.status}. Start the daemon first: npx signet start`);
61
+ process.exit(1);
62
+ }
63
+ const data = (await res.json());
64
+ console.log(` Daemon running: ${data.nodeId}`);
65
+ if (!data.connected) {
66
+ console.warn(" Warning: daemon is not connected to relay yet.");
67
+ }
68
+ }
69
+ catch {
70
+ console.error(`Cannot reach daemon at ${DAEMON_URL}.\nStart it first: npx signet start`);
71
+ process.exit(1);
72
+ }
73
+ // 2. Find MCP server binary
74
+ const serverPath = findMcpServerPath();
75
+ console.log(` MCP server: ${serverPath}`);
76
+ if (!existsSync(serverPath)) {
77
+ console.warn(` Warning: MCP server not found at ${serverPath}. Build it first: cd adapters/mcp && npm run build`);
78
+ }
79
+ // 3. Detect and configure MCP hosts
80
+ const hosts = detectMcpHosts();
81
+ const detected = hosts.filter((h) => {
82
+ const dir = h.configPath.replace(/[/\\][^/\\]+$/, "");
83
+ return h.exists || existsSync(dir);
84
+ });
85
+ if (detected.length === 0) {
86
+ console.log("\n No MCP-compatible hosts detected (Claude Code, Cursor, Windsurf, Cline).");
87
+ console.log(" Install one, then run `npx signet setup-mcp` again.");
88
+ console.log("\n Manual configuration:");
89
+ console.log(` Add this to your MCP config file:`);
90
+ console.log(` {`);
91
+ console.log(` "mcpServers": {`);
92
+ console.log(` "signet": {`);
93
+ console.log(` "command": "node",`);
94
+ console.log(` "args": ["${serverPath}"]`);
95
+ console.log(` }`);
96
+ console.log(` }`);
97
+ console.log(` }`);
98
+ return;
99
+ }
100
+ console.log(`\n Detected ${detected.length} MCP host(s):\n`);
101
+ let configured = 0;
102
+ for (const host of detected) {
103
+ const added = addToMcpConfig(host.configPath, serverPath);
104
+ if (added) {
105
+ console.log(` ✓ ${host.name} — configured (${host.configPath})`);
106
+ configured++;
107
+ }
108
+ else {
109
+ console.log(` · ${host.name} — already configured`);
110
+ }
111
+ }
112
+ console.log();
113
+ if (configured > 0) {
114
+ console.log(` Signet tools added to ${configured} host(s).`);
115
+ console.log(" Restart your editor to load the new tools.");
116
+ }
117
+ else {
118
+ console.log(" All detected hosts already have Signet configured.");
119
+ }
120
+ console.log('\n Try asking your AI: "Search for compute providers on Signet."');
121
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * `signet setup-python` — guide Python developers through Signet setup.
3
+ */
4
+ export declare function cmdSetupPython(): Promise<void>;
@@ -0,0 +1,90 @@
1
+ /**
2
+ * `signet setup-python` — guide Python developers through Signet setup.
3
+ */
4
+ import { existsSync, writeFileSync } from "fs";
5
+ import { join } from "path";
6
+ import { detectPythonVersion } from "./detect-framework.js";
7
+ const QUICKSTART_SCRIPT = `#!/usr/bin/env python3
8
+ """Signet quickstart — verify your connection and discover agents."""
9
+
10
+ import asyncio
11
+ from signet import SignetClient
12
+
13
+
14
+ async def main():
15
+ async with SignetClient() as client:
16
+ # Check connection
17
+ status = await client.status()
18
+ if status.connected:
19
+ print(f"✓ Connected to Signet network")
20
+ print(f" Agent ID: {status.node_id}")
21
+ print(f" Relay: {status.relay_url}")
22
+ else:
23
+ print("✗ Not connected. Is the daemon running? (npx signet start)")
24
+ return
25
+
26
+ # Search the directory
27
+ print("\\nSearching for agents on the network...")
28
+ agents = await client.discover(limit=5)
29
+
30
+ if agents:
31
+ print(f"\\nFound {len(agents)} agents:")
32
+ for a in agents:
33
+ caps = ", ".join(c.domain for c in a.capabilities) if a.capabilities else "general"
34
+ print(f" - {a.name} [{caps}]")
35
+ else:
36
+ print("\\nNo agents found yet. The network is just getting started!")
37
+
38
+ print("\\n✓ Signet Python client is working. You're ready to build.")
39
+ print(" See examples/ in the signet-agent package for common patterns.")
40
+
41
+
42
+ if __name__ == "__main__":
43
+ asyncio.run(main())
44
+ `;
45
+ export async function cmdSetupPython() {
46
+ console.log("Signet Python Setup\n");
47
+ // 1. Check Python version
48
+ const version = detectPythonVersion();
49
+ if (!version) {
50
+ console.error(" Python not found. Install Python 3.10+ first.");
51
+ process.exit(1);
52
+ }
53
+ const [major, minor] = version.split(".").map(Number);
54
+ if (major < 3 || (major === 3 && minor < 10)) {
55
+ console.error(` Python ${version} found, but Signet requires Python 3.10+.`);
56
+ console.error(" Upgrade: https://python.org/downloads/");
57
+ process.exit(1);
58
+ }
59
+ console.log(` Python ${version} ✓`);
60
+ // 2. Print install commands
61
+ console.log("\n Install the Signet Python package:\n");
62
+ console.log(" pip install signet-agent # Core client");
63
+ console.log(" pip install signet-agent[langchain] # + LangChain bindings");
64
+ console.log(" pip install signet-agent[crewai] # + CrewAI bindings");
65
+ console.log(" pip install signet-agent[all] # Everything");
66
+ // 3. Generate quickstart script
67
+ const quickstartPath = join(process.cwd(), "signet_quickstart.py");
68
+ if (!existsSync(quickstartPath)) {
69
+ writeFileSync(quickstartPath, QUICKSTART_SCRIPT, "utf8");
70
+ console.log(`\n Generated: ${quickstartPath}`);
71
+ }
72
+ else {
73
+ console.log(`\n Quickstart script already exists: ${quickstartPath}`);
74
+ }
75
+ // 4. Verify daemon
76
+ const daemonUrl = process.env.SIGNET_DAEMON_URL ?? "http://127.0.0.1:8766";
77
+ try {
78
+ const res = await fetch(`${daemonUrl.replace(/\/$/, "")}/status`, { signal: AbortSignal.timeout(5000) });
79
+ if (res.ok) {
80
+ console.log(" Daemon running ✓");
81
+ }
82
+ }
83
+ catch {
84
+ console.log("\n Note: Daemon not running. Start it first: npx signet start");
85
+ }
86
+ console.log("\n Next steps:");
87
+ console.log(" 1. pip install signet-agent");
88
+ console.log(" 2. npx signet start # Start daemon if not running");
89
+ console.log(" 3. python signet_quickstart.py # Verify connection");
90
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "signet-agent",
3
+ "version": "0.1.0",
4
+ "description": "Signet CLI — register your agent on the Signet directory",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "files": [
8
+ "dist/",
9
+ "README.md"
10
+ ],
11
+ "bin": {
12
+ "signet": "./dist/index.js"
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "start": "node dist/index.js",
17
+ "init": "node dist/index.js"
18
+ },
19
+ "dependencies": {
20
+ "@signet/daemon": "0.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^20.10.0",
24
+ "typescript": "^5.3.0"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ }
29
+ }