@connormartin/seed-network-agent 0.1.3 → 0.1.5
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
|
@@ -62,31 +62,46 @@ npx @connormartin/seed-network-agent@latest
|
|
|
62
62
|
|
|
63
63
|
The CLI checks npm for updates at startup at most once per day and prints the exact update command when a newer version is available. Disable this with `SEED_NETWORK_AGENT_NO_UPDATE_CHECK=1`.
|
|
64
64
|
|
|
65
|
-
##
|
|
65
|
+
## Update, build, and publish
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
From the repository root, make the agent changes, then build the package:
|
|
68
68
|
|
|
69
69
|
```bash
|
|
70
|
-
# from the repo root
|
|
71
70
|
pnpm --filter @connormartin/seed-network-agent build
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Before publishing, bump the npm package version in `apps/agent/package.json` to a version higher than the current npm `latest`:
|
|
72
74
|
|
|
75
|
+
```bash
|
|
76
|
+
npm view @connormartin/seed-network-agent version
|
|
73
77
|
cd apps/agent
|
|
74
|
-
npm version
|
|
75
|
-
npm publish --access public
|
|
78
|
+
npm version <next-version> --no-git-tag-version
|
|
76
79
|
```
|
|
77
80
|
|
|
78
|
-
Use
|
|
81
|
+
Use semver patch/minor/major as appropriate. For example, if npm `latest` is `0.1.3`, publish `0.1.4` or higher. Then verify the package contents and publish:
|
|
79
82
|
|
|
80
83
|
```bash
|
|
81
|
-
npm
|
|
82
|
-
npm
|
|
84
|
+
npm pack --dry-run
|
|
85
|
+
npm publish --access public
|
|
83
86
|
```
|
|
84
87
|
|
|
85
|
-
After publishing, users can update with:
|
|
88
|
+
After publishing, users can update their installed CLI with:
|
|
86
89
|
|
|
87
90
|
```bash
|
|
88
91
|
npm install -g @connormartin/seed-network-agent@latest
|
|
89
|
-
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If npm returns `ETARGET` immediately after publishing, the registry metadata and tarball may not have fully propagated yet. Wait a minute, verify the package, then retry with fresh registry metadata:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm view @connormartin/seed-network-agent version versions
|
|
98
|
+
npm install -g @connormartin/seed-network-agent@latest --prefer-online --registry=https://registry.npmjs.org/
|
|
99
|
+
hash -r
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Or run the latest version directly:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
90
105
|
npx @connormartin/seed-network-agent@latest
|
|
91
106
|
```
|
|
92
107
|
|
|
@@ -123,5 +138,18 @@ The wrapper can connect to the Seed Network web app as an Agent Auth client:
|
|
|
123
138
|
/seed disconnect
|
|
124
139
|
```
|
|
125
140
|
|
|
126
|
-
By default the
|
|
141
|
+
By default, the published npm CLI (`seed`) connects to `https://seed-network-v2.vercel.app/`, while the local source command (`pnpm agent`) connects to `http://localhost:3000` for development. Override either with `SEED_NETWORK_AGENT_AUTH_PROVIDER`.
|
|
127
142
|
Agent Auth identity and grants are stored locally under `~/.seed-network-agent/auth/`.
|
|
143
|
+
|
|
144
|
+
If `/submit-deal` fails with `fetch failed` during PDF upload, check the upload URL in the error. If it mentions `localhost:3000`, remove stale local Agent Auth state and reconnect to production:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
/seed disconnect
|
|
148
|
+
/connect
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
You can also force the provider for one run:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
SEED_NETWORK_AGENT_AUTH_PROVIDER=https://seed-network-v2.vercel.app/ seed
|
|
155
|
+
```
|
|
@@ -46,7 +46,7 @@ async function handleSeedCommand(args, ctx) {
|
|
|
46
46
|
const grants = connection.capabilityGrants
|
|
47
47
|
.map((grant) => `${grant.capability}:${grant.status}`)
|
|
48
48
|
.join(", ");
|
|
49
|
-
return `${connection.providerName} ${connection.agentId} (${connection.mode}) — ${grants || "no grants"}`;
|
|
49
|
+
return `${connection.providerName} ${connection.agentId} (${connection.mode}, ${connection.issuer}) — ${grants || "no grants"}`;
|
|
50
50
|
});
|
|
51
51
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
52
52
|
return;
|
|
@@ -41,7 +41,7 @@ export async function preparePdfDealSubmission(submission, connection, ui, optio
|
|
|
41
41
|
ui?.notify?.(`Deal interpretation warnings:\n${interpreted.warnings.join("\n")}`, "warning");
|
|
42
42
|
}
|
|
43
43
|
ui?.notify?.("Uploading PDF deck to Seed Network storage…", "info");
|
|
44
|
-
const deck = await uploadDealDeck(validated, connection);
|
|
44
|
+
const deck = await uploadDealDeck(validated, connection, ui);
|
|
45
45
|
const sanitizedAnswers = omitAnswer(submission.answers, "deck_file_path");
|
|
46
46
|
const explicitName = answerValue(submission.answers.name);
|
|
47
47
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createSeedAgentAuthClient, DEFAULT_PROVIDER_URL, SEED_SUBMIT_DEAL_CAPABILITY } from "../../seed-agent-auth.js";
|
|
2
|
-
export async function uploadDealDeck(validated, connection) {
|
|
1
|
+
import { createSeedAgentAuthClient, DEFAULT_PROVIDER_URL, isLocalhostUrl, SEED_SUBMIT_DEAL_CAPABILITY, } from "../../seed-agent-auth.js";
|
|
2
|
+
export async function uploadDealDeck(validated, connection, ui) {
|
|
3
3
|
const client = createSeedAgentAuthClient();
|
|
4
4
|
try {
|
|
5
5
|
const { token } = await client.signJwt({
|
|
@@ -10,17 +10,25 @@ export async function uploadDealDeck(validated, connection) {
|
|
|
10
10
|
form.set("file", new Blob([validated.buffer], { type: "application/pdf" }), validated.fileName || "deck.pdf");
|
|
11
11
|
form.set("sha256", validated.sha256);
|
|
12
12
|
form.set("size", String(validated.size));
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
const uploadUrl = agentUploadUrl(connection);
|
|
14
|
+
ui?.notify?.(`Uploading PDF deck to ${urlOrigin(uploadUrl) ?? uploadUrl}…`, "info");
|
|
15
|
+
let response;
|
|
16
|
+
try {
|
|
17
|
+
response = await fetch(uploadUrl, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: {
|
|
20
|
+
Authorization: `Bearer ${token}`,
|
|
21
|
+
},
|
|
22
|
+
body: form,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
throw new Error(`PDF deck upload could not reach ${uploadUrl} (Agent Auth issuer: ${connection.issuer}). ${formatFetchError(error)}${uploadHint(uploadUrl)}`, { cause: error });
|
|
27
|
+
}
|
|
20
28
|
const data = await response.json().catch(() => null);
|
|
21
29
|
if (!response.ok) {
|
|
22
30
|
const message = isRecord(data) && typeof data.error === "string" ? data.error : `HTTP ${response.status}`;
|
|
23
|
-
throw new Error(`PDF deck upload failed: ${message}`);
|
|
31
|
+
throw new Error(`PDF deck upload failed at ${uploadUrl}: ${message}`);
|
|
24
32
|
}
|
|
25
33
|
if (!isRecord(data) || data.ok !== true || !isRecord(data.deck)) {
|
|
26
34
|
throw new Error("PDF deck upload returned an invalid response.");
|
|
@@ -52,6 +60,36 @@ function agentUploadUrl(connection) {
|
|
|
52
60
|
return new URL("/api/agent/deal-deck-upload", DEFAULT_PROVIDER_URL).toString();
|
|
53
61
|
}
|
|
54
62
|
}
|
|
63
|
+
function uploadHint(uploadUrl) {
|
|
64
|
+
if (isLocalhostUrl(uploadUrl)) {
|
|
65
|
+
return " This looks like a stale localhost Agent Auth connection. Run /seed disconnect, then /connect, then retry /submit-deal.";
|
|
66
|
+
}
|
|
67
|
+
return " Check your network connection or set SEED_NETWORK_AGENT_DECK_UPLOAD_URL to override the upload endpoint.";
|
|
68
|
+
}
|
|
69
|
+
function formatFetchError(error) {
|
|
70
|
+
if (!(error instanceof Error))
|
|
71
|
+
return `Fetch error: ${String(error)}.`;
|
|
72
|
+
const cause = error.cause;
|
|
73
|
+
if (cause && typeof cause === "object") {
|
|
74
|
+
const code = "code" in cause && typeof cause.code === "string" ? cause.code : undefined;
|
|
75
|
+
const message = "message" in cause && typeof cause.message === "string" ? cause.message : undefined;
|
|
76
|
+
if (code && message)
|
|
77
|
+
return `Fetch error: ${error.message} (${code}: ${message}).`;
|
|
78
|
+
if (code)
|
|
79
|
+
return `Fetch error: ${error.message} (${code}).`;
|
|
80
|
+
if (message)
|
|
81
|
+
return `Fetch error: ${error.message} (${message}).`;
|
|
82
|
+
}
|
|
83
|
+
return `Fetch error: ${error.message}.`;
|
|
84
|
+
}
|
|
85
|
+
function urlOrigin(value) {
|
|
86
|
+
try {
|
|
87
|
+
return new URL(value).origin;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
55
93
|
function stringProperty(record, field) {
|
|
56
94
|
const value = record[field];
|
|
57
95
|
if (typeof value !== "string" || value.length === 0)
|
package/dist/pi.js
CHANGED
|
@@ -34,6 +34,7 @@ export async function runPi(userArgs = []) {
|
|
|
34
34
|
env: {
|
|
35
35
|
...process.env,
|
|
36
36
|
SEED_NETWORK_AGENT_ROOT: agentRoot,
|
|
37
|
+
SEED_NETWORK_AGENT_RUNTIME: process.env.SEED_NETWORK_AGENT_RUNTIME ?? agentRuntime(),
|
|
37
38
|
},
|
|
38
39
|
});
|
|
39
40
|
await new Promise((resolve, reject) => {
|
|
@@ -54,6 +55,9 @@ function resolveExtensionPath() {
|
|
|
54
55
|
return compiledExtensionPath;
|
|
55
56
|
return path.join(__dirname, "extension", "index.ts");
|
|
56
57
|
}
|
|
58
|
+
function agentRuntime() {
|
|
59
|
+
return path.basename(__dirname) === "src" ? "source" : "package";
|
|
60
|
+
}
|
|
57
61
|
function shouldCheckForUpdates(userArgs) {
|
|
58
62
|
const skipFlags = new Set(["--help", "-h", "--version", "-v", "--offline"]);
|
|
59
63
|
return !userArgs.some((arg) => skipFlags.has(arg));
|
package/dist/seed-agent-auth.js
CHANGED
|
@@ -3,8 +3,13 @@ import os from "node:os";
|
|
|
3
3
|
import { AgentAuthClient } from "@auth/agent";
|
|
4
4
|
import { SeedAgentAuthStorage } from "./agent-auth-storage.js";
|
|
5
5
|
export const SEED_SUBMIT_DEAL_CAPABILITY = "submit_deal";
|
|
6
|
-
export const
|
|
6
|
+
export const LOCAL_PROVIDER_URL = "http://localhost:3000";
|
|
7
|
+
export const PROD_PROVIDER_URL = "https://seed-network-v2.vercel.app/";
|
|
8
|
+
export const DEFAULT_PROVIDER_URL = process.env.SEED_NETWORK_AGENT_AUTH_PROVIDER ?? defaultProviderUrl();
|
|
7
9
|
const DEFAULT_SEED_CAPABILITIES = [SEED_SUBMIT_DEAL_CAPABILITY];
|
|
10
|
+
function defaultProviderUrl() {
|
|
11
|
+
return process.env.SEED_NETWORK_AGENT_RUNTIME === "source" ? LOCAL_PROVIDER_URL : PROD_PROVIDER_URL;
|
|
12
|
+
}
|
|
8
13
|
export function createSeedAgentAuthClient(ui) {
|
|
9
14
|
return new AgentAuthClient({
|
|
10
15
|
storage: new SeedAgentAuthStorage(),
|
|
@@ -45,8 +50,8 @@ export async function listSeedAgentConnections() {
|
|
|
45
50
|
}
|
|
46
51
|
export async function getPreferredConnection() {
|
|
47
52
|
const connections = await listSeedAgentConnections();
|
|
48
|
-
return connections.
|
|
49
|
-
?? connections
|
|
53
|
+
return preferCurrentProvider(connections.filter((connection) => hasActiveCapability(connection, SEED_SUBMIT_DEAL_CAPABILITY)))
|
|
54
|
+
?? preferCurrentProvider(connections);
|
|
50
55
|
}
|
|
51
56
|
export async function ensureActiveSeedCapability(client, capability, ui, approval) {
|
|
52
57
|
const storage = new SeedAgentAuthStorage();
|
|
@@ -138,11 +143,41 @@ async function waitForActiveCapability(client, agentId, capability, timeoutMs =
|
|
|
138
143
|
}
|
|
139
144
|
async function getConnectionWithCapability(capability) {
|
|
140
145
|
const connections = await listSeedAgentConnections();
|
|
141
|
-
return connections.
|
|
146
|
+
return preferCurrentProvider(connections.filter((connection) => hasActiveCapability(connection, capability)));
|
|
142
147
|
}
|
|
143
148
|
function hasActiveCapability(connection, capability) {
|
|
144
149
|
return connection.capabilityGrants.some((grant) => grant.capability === capability && grant.status === "active");
|
|
145
150
|
}
|
|
151
|
+
function preferCurrentProvider(connections) {
|
|
152
|
+
const usableConnections = connections.filter((connection) => !isStaleLocalhostConnection(connection));
|
|
153
|
+
return usableConnections.find(isCurrentProviderConnection) ?? usableConnections[0];
|
|
154
|
+
}
|
|
155
|
+
function isCurrentProviderConnection(connection) {
|
|
156
|
+
return urlOrigin(connection.issuer) === urlOrigin(DEFAULT_PROVIDER_URL);
|
|
157
|
+
}
|
|
158
|
+
export function isStaleLocalhostConnection(connection) {
|
|
159
|
+
return isLocalhostUrl(connection.issuer) && !isLocalhostUrl(DEFAULT_PROVIDER_URL);
|
|
160
|
+
}
|
|
161
|
+
export function isLocalhostUrl(value) {
|
|
162
|
+
const hostname = urlHostname(value);
|
|
163
|
+
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
|
|
164
|
+
}
|
|
165
|
+
function urlOrigin(value) {
|
|
166
|
+
try {
|
|
167
|
+
return new URL(value).origin;
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function urlHostname(value) {
|
|
174
|
+
try {
|
|
175
|
+
return new URL(value).hostname;
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
146
181
|
function isAgentAuthErrorCode(error, ...codes) {
|
|
147
182
|
return typeof error === "object"
|
|
148
183
|
&& error !== null
|
|
@@ -156,10 +191,17 @@ async function sleep(ms) {
|
|
|
156
191
|
export async function disconnectSeedAgents(ui) {
|
|
157
192
|
const connections = await listSeedAgentConnections();
|
|
158
193
|
const client = createSeedAgentAuthClient(ui);
|
|
194
|
+
const storage = new SeedAgentAuthStorage();
|
|
159
195
|
let disconnected = 0;
|
|
160
196
|
try {
|
|
161
197
|
for (const connection of connections) {
|
|
162
|
-
|
|
198
|
+
try {
|
|
199
|
+
await client.disconnectAgent(connection.agentId);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
ui?.notify?.(`Could not revoke Seed Network Agent ${connection.agentId} at ${connection.issuer}; removing the local saved connection anyway. ${formatError(error)}`, "warning");
|
|
203
|
+
}
|
|
204
|
+
await storage.deleteAgentConnection(connection.agentId);
|
|
163
205
|
disconnected += 1;
|
|
164
206
|
}
|
|
165
207
|
}
|
|
@@ -168,6 +210,22 @@ export async function disconnectSeedAgents(ui) {
|
|
|
168
210
|
}
|
|
169
211
|
return disconnected;
|
|
170
212
|
}
|
|
213
|
+
function formatError(error) {
|
|
214
|
+
if (!(error instanceof Error))
|
|
215
|
+
return String(error);
|
|
216
|
+
const cause = error.cause;
|
|
217
|
+
if (cause && typeof cause === "object") {
|
|
218
|
+
const code = "code" in cause && typeof cause.code === "string" ? cause.code : undefined;
|
|
219
|
+
const message = "message" in cause && typeof cause.message === "string" ? cause.message : undefined;
|
|
220
|
+
if (code && message)
|
|
221
|
+
return `${error.message} (${code}: ${message})`;
|
|
222
|
+
if (code)
|
|
223
|
+
return `${error.message} (${code})`;
|
|
224
|
+
if (message)
|
|
225
|
+
return `${error.message} (${message})`;
|
|
226
|
+
}
|
|
227
|
+
return error.message;
|
|
228
|
+
}
|
|
171
229
|
function formatApprovalMessage(approval) {
|
|
172
230
|
const lines = ["Seed Network Agent requires browser approval."];
|
|
173
231
|
if (approval.user_code)
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connormartin/seed-network-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"seed": "
|
|
8
|
-
"seed-agent": "
|
|
9
|
-
"seed-network-agent": "
|
|
7
|
+
"seed": "dist/cli.js",
|
|
8
|
+
"seed-agent": "dist/cli.js",
|
|
9
|
+
"seed-network-agent": "dist/cli.js"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|