@clubnet/seedclub 0.2.27 → 0.2.29
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 +22 -17
- package/assets/extensions/seedclub/auth.ts +6 -21
- package/assets/extensions/seedclub/commands/seedclub.ts +14 -5
- package/assets/extensions/seedclub/gate-state.ts +85 -0
- package/assets/extensions/seedclub/index.ts +503 -59
- package/assets/extensions/seedclub/tools/meetings.ts +305 -1
- package/assets/extensions/seedclub-ui/welcome.ts +133 -82
- package/bin/cli.js +2 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -30,9 +30,10 @@ seedclub
|
|
|
30
30
|
|
|
31
31
|
On a fresh install, the normal setup flow is:
|
|
32
32
|
|
|
33
|
-
1.
|
|
34
|
-
2.
|
|
35
|
-
3. `/
|
|
33
|
+
1. Run `seedclub`
|
|
34
|
+
2. Seed Club auth opens automatically
|
|
35
|
+
3. `/login` to sign in to a model provider such as Anthropic, OpenAI, or Gemini
|
|
36
|
+
4. `/model` to choose the model you want to use
|
|
36
37
|
|
|
37
38
|
After that, run `/seedclub` to open the main Seed Club menu.
|
|
38
39
|
|
|
@@ -42,24 +43,19 @@ After that, run `/seedclub` to open the main Seed Club menu.
|
|
|
42
43
|
curl -fsSL https://raw.githubusercontent.com/seedclub/seedclub-agent/main/install.sh | bash
|
|
43
44
|
```
|
|
44
45
|
|
|
45
|
-
###
|
|
46
|
+
### Package access
|
|
46
47
|
|
|
47
|
-
`@clubnet/seedclub` is
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
npm login
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Then `npm install -g @clubnet/seedclub` and `seedclub update` work.
|
|
48
|
+
`@clubnet/seedclub` is a public npm package. Install access is open; runtime access is enforced by Seed Club auth inside the app.
|
|
54
49
|
|
|
55
50
|
## Core workflow
|
|
56
51
|
|
|
57
52
|
The normal interactive flow is:
|
|
58
53
|
|
|
59
54
|
1. Start the app with `seedclub`
|
|
60
|
-
2. Complete
|
|
61
|
-
3.
|
|
62
|
-
4.
|
|
55
|
+
2. Complete Seed Club auth when the browser opens
|
|
56
|
+
3. Complete `/login` and `/model` if this is your first run
|
|
57
|
+
4. Open `/seedclub`
|
|
58
|
+
5. Choose the CRM, meetings, media, recordings, or transcript workflow you need
|
|
63
59
|
|
|
64
60
|
## Commands
|
|
65
61
|
|
|
@@ -68,6 +64,7 @@ The normal interactive flow is:
|
|
|
68
64
|
| `/login` | Sign in to a model provider for the underlying agent |
|
|
69
65
|
| `/model` | Choose which model to use |
|
|
70
66
|
| `/connect` | Connect your Seed Club account |
|
|
67
|
+
| `/connect-calendar` | Connect a personal Google Calendar to your Seed Club account |
|
|
71
68
|
| `/seedclub` | Main menu — connect, inspect access, and jump into CRM/meetings/media/headlines workflows |
|
|
72
69
|
| `/transcripts` | Export transcript VTT files with filters (date, person, time, output dir) |
|
|
73
70
|
|
|
@@ -77,13 +74,21 @@ Natural-language transcript retrieval is also supported (no slash command requir
|
|
|
77
74
|
|
|
78
75
|
There are two separate auth layers in the product:
|
|
79
76
|
|
|
80
|
-
1.
|
|
81
|
-
This signs you into the LLM provider you want the agent to use.
|
|
82
|
-
2. Seed Club auth: `/connect`
|
|
77
|
+
1. Seed Club auth: auto-start on launch or `/connect`
|
|
83
78
|
This connects the CLI to your Seed Club account so Seed Club tools and commands can read and write account data.
|
|
79
|
+
2. Model auth: `/login`
|
|
80
|
+
This signs you into the LLM provider you want the agent to use.
|
|
81
|
+
3. Personal calendar connect: `/connect-calendar`
|
|
82
|
+
This connects a Google Calendar to your Seed Club account for booking and availability workflows. It is only needed if you want the agent to schedule using your personal calendar.
|
|
83
|
+
|
|
84
|
+
Seed Club auth is the first gate. Until it succeeds, normal harness usage is blocked and only the setup commands are available.
|
|
84
85
|
|
|
85
86
|
`/seedclub` is the main entry point for Seed Club actions. If you are not connected yet, it will start the Seed Club connect flow automatically.
|
|
86
87
|
|
|
88
|
+
CLI auth now prefers an authorization-code exchange:
|
|
89
|
+
- browser callback: `http://127.0.0.1:<port>/callback?code=...&state=...`
|
|
90
|
+
- CLI exchange: `POST ${SEEDCLUB_API_URL}/auth/cli/exchange` with `{ code, state }`
|
|
91
|
+
|
|
87
92
|
Power-user env overrides:
|
|
88
93
|
|
|
89
94
|
```bash
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Token storage for Seed Club.
|
|
3
3
|
*
|
|
4
|
-
* Priority: SEEDCLUB_ACCESS_TOKEN
|
|
4
|
+
* Priority: SEEDCLUB_ACCESS_TOKEN env var > stored token file.
|
|
5
5
|
* Use /seedclub to connect.
|
|
6
6
|
*/
|
|
7
7
|
|
|
@@ -76,12 +76,7 @@ function tryReadStoredBasesSync(): StoredBases | null {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
export function getApiBase(): string {
|
|
79
|
-
if (
|
|
80
|
-
process.env.SEEDCLUB_API_URL ||
|
|
81
|
-
process.env.SEEDCLUB_API ||
|
|
82
|
-
process.env.SEED_NETWORK_API
|
|
83
|
-
)
|
|
84
|
-
return process.env.SEEDCLUB_API_URL || process.env.SEEDCLUB_API || process.env.SEED_NETWORK_API!;
|
|
79
|
+
if (process.env.SEEDCLUB_API_URL) return process.env.SEEDCLUB_API_URL;
|
|
85
80
|
if (shouldPreferLocalBases()) return LOCAL_API_BASE;
|
|
86
81
|
const storedBases = tryReadStoredBasesSync();
|
|
87
82
|
if (storedBases?.apiBase) return storedBases.apiBase;
|
|
@@ -90,12 +85,7 @@ export function getApiBase(): string {
|
|
|
90
85
|
}
|
|
91
86
|
|
|
92
87
|
export function getAuthBase(): string {
|
|
93
|
-
if (
|
|
94
|
-
process.env.SEEDCLUB_AUTH_URL ||
|
|
95
|
-
process.env.SEEDCLUB_AUTH ||
|
|
96
|
-
process.env.SEED_NETWORK_AUTH
|
|
97
|
-
)
|
|
98
|
-
return process.env.SEEDCLUB_AUTH_URL || process.env.SEEDCLUB_AUTH || process.env.SEED_NETWORK_AUTH!;
|
|
88
|
+
if (process.env.SEEDCLUB_AUTH_URL) return process.env.SEEDCLUB_AUTH_URL;
|
|
99
89
|
if (shouldPreferLocalBases()) return LOCAL_AUTH_BASE;
|
|
100
90
|
const storedBases = tryReadStoredBasesSync();
|
|
101
91
|
if (storedBases?.authBase) return storedBases.authBase;
|
|
@@ -141,18 +131,14 @@ async function tryReadTokenFile(path: string): Promise<StoredToken | null> {
|
|
|
141
131
|
if (
|
|
142
132
|
stored.apiBase &&
|
|
143
133
|
!shouldPreferLocalBases() &&
|
|
144
|
-
!process.env.SEEDCLUB_API_URL
|
|
145
|
-
!process.env.SEEDCLUB_API &&
|
|
146
|
-
!process.env.SEED_NETWORK_API
|
|
134
|
+
!process.env.SEEDCLUB_API_URL
|
|
147
135
|
) {
|
|
148
136
|
_cachedApiBase = stored.apiBase;
|
|
149
137
|
}
|
|
150
138
|
if (
|
|
151
139
|
stored.authBase &&
|
|
152
140
|
!shouldPreferLocalBases() &&
|
|
153
|
-
!process.env.SEEDCLUB_AUTH_URL
|
|
154
|
-
!process.env.SEEDCLUB_AUTH &&
|
|
155
|
-
!process.env.SEED_NETWORK_AUTH
|
|
141
|
+
!process.env.SEEDCLUB_AUTH_URL
|
|
156
142
|
) {
|
|
157
143
|
_cachedAuthBase = stored.authBase;
|
|
158
144
|
}
|
|
@@ -189,8 +175,7 @@ export async function getStoredBases(): Promise<StoredBases | null> {
|
|
|
189
175
|
}
|
|
190
176
|
|
|
191
177
|
export async function getToken(): Promise<string | null> {
|
|
192
|
-
if (process.env.SEEDCLUB_ACCESS_TOKEN
|
|
193
|
-
return (process.env.SEEDCLUB_ACCESS_TOKEN || process.env.SEEDCLUB_TOKEN || process.env.SEED_NETWORK_TOKEN)!;
|
|
178
|
+
if (process.env.SEEDCLUB_ACCESS_TOKEN) return process.env.SEEDCLUB_ACCESS_TOKEN;
|
|
194
179
|
const stored = await getStoredToken();
|
|
195
180
|
return stored?.token ?? null;
|
|
196
181
|
}
|
|
@@ -15,7 +15,8 @@ import {
|
|
|
15
15
|
import { getCurrentUser, getSessionContext } from "../tools/utility.js";
|
|
16
16
|
|
|
17
17
|
interface SeedclubDeps {
|
|
18
|
-
connect: (args: string | undefined, ctx: any) => Promise<
|
|
18
|
+
connect: (args: string | undefined, ctx: any) => Promise<boolean>;
|
|
19
|
+
connectCalendar: (ctx: any) => Promise<void>;
|
|
19
20
|
disconnect: (ctx: any) => Promise<void>;
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -64,6 +65,13 @@ export function registerSeedclubCommand(pi: ExtensionAPI, deps: SeedclubDeps) {
|
|
|
64
65
|
},
|
|
65
66
|
});
|
|
66
67
|
|
|
68
|
+
pi.registerCommand("connect-calendar", {
|
|
69
|
+
description: "Connect a personal Google Calendar to your Seed Club account",
|
|
70
|
+
handler: async (_args, ctx) => {
|
|
71
|
+
await deps.connectCalendar(ctx);
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
67
75
|
pi.registerCommand("clearcontext", {
|
|
68
76
|
description: "Compact the current conversation to free context while keeping Seed Club state",
|
|
69
77
|
handler: async (_args, ctx) => {
|
|
@@ -101,10 +109,7 @@ export function registerSeedclubCommand(pi: ExtensionAPI, deps: SeedclubDeps) {
|
|
|
101
109
|
description: "Seed Club",
|
|
102
110
|
handler: async (args, ctx) => {
|
|
103
111
|
const stored = await getStoredToken();
|
|
104
|
-
const hasEnvToken =
|
|
105
|
-
!!process.env.SEEDCLUB_ACCESS_TOKEN ||
|
|
106
|
-
!!process.env.SEEDCLUB_TOKEN ||
|
|
107
|
-
!!process.env.SEED_NETWORK_TOKEN;
|
|
112
|
+
const hasEnvToken = !!process.env.SEEDCLUB_ACCESS_TOKEN;
|
|
108
113
|
const isConnected = !!stored || hasEnvToken;
|
|
109
114
|
|
|
110
115
|
if (!isConnected) {
|
|
@@ -129,6 +134,7 @@ export function registerSeedclubCommand(pi: ExtensionAPI, deps: SeedclubDeps) {
|
|
|
129
134
|
"Show API/Auth environment",
|
|
130
135
|
"Use local API/Auth",
|
|
131
136
|
"Use prod API/Auth",
|
|
137
|
+
"Connect personal calendar",
|
|
132
138
|
"Open CRM prompt",
|
|
133
139
|
"Open meetings prompt",
|
|
134
140
|
"Open transcripts prompt",
|
|
@@ -168,6 +174,9 @@ export function registerSeedclubCommand(pi: ExtensionAPI, deps: SeedclubDeps) {
|
|
|
168
174
|
case "Use prod API/Auth":
|
|
169
175
|
await setSeedEnvironment("prod", ctx);
|
|
170
176
|
break;
|
|
177
|
+
case "Connect personal calendar":
|
|
178
|
+
await deps.connectCalendar(ctx);
|
|
179
|
+
break;
|
|
171
180
|
case "Open CRM prompt":
|
|
172
181
|
await new Promise((r) => setTimeout(r, 300));
|
|
173
182
|
ctx.ui.setEditorText("List CRM records for my workspace.");
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export type SeedclubAuthGateStatus = "auth_required" | "auth_in_progress" | "auth_complete";
|
|
2
|
+
|
|
3
|
+
export interface SeedclubAuthGateState {
|
|
4
|
+
status: SeedclubAuthGateStatus;
|
|
5
|
+
authUrl: string | null;
|
|
6
|
+
message: string | null;
|
|
7
|
+
error: string | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const AUTH_GATE_ALLOWED_COMMANDS = new Set([
|
|
11
|
+
"connect",
|
|
12
|
+
"seedclub",
|
|
13
|
+
"seedenv",
|
|
14
|
+
"commands",
|
|
15
|
+
"extensions",
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const state: SeedclubAuthGateState = {
|
|
19
|
+
status: "auth_required",
|
|
20
|
+
authUrl: null,
|
|
21
|
+
message: "Seed Club sign-in is required before /login or /model.",
|
|
22
|
+
error: null,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const listeners = new Set<() => void>();
|
|
26
|
+
|
|
27
|
+
function emit() {
|
|
28
|
+
for (const listener of listeners) listener();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function setState(next: Partial<SeedclubAuthGateState>) {
|
|
32
|
+
Object.assign(state, next);
|
|
33
|
+
emit();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getAuthGateState(): SeedclubAuthGateState {
|
|
37
|
+
return { ...state };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function subscribeToAuthGate(listener: () => void): () => void {
|
|
41
|
+
listeners.add(listener);
|
|
42
|
+
return () => listeners.delete(listener);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function markAuthRequired(options?: { authUrl?: string | null; message?: string | null; error?: string | null }) {
|
|
46
|
+
setState({
|
|
47
|
+
status: "auth_required",
|
|
48
|
+
authUrl: options?.authUrl ?? state.authUrl,
|
|
49
|
+
message: options?.message ?? "Seed Club sign-in is required before /login or /model.",
|
|
50
|
+
error: options?.error ?? null,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function markAuthInProgress(options?: { authUrl?: string | null; message?: string | null; error?: string | null }) {
|
|
55
|
+
setState({
|
|
56
|
+
status: "auth_in_progress",
|
|
57
|
+
authUrl: options?.authUrl ?? state.authUrl,
|
|
58
|
+
message: options?.message ?? "Opening your browser for Seed Club sign-in.",
|
|
59
|
+
error: options?.error ?? null,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function markAuthComplete(message?: string | null) {
|
|
64
|
+
setState({
|
|
65
|
+
status: "auth_complete",
|
|
66
|
+
authUrl: null,
|
|
67
|
+
message: message ?? null,
|
|
68
|
+
error: null,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function isAuthGateBlocking(): boolean {
|
|
73
|
+
return state.status !== "auth_complete";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getCommandName(text: string): string | null {
|
|
77
|
+
const trimmed = text.trim();
|
|
78
|
+
if (!trimmed.startsWith("/")) return null;
|
|
79
|
+
const token = trimmed.slice(1).split(/\s+/).find(Boolean);
|
|
80
|
+
return token ? token.toLowerCase() : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isAllowedDuringAuthGate(commandName: string | null): boolean {
|
|
84
|
+
return !!commandName && AUTH_GATE_ALLOWED_COMMANDS.has(commandName);
|
|
85
|
+
}
|