@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
- ## Publish an npm update
65
+ ## Update, build, and publish
66
66
 
67
- Updating this repository does not update the published `npx` / global CLI package. To ship CLI changes, publish a new package version to npm:
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 patch
75
- npm publish --access public
78
+ npm version <next-version> --no-git-tag-version
76
79
  ```
77
80
 
78
- Use `npm version minor` or `npm version major` instead of `patch` when appropriate. You must be logged into npm with publish access:
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 whoami
82
- npm login
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
- # or run without installing
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 provider URL is `SEED_NETWORK_AGENT_AUTH_PROVIDER` or `http://localhost:3000`.
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 response = await fetch(agentUploadUrl(connection), {
14
- method: "POST",
15
- headers: {
16
- Authorization: `Bearer ${token}`,
17
- },
18
- body: form,
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));
@@ -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 DEFAULT_PROVIDER_URL = process.env.SEED_NETWORK_AGENT_AUTH_PROVIDER ?? "http://localhost:3000";
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.find((connection) => hasActiveCapability(connection, SEED_SUBMIT_DEAL_CAPABILITY))
49
- ?? connections[0];
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.find((connection) => hasActiveCapability(connection, capability));
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
- await client.disconnectAgent(connection.agentId);
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",
3
+ "version": "0.1.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
7
- "seed": "./dist/cli.js",
8
- "seed-agent": "./dist/cli.js",
9
- "seed-network-agent": "./dist/cli.js"
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",