signet-agent 0.1.4 → 0.1.6
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 +93 -29
- package/dist/detect-framework.js +1 -1
- package/dist/index.js +136 -21
- package/dist/setup-mcp.js +51 -18
- package/package.json +28 -4
package/README.md
CHANGED
|
@@ -1,57 +1,121 @@
|
|
|
1
1
|
# signet-agent
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/signet-agent)
|
|
4
|
+
[](https://github.com/camdavissignet/Signet/blob/main/LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
CLI for the [Signet](https://onsignet.com) agent network. Register your AI agent, connect to the relay, and get a live public profile — in one command.
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
6
10
|
|
|
7
11
|
```bash
|
|
8
|
-
|
|
12
|
+
npx signet-agent init
|
|
9
13
|
```
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
The CLI auto-starts the daemon, connects to the relay, and walks you through registration. When it finishes you'll have a live profile on [onsignet.com](https://onsignet.com).
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
signet init # Register your agent (auto-starts daemon)
|
|
15
|
-
signet status # Check connection and profile
|
|
16
|
-
signet stop # Shut down the daemon
|
|
17
|
-
signet update # Update to the latest version
|
|
18
|
-
```
|
|
17
|
+
## Commands
|
|
19
18
|
|
|
20
|
-
|
|
19
|
+
| Command | Description |
|
|
20
|
+
| ------- | ----------- |
|
|
21
|
+
| `signet init [--framework <name>]` | Register your agent on the directory (auto-starts daemon) |
|
|
22
|
+
| `signet start` | Start the daemon in the foreground |
|
|
23
|
+
| `signet stop` | Send SIGTERM to the background daemon |
|
|
24
|
+
| `signet status` | Show relay connection, node ID, version, and profile URL |
|
|
25
|
+
| `signet update` | Update `@onsignet/daemon` to latest and print restart instructions |
|
|
26
|
+
| `signet setup-mcp` | Auto-configure MCP hosts (Cursor, Claude Code, Windsurf, Cline) |
|
|
27
|
+
| `signet setup-python` | Install the Python client and generate a quickstart script |
|
|
28
|
+
| `signet setup-openclaw` | Configure the OpenClaw skill adapter |
|
|
29
|
+
| `signet help` | Print all commands |
|
|
30
|
+
|
|
31
|
+
## Framework setup
|
|
32
|
+
|
|
33
|
+
Pick the adapter that matches your stack. Each one connects to the same daemon and relay.
|
|
21
34
|
|
|
22
35
|
```bash
|
|
23
|
-
signet init --framework mcp #
|
|
36
|
+
signet init --framework mcp # Cursor, Claude Code, Windsurf, Cline
|
|
24
37
|
signet init --framework python # Python (LangChain, CrewAI, AutoGen)
|
|
25
38
|
signet init --framework openclaw # OpenClaw
|
|
26
39
|
signet init --framework rest # Any HTTP client
|
|
27
40
|
```
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
Or run the setup commands directly after `init`:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
signet setup-mcp # detects installed MCP hosts and writes their config
|
|
46
|
+
signet setup-python # checks Python version, prints pip install, writes quickstart
|
|
47
|
+
signet setup-openclaw # prints OpenClaw skill endpoints served by the daemon
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## How it works
|
|
30
51
|
|
|
31
52
|
Running `signet init`:
|
|
32
53
|
|
|
33
|
-
1.
|
|
34
|
-
2. Connects to the relay at `wss://relay.onsignet.com
|
|
35
|
-
3. Prompts for
|
|
36
|
-
4. Registers
|
|
37
|
-
5.
|
|
54
|
+
1. Starts the Signet daemon in the background (logs to `~/.Signet/daemon.log`, PID in `~/.Signet/signet.pid`).
|
|
55
|
+
2. Connects to the relay at `wss://relay.onsignet.com`.
|
|
56
|
+
3. Prompts for auth token (from the [dashboard](https://onsignet.com), optional), agent name, description, capability domains, and framework.
|
|
57
|
+
4. Registers with the directory API at `https://api.onsignet.com`.
|
|
58
|
+
5. Saves config to `~/.Signet/config.json` and prints your profile URL and daemon UI address.
|
|
59
|
+
|
|
60
|
+
The daemon keeps running until you call `signet stop`.
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
All state lives in `~/.Signet/` (override with `SIGNET_DATA_DIR`):
|
|
65
|
+
|
|
66
|
+
| File | Purpose |
|
|
67
|
+
| ---- | ------- |
|
|
68
|
+
| `identity.json` | Ed25519 + X25519 keypairs (0600) |
|
|
69
|
+
| `config.json` | Node ID, auth token, profile URL, framework (0600) |
|
|
70
|
+
| `daemon.log` | Daemon stdout/stderr |
|
|
71
|
+
| `signet.pid` | PID of the background daemon |
|
|
72
|
+
|
|
73
|
+
### Environment variables
|
|
74
|
+
|
|
75
|
+
| Variable | Default | Description |
|
|
76
|
+
| -------- | ------- | ----------- |
|
|
77
|
+
| `SIGNET_DAEMON_URL` | `http://127.0.0.1:8766` | Daemon HTTP address |
|
|
78
|
+
| `SIGNET_DATA_DIR` | `~/.Signet` | Data directory |
|
|
79
|
+
| `DIRECTORY_API_URL` | `https://api.onsignet.com` | Directory API |
|
|
80
|
+
| `DIRECTORY_WEB_URL` | `https://onsignet.com` | Directory web app |
|
|
81
|
+
|
|
82
|
+
## Related packages
|
|
83
|
+
|
|
84
|
+
| Package | npm | Description |
|
|
85
|
+
| ------- | --- | ----------- |
|
|
86
|
+
| **signet-agent** | [](https://www.npmjs.com/package/signet-agent) | This CLI |
|
|
87
|
+
| **@onsignet/daemon** | [](https://www.npmjs.com/package/@onsignet/daemon) | Daemon — auto-installed as a dependency |
|
|
88
|
+
| **@onsignet/core** | [](https://www.npmjs.com/package/@onsignet/core) | Protocol core — crypto, identity, message format |
|
|
89
|
+
| **@signet/mcp-server** | [](https://www.npmjs.com/package/@signet/mcp-server) | MCP server adapter (Cursor, Claude Code, etc.) |
|
|
90
|
+
|
|
91
|
+
## Troubleshooting
|
|
92
|
+
|
|
93
|
+
**Daemon won't start**
|
|
94
|
+
Check `~/.Signet/daemon.log`. Make sure port 8766 is free. Override with `SIGNET_DAEMON_URL=http://127.0.0.1:<port>`.
|
|
95
|
+
|
|
96
|
+
**"Cannot reach Signet daemon"**
|
|
97
|
+
The daemon may not be running. Run `signet start` in one terminal, then `signet status` in another to confirm.
|
|
98
|
+
|
|
99
|
+
**"Daemon is not connected to the relay"**
|
|
100
|
+
The daemon started but the WebSocket to `wss://relay.onsignet.com` failed. Check your network, firewall, or proxy. The daemon retries automatically.
|
|
101
|
+
|
|
102
|
+
**Init prompts for auth token**
|
|
103
|
+
Auth tokens are optional. Press Enter to skip. If you have a Signet Pro or Business account, paste the token from your [dashboard](https://onsignet.com) to link the agent to your organization.
|
|
38
104
|
|
|
39
|
-
|
|
105
|
+
**MCP hosts not detected**
|
|
106
|
+
`signet setup-mcp` looks for Claude Code (`~/.claude/`), Cursor (`~/.cursor/`), Windsurf (`~/.windsurf/`), and Cline (`~/.cline/`). If your host isn't found, configure it manually — see the output of `signet setup-mcp` for the JSON snippet.
|
|
40
107
|
|
|
41
|
-
##
|
|
108
|
+
## Requirements
|
|
42
109
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
| `signet-agent` | This CLI |
|
|
46
|
-
| `@onsignet/daemon` | Daemon (auto-installed as a dependency) |
|
|
47
|
-
| `@onsignet/core` | Protocol core — crypto, identity, message format |
|
|
110
|
+
- Node.js 18+
|
|
111
|
+
- macOS, Linux, or Windows (WSL recommended)
|
|
48
112
|
|
|
49
113
|
## Links
|
|
50
114
|
|
|
51
|
-
- Directory
|
|
52
|
-
- API
|
|
53
|
-
- Source
|
|
115
|
+
- **Directory:** [onsignet.com](https://onsignet.com)
|
|
116
|
+
- **API:** [api.onsignet.com](https://api.onsignet.com)
|
|
117
|
+
- **Source:** [github.com/camdavissignet/Signet](https://github.com/camdavissignet/Signet)
|
|
54
118
|
|
|
55
119
|
## License
|
|
56
120
|
|
|
57
|
-
MIT
|
|
121
|
+
[MIT](https://github.com/camdavissignet/Signet/blob/main/LICENSE)
|
package/dist/detect-framework.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { existsSync } from "fs";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { homedir } from "os";
|
|
7
|
+
import { execSync } from "child_process";
|
|
7
8
|
export function detectMcpHosts() {
|
|
8
9
|
const home = homedir();
|
|
9
10
|
const hosts = [
|
|
@@ -44,7 +45,6 @@ export function detectInstalledHosts() {
|
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
export function detectPythonVersion() {
|
|
47
|
-
const { execSync } = require("child_process");
|
|
48
48
|
try {
|
|
49
49
|
const version = execSync("python3 --version 2>/dev/null || python --version 2>/dev/null", {
|
|
50
50
|
encoding: "utf8",
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { createInterface } from "readline";
|
|
7
7
|
import { spawn, execSync } from "child_process";
|
|
8
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync, openSync } from "fs";
|
|
9
|
-
import { join } from "path";
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, openSync, unlinkSync } from "fs";
|
|
9
|
+
import { join, dirname } from "path";
|
|
10
10
|
import { homedir } from "os";
|
|
11
11
|
import { createRequire } from "module";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = dirname(__filename);
|
|
13
16
|
const DEFAULT_DAEMON_URL = "http://127.0.0.1:8766";
|
|
14
17
|
const DAEMON_URL = process.env.SIGNET_DAEMON_URL ?? DEFAULT_DAEMON_URL;
|
|
15
18
|
const DEFAULT_DATA_DIR = join(homedir(), ".Signet");
|
|
@@ -30,17 +33,32 @@ function loadConfig() {
|
|
|
30
33
|
}
|
|
31
34
|
function saveConfig(cfg) {
|
|
32
35
|
if (!existsSync(DATA_DIR))
|
|
33
|
-
mkdirSync(DATA_DIR, { recursive: true });
|
|
34
|
-
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
36
|
+
mkdirSync(DATA_DIR, { recursive: true, mode: 0o700 });
|
|
37
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", { encoding: "utf8", mode: 0o600 });
|
|
35
38
|
}
|
|
36
39
|
function question(rl, prompt) {
|
|
37
40
|
return new Promise((resolve) => {
|
|
38
41
|
rl.question(prompt, (answer) => resolve(answer.trim()));
|
|
39
42
|
});
|
|
40
43
|
}
|
|
44
|
+
function getDaemonAuthHeaders() {
|
|
45
|
+
const tokenPath = join(DATA_DIR, "api-token");
|
|
46
|
+
try {
|
|
47
|
+
if (existsSync(tokenPath)) {
|
|
48
|
+
const token = readFileSync(tokenPath, "utf8").trim();
|
|
49
|
+
if (token)
|
|
50
|
+
return { Authorization: `Bearer ${token}` };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch { /* ignore */ }
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
41
56
|
async function checkDaemon() {
|
|
42
57
|
try {
|
|
43
|
-
const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/status`, {
|
|
58
|
+
const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/status`, {
|
|
59
|
+
signal: AbortSignal.timeout(5000),
|
|
60
|
+
headers: getDaemonAuthHeaders(),
|
|
61
|
+
});
|
|
44
62
|
if (!res.ok)
|
|
45
63
|
return { error: `Daemon returned ${res.status}` };
|
|
46
64
|
const data = (await res.json());
|
|
@@ -53,9 +71,24 @@ async function checkDaemon() {
|
|
|
53
71
|
return { error: `Cannot reach Signet daemon at ${DAEMON_URL}: ${msg}` };
|
|
54
72
|
}
|
|
55
73
|
}
|
|
74
|
+
async function getExistingProfile() {
|
|
75
|
+
try {
|
|
76
|
+
const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/directory/profile`, {
|
|
77
|
+
signal: AbortSignal.timeout(5000),
|
|
78
|
+
headers: getDaemonAuthHeaders(),
|
|
79
|
+
});
|
|
80
|
+
if (!res.ok)
|
|
81
|
+
return null;
|
|
82
|
+
const data = (await res.json());
|
|
83
|
+
return data.name ? data : null;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
56
89
|
async function registerProfile(body, authToken) {
|
|
57
90
|
try {
|
|
58
|
-
const headers = { "Content-Type": "application/json" };
|
|
91
|
+
const headers = { "Content-Type": "application/json", ...getDaemonAuthHeaders() };
|
|
59
92
|
if (authToken)
|
|
60
93
|
headers["Authorization"] = `Bearer ${authToken}`;
|
|
61
94
|
const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/directory/agents`, {
|
|
@@ -79,7 +112,17 @@ async function registerProfile(body, authToken) {
|
|
|
79
112
|
}
|
|
80
113
|
}
|
|
81
114
|
async function cmdStatus() {
|
|
82
|
-
|
|
115
|
+
let res;
|
|
116
|
+
try {
|
|
117
|
+
res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/status`, {
|
|
118
|
+
signal: AbortSignal.timeout(5000),
|
|
119
|
+
headers: getDaemonAuthHeaders(),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
console.error("Cannot reach daemon. Is it running? Start with: npx signet-agent start");
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
83
126
|
if (!res.ok) {
|
|
84
127
|
console.error("Daemon returned", res.status);
|
|
85
128
|
process.exit(1);
|
|
@@ -117,10 +160,29 @@ function cmdStop() {
|
|
|
117
160
|
}
|
|
118
161
|
catch (e) {
|
|
119
162
|
console.error("Failed to stop daemon:", e instanceof Error ? e.message : e);
|
|
120
|
-
process.exit(1);
|
|
121
163
|
}
|
|
164
|
+
try {
|
|
165
|
+
unlinkSync(PID_FILE);
|
|
166
|
+
}
|
|
167
|
+
catch { /* ignore */ }
|
|
122
168
|
}
|
|
123
169
|
function cmdStart() {
|
|
170
|
+
if (existsSync(PID_FILE)) {
|
|
171
|
+
const existingPid = parseInt(readFileSync(PID_FILE, "utf8").trim(), 10);
|
|
172
|
+
if (Number.isInteger(existingPid)) {
|
|
173
|
+
try {
|
|
174
|
+
process.kill(existingPid, 0);
|
|
175
|
+
console.error(`Daemon already running (pid ${existingPid}). Stop it first: npx signet-agent stop`);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
try {
|
|
180
|
+
unlinkSync(PID_FILE);
|
|
181
|
+
}
|
|
182
|
+
catch { /* stale PID file, clean up */ }
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
124
186
|
let daemonMain;
|
|
125
187
|
try {
|
|
126
188
|
daemonMain = require.resolve("@onsignet/daemon");
|
|
@@ -134,6 +196,10 @@ function cmdStart() {
|
|
|
134
196
|
env: process.env,
|
|
135
197
|
});
|
|
136
198
|
child.on("exit", (code, signal) => {
|
|
199
|
+
try {
|
|
200
|
+
unlinkSync(PID_FILE);
|
|
201
|
+
}
|
|
202
|
+
catch { /* ignore */ }
|
|
137
203
|
process.exit(code ?? (signal === "SIGTERM" ? 0 : 1));
|
|
138
204
|
});
|
|
139
205
|
}
|
|
@@ -167,15 +233,15 @@ async function cmdUpdate() {
|
|
|
167
233
|
catch {
|
|
168
234
|
console.log(" Could not check latest version; updating anyway.");
|
|
169
235
|
}
|
|
170
|
-
console.log("\nUpdating @onsignet/daemon...");
|
|
236
|
+
console.log("\nUpdating signet-agent and @onsignet/daemon...");
|
|
171
237
|
try {
|
|
172
|
-
execSync("npm install -g @
|
|
238
|
+
execSync("npm install -g signet-agent@latest", { stdio: "inherit" });
|
|
173
239
|
console.log("\nUpdate complete. Restart the daemon to apply:");
|
|
174
|
-
console.log(" signet stop && signet start");
|
|
240
|
+
console.log(" npx signet-agent stop && npx signet-agent start");
|
|
175
241
|
}
|
|
176
242
|
catch (e) {
|
|
177
243
|
console.error("Update failed:", e instanceof Error ? e.message : e);
|
|
178
|
-
console.error("\nTry manually: npm install -g @
|
|
244
|
+
console.error("\nTry manually: npm install -g signet-agent@latest");
|
|
179
245
|
process.exit(1);
|
|
180
246
|
}
|
|
181
247
|
}
|
|
@@ -198,7 +264,7 @@ async function autoStartAndWait() {
|
|
|
198
264
|
});
|
|
199
265
|
child.unref();
|
|
200
266
|
if (child.pid)
|
|
201
|
-
writeFileSync(PID_FILE, String(child.pid), "utf8");
|
|
267
|
+
writeFileSync(PID_FILE, String(child.pid), { encoding: "utf8", mode: 0o600 });
|
|
202
268
|
// Poll up to 12 seconds for daemon to be up and relay-connected
|
|
203
269
|
const deadline = Date.now() + 12_000;
|
|
204
270
|
while (Date.now() < deadline) {
|
|
@@ -246,23 +312,60 @@ async function cmdInit() {
|
|
|
246
312
|
const config = loadConfig();
|
|
247
313
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
248
314
|
let authToken = config.authToken;
|
|
249
|
-
if (
|
|
250
|
-
const
|
|
315
|
+
if (authToken) {
|
|
316
|
+
const changeToken = await question(rl, `Auth token saved. Change it? (y/N): `);
|
|
317
|
+
if (changeToken.toLowerCase() === "y") {
|
|
318
|
+
const tokenInput = await question(rl, "New auth token: ");
|
|
319
|
+
if (tokenInput)
|
|
320
|
+
authToken = tokenInput;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
console.log(" To link this agent to your account:");
|
|
325
|
+
console.log(" 1. Sign in at onsignet.com/dashboard");
|
|
326
|
+
console.log(' 2. Click "Copy token" in the setup section');
|
|
327
|
+
console.log(" 3. Paste it below\n");
|
|
328
|
+
const tokenInput = await question(rl, "Auth token (or press Enter to skip): ");
|
|
251
329
|
if (tokenInput)
|
|
252
330
|
authToken = tokenInput;
|
|
253
331
|
}
|
|
254
|
-
const
|
|
255
|
-
const
|
|
332
|
+
const existing = await getExistingProfile();
|
|
333
|
+
const defaultName = existing?.name ?? "My Agent";
|
|
334
|
+
if (existing?.name) {
|
|
335
|
+
console.log(" Updating existing directory profile. Press Enter to keep current values.\n");
|
|
336
|
+
}
|
|
337
|
+
const namePrompt = existing?.name
|
|
338
|
+
? `Agent name [${existing.name}] (press Enter to keep): `
|
|
339
|
+
: "Agent name (e.g. My Research Assistant): ";
|
|
340
|
+
const nameInput = await question(rl, namePrompt);
|
|
341
|
+
const name = nameInput || defaultName;
|
|
342
|
+
const descPrompt = existing?.description != null && existing.description !== ""
|
|
343
|
+
? `Short description [${existing.description}] (press Enter to keep): `
|
|
344
|
+
: "Short description (optional): ";
|
|
345
|
+
const descriptionInput = await question(rl, descPrompt);
|
|
346
|
+
const description = descriptionInput !== "" ? descriptionInput : (existing?.description ?? "");
|
|
256
347
|
const domainsInput = await question(rl, "Capability domains, comma-separated (e.g. research, scheduling) (optional): ");
|
|
257
348
|
const capabilities = domainsInput
|
|
258
349
|
? domainsInput.split(",").map((d) => ({ domain: d.trim() })).filter((c) => c.domain.length > 0)
|
|
259
350
|
: undefined;
|
|
260
351
|
// Framework selection if not provided via flag
|
|
261
352
|
if (!framework) {
|
|
262
|
-
const
|
|
353
|
+
const frameworkDefault = existing?.framework ? ` [${existing.framework}]` : "";
|
|
354
|
+
const frameworkInput = await question(rl, `Framework (openclaw / mcp / python / rest)${frameworkDefault} [press Enter to auto-detect]: `);
|
|
263
355
|
if (frameworkInput && ["openclaw", "mcp", "python", "rest"].includes(frameworkInput)) {
|
|
264
356
|
framework = frameworkInput;
|
|
265
357
|
}
|
|
358
|
+
else if (existing?.framework && ["openclaw", "mcp", "python", "rest"].includes(existing.framework)) {
|
|
359
|
+
framework = existing.framework;
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
const { detectFramework } = await import("./detect-framework.js");
|
|
363
|
+
const detected = detectFramework();
|
|
364
|
+
if (detected !== "unknown") {
|
|
365
|
+
framework = detected;
|
|
366
|
+
console.log(` Auto-detected framework: ${detected}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
266
369
|
}
|
|
267
370
|
rl.close();
|
|
268
371
|
console.log("\nRegistering with the directory...");
|
|
@@ -356,12 +459,24 @@ async function main() {
|
|
|
356
459
|
await cmdSetupOpenclaw();
|
|
357
460
|
return;
|
|
358
461
|
case "serve-mcp": {
|
|
359
|
-
|
|
462
|
+
let mcpPath = join(__dirname, "..", "..", "adapters", "mcp", "dist", "index.js");
|
|
463
|
+
if (!existsSync(mcpPath)) {
|
|
464
|
+
try {
|
|
465
|
+
mcpPath = require.resolve("@signet/mcp-server");
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
try {
|
|
469
|
+
mcpPath = require.resolve("@onsignet/mcp-server");
|
|
470
|
+
}
|
|
471
|
+
catch { /* use default */ }
|
|
472
|
+
}
|
|
473
|
+
}
|
|
360
474
|
try {
|
|
361
|
-
|
|
475
|
+
const child = spawn(process.execPath, [mcpPath], { stdio: "inherit", env: process.env });
|
|
476
|
+
child.on("exit", (code) => process.exit(code ?? 1));
|
|
362
477
|
}
|
|
363
478
|
catch {
|
|
364
|
-
console.error(
|
|
479
|
+
console.error(`Failed to start MCP server at ${mcpPath}. Build it first: cd adapters/mcp && npm run build`);
|
|
365
480
|
}
|
|
366
481
|
return;
|
|
367
482
|
}
|
package/dist/setup-mcp.js
CHANGED
|
@@ -2,9 +2,28 @@
|
|
|
2
2
|
* `signet setup-mcp` — auto-configure MCP hosts to use Signet tools.
|
|
3
3
|
*/
|
|
4
4
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
5
|
-
import { join, resolve } from "path";
|
|
5
|
+
import { join, resolve, dirname } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { createRequire } from "module";
|
|
8
|
+
import { homedir } from "os";
|
|
6
9
|
import { detectMcpHosts } from "./detect-framework.js";
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
7
13
|
const DAEMON_URL = process.env.SIGNET_DAEMON_URL ?? "http://127.0.0.1:8766";
|
|
14
|
+
const DATA_DIR = process.env.SIGNET_DATA_DIR ?? join(homedir(), ".Signet");
|
|
15
|
+
function getDaemonAuthHeaders() {
|
|
16
|
+
const tokenPath = join(DATA_DIR, "api-token");
|
|
17
|
+
try {
|
|
18
|
+
if (existsSync(tokenPath)) {
|
|
19
|
+
const token = readFileSync(tokenPath, "utf8").trim();
|
|
20
|
+
if (token)
|
|
21
|
+
return { Authorization: `Bearer ${token}` };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch { /* ignore */ }
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
8
27
|
function findMcpServerPath() {
|
|
9
28
|
const candidates = [
|
|
10
29
|
join(__dirname, "..", "..", "adapters", "mcp", "dist", "index.js"),
|
|
@@ -15,16 +34,28 @@ function findMcpServerPath() {
|
|
|
15
34
|
if (existsSync(resolved))
|
|
16
35
|
return resolved;
|
|
17
36
|
}
|
|
18
|
-
// Fall back: try require resolution
|
|
19
37
|
try {
|
|
20
|
-
|
|
21
|
-
return path;
|
|
38
|
+
return require.resolve("@signet/mcp-server");
|
|
22
39
|
}
|
|
23
|
-
catch {
|
|
24
|
-
|
|
40
|
+
catch { /* not installed */ }
|
|
41
|
+
try {
|
|
42
|
+
return require.resolve("@onsignet/mcp-server");
|
|
25
43
|
}
|
|
44
|
+
catch { /* not installed */ }
|
|
26
45
|
return resolve(join(__dirname, "..", "..", "adapters", "mcp", "dist", "index.js"));
|
|
27
46
|
}
|
|
47
|
+
function getApiTokenForEnv() {
|
|
48
|
+
if (process.env.SIGNET_API_TOKEN)
|
|
49
|
+
return process.env.SIGNET_API_TOKEN;
|
|
50
|
+
const tokenPath = join(DATA_DIR, "api-token");
|
|
51
|
+
try {
|
|
52
|
+
if (existsSync(tokenPath)) {
|
|
53
|
+
return readFileSync(tokenPath, "utf8").trim();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch { /* ignore */ }
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
28
59
|
function addToMcpConfig(configPath, serverPath) {
|
|
29
60
|
let config = {};
|
|
30
61
|
if (existsSync(configPath)) {
|
|
@@ -32,17 +63,19 @@ function addToMcpConfig(configPath, serverPath) {
|
|
|
32
63
|
config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
33
64
|
}
|
|
34
65
|
catch {
|
|
35
|
-
|
|
66
|
+
console.error(` Error: ${configPath} contains invalid JSON. Please fix or delete it before running setup-mcp.`);
|
|
67
|
+
return false;
|
|
36
68
|
}
|
|
37
69
|
}
|
|
38
70
|
const servers = (config.mcpServers ?? {});
|
|
39
71
|
if (servers.signet) {
|
|
40
|
-
return false;
|
|
72
|
+
return false;
|
|
41
73
|
}
|
|
74
|
+
const apiToken = getApiTokenForEnv();
|
|
42
75
|
servers.signet = {
|
|
43
76
|
command: "node",
|
|
44
77
|
args: [serverPath],
|
|
45
|
-
env: {},
|
|
78
|
+
env: apiToken ? { SIGNET_API_TOKEN: apiToken } : {},
|
|
46
79
|
};
|
|
47
80
|
config.mcpServers = servers;
|
|
48
81
|
const dir = configPath.replace(/[/\\][^/\\]+$/, "");
|
|
@@ -53,11 +86,13 @@ function addToMcpConfig(configPath, serverPath) {
|
|
|
53
86
|
}
|
|
54
87
|
export async function cmdSetupMcp() {
|
|
55
88
|
console.log("Signet MCP Setup\n");
|
|
56
|
-
// 1. Check daemon
|
|
57
89
|
try {
|
|
58
|
-
const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/status`, {
|
|
90
|
+
const res = await fetch(`${DAEMON_URL.replace(/\/$/, "")}/status`, {
|
|
91
|
+
signal: AbortSignal.timeout(5000),
|
|
92
|
+
headers: getDaemonAuthHeaders(),
|
|
93
|
+
});
|
|
59
94
|
if (!res.ok) {
|
|
60
|
-
console.error(`Daemon returned HTTP ${res.status}. Start the daemon first: npx signet start`);
|
|
95
|
+
console.error(`Daemon returned HTTP ${res.status}. Start the daemon first: npx signet-agent start`);
|
|
61
96
|
process.exit(1);
|
|
62
97
|
}
|
|
63
98
|
const data = (await res.json());
|
|
@@ -67,16 +102,14 @@ export async function cmdSetupMcp() {
|
|
|
67
102
|
}
|
|
68
103
|
}
|
|
69
104
|
catch {
|
|
70
|
-
console.error(`Cannot reach daemon at ${DAEMON_URL}.\nStart it first: npx signet start`);
|
|
105
|
+
console.error(`Cannot reach daemon at ${DAEMON_URL}.\nStart it first: npx signet-agent start`);
|
|
71
106
|
process.exit(1);
|
|
72
107
|
}
|
|
73
|
-
// 2. Find MCP server binary
|
|
74
108
|
const serverPath = findMcpServerPath();
|
|
75
109
|
console.log(` MCP server: ${serverPath}`);
|
|
76
110
|
if (!existsSync(serverPath)) {
|
|
77
111
|
console.warn(` Warning: MCP server not found at ${serverPath}. Build it first: cd adapters/mcp && npm run build`);
|
|
78
112
|
}
|
|
79
|
-
// 3. Detect and configure MCP hosts
|
|
80
113
|
const hosts = detectMcpHosts();
|
|
81
114
|
const detected = hosts.filter((h) => {
|
|
82
115
|
const dir = h.configPath.replace(/[/\\][^/\\]+$/, "");
|
|
@@ -84,7 +117,7 @@ export async function cmdSetupMcp() {
|
|
|
84
117
|
});
|
|
85
118
|
if (detected.length === 0) {
|
|
86
119
|
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.");
|
|
120
|
+
console.log(" Install one, then run `npx signet-agent setup-mcp` again.");
|
|
88
121
|
console.log("\n Manual configuration:");
|
|
89
122
|
console.log(` Add this to your MCP config file:`);
|
|
90
123
|
console.log(` {`);
|
|
@@ -102,11 +135,11 @@ export async function cmdSetupMcp() {
|
|
|
102
135
|
for (const host of detected) {
|
|
103
136
|
const added = addToMcpConfig(host.configPath, serverPath);
|
|
104
137
|
if (added) {
|
|
105
|
-
console.log(`
|
|
138
|
+
console.log(` + ${host.name} — configured (${host.configPath})`);
|
|
106
139
|
configured++;
|
|
107
140
|
}
|
|
108
141
|
else {
|
|
109
|
-
console.log(`
|
|
142
|
+
console.log(` . ${host.name} — already configured`);
|
|
110
143
|
}
|
|
111
144
|
}
|
|
112
145
|
console.log();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "signet-agent",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Signet
|
|
3
|
+
"version": "0.1.6",
|
|
4
|
+
"description": "CLI for the Signet agent network. Register your AI agent on onsignet.com, connect to the relay, and get a live public profile — in one command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"init": "node dist/index.js"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@onsignet/daemon": "0.1.3"
|
|
20
|
+
"@onsignet/daemon": "^0.1.3"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/node": "^20.10.0",
|
|
@@ -25,5 +25,29 @@
|
|
|
25
25
|
},
|
|
26
26
|
"engines": {
|
|
27
27
|
"node": ">=18.0.0"
|
|
28
|
-
}
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/camdavissignet/Signet.git",
|
|
32
|
+
"directory": "cli"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://onsignet.com",
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/camdavissignet/Signet/issues"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"signet",
|
|
40
|
+
"ai-agents",
|
|
41
|
+
"agent-directory",
|
|
42
|
+
"agent-identity",
|
|
43
|
+
"agent-communication",
|
|
44
|
+
"mcp",
|
|
45
|
+
"model-context-protocol",
|
|
46
|
+
"langchain",
|
|
47
|
+
"crewai",
|
|
48
|
+
"autogen",
|
|
49
|
+
"openclaw",
|
|
50
|
+
"daemon",
|
|
51
|
+
"cli"
|
|
52
|
+
]
|
|
29
53
|
}
|