@connormartin/seed-network-agent 0.1.0 → 0.1.2

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
@@ -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/onboarding` — `/onboarding` command and onboarding questionnaire.
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. Add a deterministic onboarding wizard.
38
- 4. Load versioned feature questionnaires from each feature directory.
39
- 5. Save onboarding answers locally through `src/store.ts`.
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 `/onboarding` inside the agent to start the onboarding wizard.
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
- /seed connect [provider-url]
121
+ /connect [provider-url]
103
122
  /seed status
104
- /seed sync-onboarding
105
123
  /seed disconnect
106
124
  ```
107
125
 
@@ -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, syncOnboardingSubmission, } from "../../seed-agent-auth.js";
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: connect, status, sync-onboarding, disconnect",
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", ...rest] = args.trim().split(/\s+/).filter(Boolean);
33
+ const [subcommand = "status"] = args.trim().split(/\s+/).filter(Boolean);
28
34
  switch (subcommand) {
29
35
  case "connect": {
30
- const providerUrl = rest[0] ?? DEFAULT_PROVIDER_URL;
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 /seed connect ${DEFAULT_PROVIDER_URL}`, "warning");
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 connect [provider-url], /seed status, /seed sync-onboarding, /seed disconnect", "warning");
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 /onboarding to start onboarding, or /submit-deal to submit a company."),
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 { runOnboardingWizard } from "../../wizard.js";
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 runOnboardingWizard(ctx.ui, questionnaire, {
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.",
@@ -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 = [SEED_ONBOARDING_CAPABILITY, SEED_SUBMIT_DEAL_CAPABILITY];
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: "Sync onboarding answers and submit deal intake records to the connected Seed Network account.",
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, SEED_ONBOARDING_CAPABILITY))
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 /seed connect first.");
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 runOnboardingWizard(ui, questionnaire, options = {}) {
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 onboarding.`, "info");
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 onboarding answers", formatReview(questionnaire, answers, options));
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 ?? "Onboarding review cancelled. Progress remains saved locally.", "warning");
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 local submission.",
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 locally?");
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.0",
3
+ "version": "0.1.2",
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/onboarding dist/features/submit-deal-pdf && cp src/features/onboarding/questionnaire.v1.json dist/features/onboarding/questionnaire.v1.json && cp src/features/submit-deal-pdf/questionnaire.v1.json dist/features/submit-deal-pdf/questionnaire.v1.json && chmod +x dist/cli.js",
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 seed network is a private network of angel investors that collectively identify, diligence, and invest in early stage startups.
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 onboarding wrapper.
8
- - Be explicit that onboarding answers stay local unless the user connects Agent Auth and approves syncing.
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
- - After onboarding, behave like normal Pi unless the user asks for Seed-specific workflows.
10
+ - Otherwise, behave like normal Pi unless the user asks for Seed-specific workflows.
11
11
 
12
- The deterministic onboarding and deal submission sequences are available through extension commands, not enforced by this prompt.
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
- }