@connormartin/seed-network-agent 0.1.0 → 0.1.1
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 +35 -17
- package/dist/extension/index.js +0 -2
- package/dist/features/agent-auth/register.js +21 -22
- package/dist/features/branding/register.js +1 -1
- package/dist/features/submit-deal-pdf/register.js +2 -2
- package/dist/seed-agent-auth.js +4 -23
- package/dist/wizard.js +6 -6
- package/package.json +2 -2
- package/prompts/SYSTEM.md +5 -5
- package/dist/features/onboarding/questionnaire.v1.json +0 -89
- package/dist/features/onboarding/register.js +0 -78
- package/dist/onboarding-extension.js +0 -1
- package/dist/store.js +0 -92
package/README.md
CHANGED
|
@@ -10,8 +10,7 @@ Seed-owned behavior:
|
|
|
10
10
|
- `prompts/*`
|
|
11
11
|
- `src/extension/*` — Pi extension entrypoint and shared extension types
|
|
12
12
|
- `src/features/*` — feature modules registered by the extension
|
|
13
|
-
- `src/wizard.ts`
|
|
14
|
-
- `src/store.ts`
|
|
13
|
+
- `src/wizard.ts` — shared deterministic questionnaire helper used by feature commands
|
|
15
14
|
|
|
16
15
|
Pi boundary:
|
|
17
16
|
- `src/pi.ts`
|
|
@@ -24,8 +23,7 @@ Features should be easy to find and remove. Add new Seed Agent behavior under `s
|
|
|
24
23
|
|
|
25
24
|
Current features:
|
|
26
25
|
- `features/branding` — Seed header/title customization.
|
|
27
|
-
- `features/
|
|
28
|
-
- `features/agent-auth` — `/seed` Agent Auth management commands.
|
|
26
|
+
- `features/agent-auth` — `/connect` and `/seed` Agent Auth management commands.
|
|
29
27
|
- `features/submit-deal-pdf` — `/submit-deal` PDF intake, local extraction, model interpretation, upload, and capability execution.
|
|
30
28
|
|
|
31
29
|
Pi supports this directory-extension shape directly: a multi-file extension is an `index.ts` entrypoint plus helper modules.
|
|
@@ -34,10 +32,9 @@ Pi supports this directory-extension shape directly: a multi-file extension is a
|
|
|
34
32
|
|
|
35
33
|
1. Start Pi with Seed Network Agent resources loaded.
|
|
36
34
|
2. Register Seed feature modules from `src/extension/index.ts`.
|
|
37
|
-
3.
|
|
38
|
-
4.
|
|
39
|
-
5.
|
|
40
|
-
6. Add `/submit-deal` for local PDF deal intake.
|
|
35
|
+
3. Load versioned feature questionnaires from each feature directory.
|
|
36
|
+
4. Add `/submit-deal` for local PDF deal intake.
|
|
37
|
+
5. Add `/connect` and `/seed` commands for Agent Auth connection management.
|
|
41
38
|
|
|
42
39
|
## What we do not change
|
|
43
40
|
|
|
@@ -65,6 +62,34 @@ npx @connormartin/seed-network-agent@latest
|
|
|
65
62
|
|
|
66
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`.
|
|
67
64
|
|
|
65
|
+
## Publish an npm update
|
|
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:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# from the repo root
|
|
71
|
+
pnpm --filter @connormartin/seed-network-agent build
|
|
72
|
+
|
|
73
|
+
cd apps/agent
|
|
74
|
+
npm version patch
|
|
75
|
+
npm publish --access public
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use `npm version minor` or `npm version major` instead of `patch` when appropriate. You must be logged into npm with publish access:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
npm whoami
|
|
82
|
+
npm login
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
After publishing, users can update with:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install -g @connormartin/seed-network-agent@latest
|
|
89
|
+
# or run without installing
|
|
90
|
+
npx @connormartin/seed-network-agent@latest
|
|
91
|
+
```
|
|
92
|
+
|
|
68
93
|
## Run locally
|
|
69
94
|
|
|
70
95
|
From the repository root:
|
|
@@ -80,11 +105,7 @@ pnpm --filter @connormartin/seed-network-agent build
|
|
|
80
105
|
pnpm --filter @connormartin/seed-network-agent start
|
|
81
106
|
```
|
|
82
107
|
|
|
83
|
-
Run `/
|
|
84
|
-
|
|
85
|
-
Run `/submit-deal` inside the agent to create a Seed Network deal from a local pitch deck PDF and the first intake questions.
|
|
86
|
-
|
|
87
|
-
If you need the previous startup behavior, set `SEED_NETWORK_AGENT_AUTOSTART_ONBOARDING=true` before launching the agent.
|
|
108
|
+
Run `/submit-deal` inside the agent to create a Seed Network deal from a local pitch deck PDF and intake questions.
|
|
88
109
|
|
|
89
110
|
You can forward normal Pi flags through the wrapper:
|
|
90
111
|
|
|
@@ -92,16 +113,13 @@ You can forward normal Pi flags through the wrapper:
|
|
|
92
113
|
pnpm agent -- --model anthropic/claude-sonnet-4-5
|
|
93
114
|
```
|
|
94
115
|
|
|
95
|
-
Local onboarding progress is written under `~/.seed-network-agent/onboarding/`.
|
|
96
|
-
|
|
97
116
|
## Seed Agent Auth
|
|
98
117
|
|
|
99
118
|
The wrapper can connect to the Seed Network web app as an Agent Auth client:
|
|
100
119
|
|
|
101
120
|
```bash
|
|
102
|
-
/
|
|
121
|
+
/connect [provider-url]
|
|
103
122
|
/seed status
|
|
104
|
-
/seed sync-onboarding
|
|
105
123
|
/seed disconnect
|
|
106
124
|
```
|
|
107
125
|
|
package/dist/extension/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { registerAgentAuthCommands } from "../features/agent-auth/register.js";
|
|
2
2
|
import { registerBranding } from "../features/branding/register.js";
|
|
3
|
-
import { registerOnboarding } from "../features/onboarding/register.js";
|
|
4
3
|
import { registerSubmitDealPdf } from "../features/submit-deal-pdf/register.js";
|
|
5
4
|
export default function seedNetworkExtension(pi) {
|
|
6
5
|
registerBranding(pi);
|
|
7
|
-
registerOnboarding(pi);
|
|
8
6
|
registerSubmitDealPdf(pi);
|
|
9
7
|
registerAgentAuthCommands(pi);
|
|
10
8
|
}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import { connectSeedAgent, DEFAULT_PROVIDER_URL, disconnectSeedAgents, listSeedAgentConnections,
|
|
2
|
-
import { OnboardingStore } from "../../store.js";
|
|
1
|
+
import { connectSeedAgent, DEFAULT_PROVIDER_URL, disconnectSeedAgents, listSeedAgentConnections, } from "../../seed-agent-auth.js";
|
|
3
2
|
export function registerAgentAuthCommands(pi) {
|
|
3
|
+
pi.registerCommand("connect", {
|
|
4
|
+
description: "Connect this local agent to Seed Network with Agent Auth",
|
|
5
|
+
handler: async (args, ctx) => {
|
|
6
|
+
if (ctx.hasUI === false) {
|
|
7
|
+
throw new Error("/connect requires interactive Pi UI.");
|
|
8
|
+
}
|
|
9
|
+
await connectSeedAgentCommand(args, ctx);
|
|
10
|
+
},
|
|
11
|
+
});
|
|
4
12
|
pi.registerCommand("seed", {
|
|
5
|
-
description: "Manage Seed Network Agent Auth:
|
|
13
|
+
description: "Manage Seed Network Agent Auth: status, disconnect",
|
|
6
14
|
getArgumentCompletions: completeSeedCommand,
|
|
7
15
|
handler: async (args, ctx) => {
|
|
8
16
|
if (ctx.hasUI === false) {
|
|
@@ -14,9 +22,7 @@ export function registerAgentAuthCommands(pi) {
|
|
|
14
22
|
}
|
|
15
23
|
function completeSeedCommand(prefix) {
|
|
16
24
|
const commands = [
|
|
17
|
-
{ value: "connect", label: "connect", description: "Connect this local agent to Seed Network" },
|
|
18
25
|
{ value: "status", label: "status", description: "Show saved Agent Auth connections and grants" },
|
|
19
|
-
{ value: "sync-onboarding", label: "sync-onboarding", description: "Upload the latest local onboarding submission" },
|
|
20
26
|
{ value: "disconnect", label: "disconnect", description: "Revoke and remove local Agent Auth connections" },
|
|
21
27
|
];
|
|
22
28
|
const firstToken = prefix.trimStart().split(/\s+/, 1)[0] ?? "";
|
|
@@ -24,19 +30,16 @@ function completeSeedCommand(prefix) {
|
|
|
24
30
|
return filtered.length > 0 ? filtered : null;
|
|
25
31
|
}
|
|
26
32
|
async function handleSeedCommand(args, ctx) {
|
|
27
|
-
const [subcommand = "status"
|
|
33
|
+
const [subcommand = "status"] = args.trim().split(/\s+/).filter(Boolean);
|
|
28
34
|
switch (subcommand) {
|
|
29
35
|
case "connect": {
|
|
30
|
-
|
|
31
|
-
ctx.ui.notify(`Connecting Seed Network Agent to ${providerUrl}…`, "info");
|
|
32
|
-
const result = await connectSeedAgent(providerUrl, ctx.ui);
|
|
33
|
-
ctx.ui.notify(`Seed Network Agent connected: ${result.agentId} (${result.status}).`, "info");
|
|
36
|
+
ctx.ui.notify("Use /connect [provider-url] to connect this local agent.", "warning");
|
|
34
37
|
return;
|
|
35
38
|
}
|
|
36
39
|
case "status": {
|
|
37
40
|
const connections = await listSeedAgentConnections();
|
|
38
41
|
if (connections.length === 0) {
|
|
39
|
-
ctx.ui.notify(`No Seed Network Agent Auth connection. Run /
|
|
42
|
+
ctx.ui.notify(`No Seed Network Agent Auth connection. Run /connect ${DEFAULT_PROVIDER_URL}`, "warning");
|
|
40
43
|
return;
|
|
41
44
|
}
|
|
42
45
|
const lines = connections.map((connection) => {
|
|
@@ -48,22 +51,18 @@ async function handleSeedCommand(args, ctx) {
|
|
|
48
51
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
49
52
|
return;
|
|
50
53
|
}
|
|
51
|
-
case "sync-onboarding": {
|
|
52
|
-
const submission = await new OnboardingStore().loadLatestSubmission();
|
|
53
|
-
if (!submission) {
|
|
54
|
-
ctx.ui.notify("No completed onboarding submission found. Run /onboarding first.", "warning");
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const result = await syncOnboardingSubmission(submission, ctx.ui);
|
|
58
|
-
ctx.ui.notify(`Seed Network onboarding synced: ${JSON.stringify(result.data ?? result)}`, "info");
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
54
|
case "disconnect": {
|
|
62
55
|
const count = await disconnectSeedAgents(ctx.ui);
|
|
63
56
|
ctx.ui.notify(`Disconnected ${count} Seed Network Agent connection${count === 1 ? "" : "s"}.`, "info");
|
|
64
57
|
return;
|
|
65
58
|
}
|
|
66
59
|
default:
|
|
67
|
-
ctx.ui.notify("Unknown /seed command. Use: /seed
|
|
60
|
+
ctx.ui.notify("Unknown /seed command. Use: /seed status, /seed disconnect, or /connect [provider-url]", "warning");
|
|
68
61
|
}
|
|
69
62
|
}
|
|
63
|
+
async function connectSeedAgentCommand(args, ctx) {
|
|
64
|
+
const providerUrl = args.trim().split(/\s+/, 1).filter(Boolean)[0] ?? DEFAULT_PROVIDER_URL;
|
|
65
|
+
ctx.ui.notify(`Connecting Seed Network Agent to ${providerUrl}…`, "info");
|
|
66
|
+
const result = await connectSeedAgent(providerUrl, ctx.ui);
|
|
67
|
+
ctx.ui.notify(`Seed Network Agent connected: ${result.agentId} (${result.status}).`, "info");
|
|
68
|
+
}
|
|
@@ -15,7 +15,7 @@ function installSeedBranding(ctx) {
|
|
|
15
15
|
"",
|
|
16
16
|
accent(rule),
|
|
17
17
|
`${accent("🌱 ")}${theme.bold("Seed Network Agent")}`,
|
|
18
|
-
dim("Run /
|
|
18
|
+
dim("Run /connect to connect to the seed network, or /submit-deal to submit a company."),
|
|
19
19
|
accent(rule),
|
|
20
20
|
"",
|
|
21
21
|
];
|
|
@@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { complete } from "@earendil-works/pi-ai";
|
|
5
|
-
import {
|
|
5
|
+
import { runQuestionnaireWizard } from "../../wizard.js";
|
|
6
6
|
import { submitDealSubmission } from "./pipeline.js";
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = path.dirname(__filename);
|
|
@@ -25,7 +25,7 @@ export function registerSubmitDealPdf(pi) {
|
|
|
25
25
|
}
|
|
26
26
|
async function startSubmitDeal(ctx) {
|
|
27
27
|
const questionnaire = await loadQuestionnaire(submitDealQuestionsPath);
|
|
28
|
-
const result = await
|
|
28
|
+
const result = await runQuestionnaireWizard(ctx.ui, questionnaire, {
|
|
29
29
|
startMessage: "Submit deal: starting deal intake wizard.",
|
|
30
30
|
reviewTitle: "Review deal submission",
|
|
31
31
|
reviewDescription: "Review these deal intake answers before creating the Seed Network deal.",
|
package/dist/seed-agent-auth.js
CHANGED
|
@@ -2,10 +2,9 @@ import { spawn } from "node:child_process";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import { AgentAuthClient } from "@auth/agent";
|
|
4
4
|
import { SeedAgentAuthStorage } from "./agent-auth-storage.js";
|
|
5
|
-
export const SEED_ONBOARDING_CAPABILITY = "submit_onboarding_answers";
|
|
6
5
|
export const SEED_SUBMIT_DEAL_CAPABILITY = "submit_deal";
|
|
7
6
|
export const DEFAULT_PROVIDER_URL = process.env.SEED_NETWORK_AGENT_AUTH_PROVIDER ?? "http://localhost:3000";
|
|
8
|
-
const DEFAULT_SEED_CAPABILITIES = [
|
|
7
|
+
const DEFAULT_SEED_CAPABILITIES = [SEED_SUBMIT_DEAL_CAPABILITY];
|
|
9
8
|
export function createSeedAgentAuthClient(ui) {
|
|
10
9
|
return new AgentAuthClient({
|
|
11
10
|
storage: new SeedAgentAuthStorage(),
|
|
@@ -32,7 +31,7 @@ export async function connectSeedAgent(providerUrl, ui) {
|
|
|
32
31
|
mode: "delegated",
|
|
33
32
|
name: "Seed Network Agent",
|
|
34
33
|
capabilities: DEFAULT_SEED_CAPABILITIES,
|
|
35
|
-
reason: "
|
|
34
|
+
reason: "Submit deal intake records to the connected Seed Network account.",
|
|
36
35
|
preferredMethod: "device_authorization",
|
|
37
36
|
bindingMessage: "Approve Seed Network Agent workspace access",
|
|
38
37
|
});
|
|
@@ -44,27 +43,9 @@ export async function connectSeedAgent(providerUrl, ui) {
|
|
|
44
43
|
export async function listSeedAgentConnections() {
|
|
45
44
|
return new SeedAgentAuthStorage().listAgentConnections();
|
|
46
45
|
}
|
|
47
|
-
export async function syncOnboardingSubmission(submission, ui) {
|
|
48
|
-
const client = createSeedAgentAuthClient(ui);
|
|
49
|
-
try {
|
|
50
|
-
const connection = await ensureActiveSeedCapability(client, SEED_ONBOARDING_CAPABILITY, ui, {
|
|
51
|
-
reason: "Sync local Seed Network Agent onboarding answers to the connected Seed Network account.",
|
|
52
|
-
bindingMessage: "Approve Seed Network Agent onboarding sync",
|
|
53
|
-
});
|
|
54
|
-
return await client.executeCapability({
|
|
55
|
-
agentId: connection.agentId,
|
|
56
|
-
capability: SEED_ONBOARDING_CAPABILITY,
|
|
57
|
-
arguments: submission,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
finally {
|
|
61
|
-
client.destroy();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
46
|
export async function getPreferredConnection() {
|
|
65
47
|
const connections = await listSeedAgentConnections();
|
|
66
|
-
return connections.find((connection) => hasActiveCapability(connection,
|
|
67
|
-
?? connections.find((connection) => hasActiveCapability(connection, SEED_SUBMIT_DEAL_CAPABILITY))
|
|
48
|
+
return connections.find((connection) => hasActiveCapability(connection, SEED_SUBMIT_DEAL_CAPABILITY))
|
|
68
49
|
?? connections[0];
|
|
69
50
|
}
|
|
70
51
|
export async function ensureActiveSeedCapability(client, capability, ui, approval) {
|
|
@@ -81,7 +62,7 @@ export async function ensureActiveSeedCapability(client, capability, ui, approva
|
|
|
81
62
|
return connection;
|
|
82
63
|
}
|
|
83
64
|
if (!connection) {
|
|
84
|
-
throw new Error("No Seed Network Agent Auth connection found. Run /
|
|
65
|
+
throw new Error("No Seed Network Agent Auth connection found. Run /connect first.");
|
|
85
66
|
}
|
|
86
67
|
// Refresh server-side grants before requesting. This recovers from a prior
|
|
87
68
|
// run where the user approved in the browser after the local command exited.
|
package/dist/wizard.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export async function
|
|
1
|
+
export async function runQuestionnaireWizard(ui, questionnaire, options = {}) {
|
|
2
2
|
validateQuestionnaire(questionnaire);
|
|
3
3
|
const answers = { ...(options.initialAnswers ?? {}) };
|
|
4
|
-
ui.notify?.(options.startMessage ?? `${questionnaire.title}: starting local
|
|
4
|
+
ui.notify?.(options.startMessage ?? `${questionnaire.title}: starting local questionnaire.`, "info");
|
|
5
5
|
for (const question of questionnaire.questions) {
|
|
6
6
|
const answer = await askQuestion(ui, question, answers[question.id]);
|
|
7
7
|
if (!answer) {
|
|
@@ -10,10 +10,10 @@ export async function runOnboardingWizard(ui, questionnaire, options = {}) {
|
|
|
10
10
|
answers[question.id] = answer;
|
|
11
11
|
await options.onAnswer?.(question, answer);
|
|
12
12
|
}
|
|
13
|
-
const confirmed = await ui.confirm(options.reviewTitle ?? "Review
|
|
13
|
+
const confirmed = await ui.confirm(options.reviewTitle ?? "Review answers", formatReview(questionnaire, answers, options));
|
|
14
14
|
if (!confirmed) {
|
|
15
15
|
if (!options.suppressReviewCancelledMessage) {
|
|
16
|
-
ui.notify?.(options.reviewCancelledMessage ?? "
|
|
16
|
+
ui.notify?.(options.reviewCancelledMessage ?? "Review cancelled.", "warning");
|
|
17
17
|
}
|
|
18
18
|
return { cancelled: true, answers };
|
|
19
19
|
}
|
|
@@ -84,14 +84,14 @@ function formatReview(questionnaire, answers, options = {}) {
|
|
|
84
84
|
const lines = [
|
|
85
85
|
options.reviewDescription
|
|
86
86
|
?? questionnaire.description
|
|
87
|
-
?? "Review your answers before saving the final
|
|
87
|
+
?? "Review your answers before saving the final submission.",
|
|
88
88
|
"",
|
|
89
89
|
];
|
|
90
90
|
for (const question of questionnaire.questions) {
|
|
91
91
|
const answer = answers[question.id];
|
|
92
92
|
lines.push(`${question.label ?? question.id}: ${answer ? displayAnswer(answer) : "(unanswered)"}`);
|
|
93
93
|
}
|
|
94
|
-
lines.push("", options.reviewConfirmPrompt ?? "Save this final submission
|
|
94
|
+
lines.push("", options.reviewConfirmPrompt ?? "Save this final submission?");
|
|
95
95
|
return lines.join("\n");
|
|
96
96
|
}
|
|
97
97
|
function validateQuestionnaire(questionnaire) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@connormartin/seed-network-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"README.md"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "rm -rf dist && tsc -p tsconfig.json && mkdir -p dist/features/
|
|
17
|
+
"build": "rm -rf dist && tsc -p tsconfig.json && mkdir -p dist/features/submit-deal-pdf && cp src/features/submit-deal-pdf/questionnaire.v1.json dist/features/submit-deal-pdf/questionnaire.v1.json && chmod +x dist/cli.js",
|
|
18
18
|
"dev": "tsx src/cli.ts",
|
|
19
19
|
"start": "node dist/cli.js",
|
|
20
20
|
"prepack": "npm run build"
|
package/prompts/SYSTEM.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
You are Seed Network Agent, a thin wrapper around upstream Pi.
|
|
2
2
|
|
|
3
|
-
The
|
|
3
|
+
The Seed Network is a private network of angel investors that collectively identify, diligence, and invest in early stage startups.
|
|
4
4
|
|
|
5
5
|
Keep the Seed-specific layer obvious:
|
|
6
6
|
- Use clear, direct language.
|
|
7
|
-
- Be explicit when a behavior is part of the Seed
|
|
8
|
-
-
|
|
7
|
+
- Be explicit when a behavior is part of the Seed Network wrapper.
|
|
8
|
+
- Use `/connect` to connect this local agent to Seed Network with Agent Auth.
|
|
9
9
|
- Use `/submit-deal` for the deterministic deal intake flow; it creates a deal only after the user reviews the answers and the Agent Auth capability is granted.
|
|
10
|
-
-
|
|
10
|
+
- Otherwise, behave like normal Pi unless the user asks for Seed-specific workflows.
|
|
11
11
|
|
|
12
|
-
The deterministic
|
|
12
|
+
The deterministic deal submission sequence is available through an extension command, not enforced by this prompt.
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "onboarding.v1",
|
|
3
|
-
"title": "Seed Network onboarding",
|
|
4
|
-
"description": "A short local-only onboarding flow for Seed Network Agent.",
|
|
5
|
-
"questions": [
|
|
6
|
-
{
|
|
7
|
-
"id": "name",
|
|
8
|
-
"label": "Name",
|
|
9
|
-
"type": "text",
|
|
10
|
-
"required": true,
|
|
11
|
-
"prompt": "What should Seed Network Agent call you?",
|
|
12
|
-
"placeholder": "Your name"
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"id": "role",
|
|
16
|
-
"label": "Role",
|
|
17
|
-
"type": "single_select",
|
|
18
|
-
"required": true,
|
|
19
|
-
"prompt": "Which role best describes you right now?",
|
|
20
|
-
"options": [
|
|
21
|
-
{
|
|
22
|
-
"value": "founder",
|
|
23
|
-
"label": "Founder"
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"value": "investor",
|
|
27
|
-
"label": "Investor"
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"value": "operator",
|
|
31
|
-
"label": "Operator"
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"value": "builder",
|
|
35
|
-
"label": "Builder"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"value": "other",
|
|
39
|
-
"label": "Other"
|
|
40
|
-
}
|
|
41
|
-
]
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"id": "primary_goal",
|
|
45
|
-
"label": "Goal",
|
|
46
|
-
"type": "text",
|
|
47
|
-
"required": true,
|
|
48
|
-
"prompt": "What is the main outcome you want from Seed Network Agent?",
|
|
49
|
-
"placeholder": "e.g. track deal flow, manage investor follow-ups, prepare intro requests"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"id": "workflow_focus",
|
|
53
|
-
"label": "Focus",
|
|
54
|
-
"type": "single_select",
|
|
55
|
-
"required": true,
|
|
56
|
-
"prompt": "Which workflow should we optimize around first?",
|
|
57
|
-
"options": [
|
|
58
|
-
{
|
|
59
|
-
"value": "telegram_triage",
|
|
60
|
-
"label": "Telegram triage"
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"value": "intro_requests",
|
|
64
|
-
"label": "Intro requests"
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
"value": "deal_intake",
|
|
68
|
-
"label": "Deal intake"
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
"value": "meeting_followups",
|
|
72
|
-
"label": "Meeting follow-ups"
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
"value": "other",
|
|
76
|
-
"label": "Something else"
|
|
77
|
-
}
|
|
78
|
-
]
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
"id": "notes",
|
|
82
|
-
"label": "Notes",
|
|
83
|
-
"type": "text",
|
|
84
|
-
"required": false,
|
|
85
|
-
"prompt": "Anything else we should know for this local onboarding pass?",
|
|
86
|
-
"placeholder": "Optional"
|
|
87
|
-
}
|
|
88
|
-
]
|
|
89
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { getPreferredConnection, syncOnboardingSubmission } from "../../seed-agent-auth.js";
|
|
5
|
-
import { OnboardingStore } from "../../store.js";
|
|
6
|
-
import { runOnboardingWizard } from "../../wizard.js";
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
-
const __dirname = path.dirname(__filename);
|
|
9
|
-
const onboardingQuestionsPath = path.join(__dirname, "questionnaire.v1.json");
|
|
10
|
-
let autoStarted = false;
|
|
11
|
-
export function registerOnboarding(pi) {
|
|
12
|
-
pi.on("session_start", async (event, ctx) => {
|
|
13
|
-
if (event.reason !== "startup")
|
|
14
|
-
return;
|
|
15
|
-
if (process.env.SEED_NETWORK_AGENT_AUTOSTART_ONBOARDING !== "true")
|
|
16
|
-
return;
|
|
17
|
-
if (ctx.hasUI === false)
|
|
18
|
-
return;
|
|
19
|
-
if (autoStarted)
|
|
20
|
-
return;
|
|
21
|
-
autoStarted = true;
|
|
22
|
-
await startOnboarding(ctx, "auto");
|
|
23
|
-
});
|
|
24
|
-
pi.registerCommand("onboarding", {
|
|
25
|
-
description: "Start the Seed Network deterministic local onboarding wizard",
|
|
26
|
-
handler: async (_args, ctx) => {
|
|
27
|
-
if (ctx.hasUI === false) {
|
|
28
|
-
throw new Error("/onboarding requires interactive Pi UI.");
|
|
29
|
-
}
|
|
30
|
-
await startOnboarding(ctx, "manual");
|
|
31
|
-
},
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
async function startOnboarding(ctx, source) {
|
|
35
|
-
const questionnaire = await loadQuestionnaire();
|
|
36
|
-
const store = new OnboardingStore();
|
|
37
|
-
const progress = await store.loadProgress();
|
|
38
|
-
let initialAnswers;
|
|
39
|
-
if (progress?.questionnaireVersion === questionnaire.version) {
|
|
40
|
-
const answerCount = Object.keys(progress.answers).length;
|
|
41
|
-
const resume = await ctx.ui.confirm("Resume onboarding?", `Found ${answerCount} saved answer${answerCount === 1 ? "" : "s"} from ${progress.updatedAt}.\n\nResume from local progress?`);
|
|
42
|
-
if (resume)
|
|
43
|
-
initialAnswers = progress.answers;
|
|
44
|
-
}
|
|
45
|
-
const result = await runOnboardingWizard(ctx.ui, questionnaire, {
|
|
46
|
-
initialAnswers,
|
|
47
|
-
onAnswer: (question, answer) => store.recordAnswer(questionnaire.version, question, answer),
|
|
48
|
-
});
|
|
49
|
-
if (result.cancelled || !result.submission) {
|
|
50
|
-
const prefix = source === "auto" ? "Startup onboarding" : "Seed Network onboarding";
|
|
51
|
-
ctx.ui.notify(`${prefix} cancelled. Progress is saved locally.`, "warning");
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
const submissionPath = await store.saveSubmission(result.submission);
|
|
55
|
-
ctx.ui.notify(`Seed Network onboarding saved locally: ${submissionPath}`, "info");
|
|
56
|
-
const connection = await getPreferredConnection();
|
|
57
|
-
if (!connection) {
|
|
58
|
-
ctx.ui.notify("Run /seed connect to sync this onboarding to Seed Network when ready.", "info");
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const shouldSync = await ctx.ui.confirm("Sync onboarding?", "Seed Network Agent is connected. Upload this local onboarding submission to your Seed Network account now?");
|
|
62
|
-
if (!shouldSync)
|
|
63
|
-
return;
|
|
64
|
-
try {
|
|
65
|
-
await syncOnboardingSubmission(result.submission, ctx.ui);
|
|
66
|
-
ctx.ui.notify("Seed Network onboarding synced.", "info");
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
ctx.ui.notify(`Seed Network onboarding sync failed: ${errorMessage(error)}`, "error");
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
async function loadQuestionnaire(filePath = onboardingQuestionsPath) {
|
|
73
|
-
const raw = await readFile(filePath, "utf8");
|
|
74
|
-
return JSON.parse(raw);
|
|
75
|
-
}
|
|
76
|
-
function errorMessage(error) {
|
|
77
|
-
return error instanceof Error ? error.message : String(error);
|
|
78
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from "./extension/index.js";
|
package/dist/store.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
export class OnboardingStore {
|
|
5
|
-
onboardingDir;
|
|
6
|
-
progressPath;
|
|
7
|
-
submissionsDir;
|
|
8
|
-
constructor(baseDir = path.join(os.homedir(), ".seed-network-agent")) {
|
|
9
|
-
this.onboardingDir = path.join(baseDir, "onboarding");
|
|
10
|
-
this.progressPath = path.join(this.onboardingDir, "progress.json");
|
|
11
|
-
this.submissionsDir = path.join(this.onboardingDir, "submissions");
|
|
12
|
-
}
|
|
13
|
-
async loadProgress() {
|
|
14
|
-
try {
|
|
15
|
-
const raw = await readFile(this.progressPath, "utf8");
|
|
16
|
-
const parsed = JSON.parse(raw);
|
|
17
|
-
if (!parsed.answers || parsed.completedAt)
|
|
18
|
-
return undefined;
|
|
19
|
-
return parsed;
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
if (isNodeError(error) && error.code === "ENOENT")
|
|
23
|
-
return undefined;
|
|
24
|
-
throw error;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
async recordAnswer(questionnaireVersion, question, answer) {
|
|
28
|
-
const existing = await this.loadProgress();
|
|
29
|
-
const now = new Date().toISOString();
|
|
30
|
-
const progress = {
|
|
31
|
-
questionnaireVersion,
|
|
32
|
-
startedAt: existing?.startedAt ?? now,
|
|
33
|
-
updatedAt: now,
|
|
34
|
-
answers: {
|
|
35
|
-
...(existing?.answers ?? {}),
|
|
36
|
-
[question.id]: answer,
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
await this.writeProgress(progress);
|
|
40
|
-
}
|
|
41
|
-
async saveSubmission(submission) {
|
|
42
|
-
await mkdir(this.submissionsDir, { recursive: true });
|
|
43
|
-
const fileName = `${safeTimestamp(submission.submittedAt)}.json`;
|
|
44
|
-
const submissionPath = path.join(this.submissionsDir, fileName);
|
|
45
|
-
await writeJsonAtomic(submissionPath, submission);
|
|
46
|
-
const progress = await this.loadProgress();
|
|
47
|
-
if (progress) {
|
|
48
|
-
await this.writeProgress({
|
|
49
|
-
...progress,
|
|
50
|
-
updatedAt: submission.submittedAt,
|
|
51
|
-
completedAt: submission.submittedAt,
|
|
52
|
-
answers: submission.answers,
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
return submissionPath;
|
|
56
|
-
}
|
|
57
|
-
async loadLatestSubmission() {
|
|
58
|
-
try {
|
|
59
|
-
const entries = await readdir(this.submissionsDir, { withFileTypes: true });
|
|
60
|
-
const fileNames = entries
|
|
61
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
62
|
-
.map((entry) => entry.name)
|
|
63
|
-
.sort()
|
|
64
|
-
.reverse();
|
|
65
|
-
for (const fileName of fileNames) {
|
|
66
|
-
const raw = await readFile(path.join(this.submissionsDir, fileName), "utf8");
|
|
67
|
-
return JSON.parse(raw);
|
|
68
|
-
}
|
|
69
|
-
return undefined;
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
if (isNodeError(error) && error.code === "ENOENT")
|
|
73
|
-
return undefined;
|
|
74
|
-
throw error;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
async writeProgress(progress) {
|
|
78
|
-
await mkdir(this.onboardingDir, { recursive: true });
|
|
79
|
-
await writeJsonAtomic(this.progressPath, progress);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
async function writeJsonAtomic(filePath, value) {
|
|
83
|
-
const tmpPath = `${filePath}.${process.pid}.tmp`;
|
|
84
|
-
await writeFile(tmpPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
85
|
-
await rename(tmpPath, filePath);
|
|
86
|
-
}
|
|
87
|
-
function safeTimestamp(value) {
|
|
88
|
-
return value.replace(/[:.]/g, "-");
|
|
89
|
-
}
|
|
90
|
-
function isNodeError(error) {
|
|
91
|
-
return typeof error === "object" && error !== null && "code" in error;
|
|
92
|
-
}
|