@clubnet/seedclub 0.2.32 → 0.2.33
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/assets/extensions/seedclub/auth.ts +21 -21
- package/assets/extensions/seedclub/commands/seedclub.ts +5 -4
- package/assets/extensions/seedclub/index.ts +2 -4
- package/assets/extensions/seedclub/tool-utils.ts +15 -11
- package/assets/extensions/seedclub/tools/meetings.ts +50 -1
- package/assets/extensions/seedclub/ui-copy.ts +3 -21
- package/package.json +1 -1
|
@@ -20,8 +20,6 @@ const DEFAULT_AUTH_BASE = "https://auth.seedclub.com";
|
|
|
20
20
|
const LOCAL_API_BASE = "http://localhost:3001";
|
|
21
21
|
const LOCAL_AUTH_BASE = "http://localhost:3000";
|
|
22
22
|
|
|
23
|
-
export type SeedclubEnvironmentMode = "local" | "prod" | "custom";
|
|
24
|
-
|
|
25
23
|
export interface StoredToken {
|
|
26
24
|
token: string;
|
|
27
25
|
email: string;
|
|
@@ -34,8 +32,6 @@ export interface StoredToken {
|
|
|
34
32
|
export interface StoredBases {
|
|
35
33
|
apiBase: string;
|
|
36
34
|
authBase: string;
|
|
37
|
-
mode: SeedclubEnvironmentMode;
|
|
38
|
-
updatedAt: string;
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
let _cachedApiBase: string | null = null;
|
|
@@ -50,6 +46,23 @@ function shouldPreferLocalBases(): boolean {
|
|
|
50
46
|
);
|
|
51
47
|
}
|
|
52
48
|
|
|
49
|
+
function normalizeStoredBases(value: unknown): StoredBases | null {
|
|
50
|
+
if (!value || typeof value !== "object") {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const stored = value as Partial<StoredBases>;
|
|
55
|
+
|
|
56
|
+
if (typeof stored.apiBase !== "string" || typeof stored.authBase !== "string") {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
apiBase: stored.apiBase,
|
|
62
|
+
authBase: stored.authBase,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
function tryReadStoredBasesSync(): StoredBases | null {
|
|
54
67
|
if (_cachedBases !== undefined) {
|
|
55
68
|
return _cachedBases;
|
|
@@ -57,13 +70,8 @@ function tryReadStoredBasesSync(): StoredBases | null {
|
|
|
57
70
|
|
|
58
71
|
try {
|
|
59
72
|
const content = readFileSync(BASES_FILE, "utf-8");
|
|
60
|
-
const stored = JSON.parse(content)
|
|
61
|
-
if (
|
|
62
|
-
!stored ||
|
|
63
|
-
typeof stored.apiBase !== "string" ||
|
|
64
|
-
typeof stored.authBase !== "string" ||
|
|
65
|
-
typeof stored.mode !== "string"
|
|
66
|
-
) {
|
|
73
|
+
const stored = normalizeStoredBases(JSON.parse(content));
|
|
74
|
+
if (!stored) {
|
|
67
75
|
_cachedBases = null;
|
|
68
76
|
return null;
|
|
69
77
|
}
|
|
@@ -158,13 +166,8 @@ export async function getStoredToken(): Promise<StoredToken | null> {
|
|
|
158
166
|
export async function getStoredBases(): Promise<StoredBases | null> {
|
|
159
167
|
try {
|
|
160
168
|
const content = await readFile(BASES_FILE, "utf-8");
|
|
161
|
-
const stored = JSON.parse(content)
|
|
162
|
-
if (
|
|
163
|
-
!stored ||
|
|
164
|
-
typeof stored.apiBase !== "string" ||
|
|
165
|
-
typeof stored.authBase !== "string" ||
|
|
166
|
-
typeof stored.mode !== "string"
|
|
167
|
-
) {
|
|
169
|
+
const stored = normalizeStoredBases(JSON.parse(content));
|
|
170
|
+
if (!stored) {
|
|
168
171
|
return null;
|
|
169
172
|
}
|
|
170
173
|
_cachedBases = stored;
|
|
@@ -183,13 +186,10 @@ export async function getToken(): Promise<string | null> {
|
|
|
183
186
|
export async function storeBases(
|
|
184
187
|
apiBase: string,
|
|
185
188
|
authBase: string,
|
|
186
|
-
mode: SeedclubEnvironmentMode = "custom",
|
|
187
189
|
): Promise<void> {
|
|
188
190
|
const stored: StoredBases = {
|
|
189
191
|
apiBase,
|
|
190
192
|
authBase,
|
|
191
|
-
mode,
|
|
192
|
-
updatedAt: new Date().toISOString(),
|
|
193
193
|
};
|
|
194
194
|
_cachedApiBase = apiBase;
|
|
195
195
|
_cachedAuthBase = authBase;
|
|
@@ -41,7 +41,6 @@ async function showSeedEnvironment(ctx: any) {
|
|
|
41
41
|
const lines = [
|
|
42
42
|
`effective api: ${getApiBase()}`,
|
|
43
43
|
`effective auth: ${getAuthBase()}`,
|
|
44
|
-
`stored mode: ${storedBases?.mode ?? "none"}`,
|
|
45
44
|
`stored api: ${storedBases?.apiBase ?? "none"}`,
|
|
46
45
|
`stored auth: ${storedBases?.authBase ?? "none"}`,
|
|
47
46
|
];
|
|
@@ -50,7 +49,7 @@ async function showSeedEnvironment(ctx: any) {
|
|
|
50
49
|
|
|
51
50
|
async function setSeedEnvironment(mode: "local" | "prod", ctx: any) {
|
|
52
51
|
const bases = getDefaultBases(mode);
|
|
53
|
-
await storeBases(bases.apiBase, bases.authBase
|
|
52
|
+
await storeBases(bases.apiBase, bases.authBase);
|
|
54
53
|
ctx.ui.notify(
|
|
55
54
|
`Seed Club environment set to ${mode}.\napi: ${bases.apiBase}\nauth: ${bases.authBase}\nReconnect if your current token belongs to a different environment.`,
|
|
56
55
|
"info",
|
|
@@ -113,13 +112,15 @@ export function registerSeedclubCommand(pi: ExtensionAPI, deps: SeedclubDeps) {
|
|
|
113
112
|
const isConnected = !!stored || hasEnvToken;
|
|
114
113
|
|
|
115
114
|
if (!isConnected) {
|
|
116
|
-
|
|
115
|
+
await deps.connect(args, ctx);
|
|
116
|
+
return;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
119
|
const user = await getCurrentUser();
|
|
120
120
|
if ("error" in user) {
|
|
121
121
|
ctx.ui.notify("Session expired. Reconnecting...", "info");
|
|
122
|
-
|
|
122
|
+
await deps.connect(args, ctx);
|
|
123
|
+
return;
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
const session = await getSessionContext();
|
|
@@ -9,7 +9,7 @@ import { randomBytes } from "node:crypto";
|
|
|
9
9
|
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
|
|
10
10
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
11
11
|
import { api, clearCredentials, setCachedToken } from "./api-client.js";
|
|
12
|
-
import { getApiBase, getAuthBase,
|
|
12
|
+
import { getApiBase, getAuthBase, getStoredToken, storeToken } from "./auth.js";
|
|
13
13
|
import { renderCallbackPage } from "./browser-pages.js";
|
|
14
14
|
import { registerSeedclubCommand } from "./commands/seedclub.js";
|
|
15
15
|
import { registerTranscriptsCommand } from "./commands/transcripts.js";
|
|
@@ -93,7 +93,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
async function applyConnectedStatus(ctx: any, user: { name?: string | null; email?: string | null }) {
|
|
96
|
-
const storedBases = await getStoredBases();
|
|
97
96
|
const effectiveApiBase = getApiBase();
|
|
98
97
|
const effectiveAuthBase = getAuthBase();
|
|
99
98
|
const isDev = effectiveApiBase.includes("localhost") || effectiveApiBase.includes("127.0.0.1");
|
|
@@ -102,8 +101,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
102
101
|
(effectiveAuthBase.includes("localhost") || effectiveAuthBase.includes("127.0.0.1"));
|
|
103
102
|
|
|
104
103
|
ctx.ui.setStatus("seed", formatSeedLabel(user.name ?? undefined, user.email ?? undefined));
|
|
105
|
-
|
|
106
|
-
else ctx.ui.setStatus("seed-env", undefined);
|
|
104
|
+
ctx.ui.setStatus("seed-env", undefined);
|
|
107
105
|
if (isDev) ctx.ui.setStatus("seed-api", `dev: ${effectiveApiBase}`);
|
|
108
106
|
else ctx.ui.setStatus("seed-api", undefined);
|
|
109
107
|
if (hasSeparateDevAuthBase) ctx.ui.setStatus("seed-auth", `auth: ${effectiveAuthBase}`);
|
|
@@ -111,9 +111,9 @@ export function makeProgressCallRenderer(
|
|
|
111
111
|
label: string,
|
|
112
112
|
detail?: (args: any) => string | undefined,
|
|
113
113
|
) {
|
|
114
|
-
return (args: any, theme: any, context
|
|
115
|
-
const state =
|
|
116
|
-
if (context
|
|
114
|
+
return (args: any, theme: any, context?: any) => {
|
|
115
|
+
const state = context?.state;
|
|
116
|
+
if (context?.isPartial && state) {
|
|
117
117
|
if (!state.spinnerTimer) {
|
|
118
118
|
state.spinnerFrame = 0;
|
|
119
119
|
state.spinnerTimer = setInterval(() => {
|
|
@@ -122,13 +122,13 @@ export function makeProgressCallRenderer(
|
|
|
122
122
|
}, 90);
|
|
123
123
|
state.spinnerTimer.unref?.();
|
|
124
124
|
}
|
|
125
|
-
} else if (state
|
|
125
|
+
} else if (state?.spinnerTimer) {
|
|
126
126
|
clearInterval(state.spinnerTimer);
|
|
127
127
|
state.spinnerTimer = undefined;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
-
const frame = context
|
|
131
|
-
const tone = context
|
|
130
|
+
const frame = context?.isPartial && state ? SPINNER_FRAMES[state.spinnerFrame ?? 0] : "•";
|
|
131
|
+
const tone = context?.isPartial ? "accent" : context?.isError ? "error" : "muted";
|
|
132
132
|
let text = `${theme.fg(tone, frame)} ${theme.fg("toolTitle", label)}`;
|
|
133
133
|
const detailText = detail?.(args);
|
|
134
134
|
if (detailText) text += theme.fg("dim", ` · ${detailText}`);
|
|
@@ -158,9 +158,9 @@ export function makeProgressResultRenderer(
|
|
|
158
158
|
successLabel = "Completed",
|
|
159
159
|
summary?: (details: any, args: any) => string | undefined,
|
|
160
160
|
) {
|
|
161
|
-
return (result: any, options: any, theme: any, context
|
|
162
|
-
const state = context
|
|
163
|
-
if (state
|
|
161
|
+
return (result: any, options: any, theme: any, context?: any) => {
|
|
162
|
+
const state = context?.state;
|
|
163
|
+
if (state?.spinnerTimer) {
|
|
164
164
|
clearInterval(state.spinnerTimer);
|
|
165
165
|
state.spinnerTimer = undefined;
|
|
166
166
|
}
|
|
@@ -170,14 +170,18 @@ export function makeProgressResultRenderer(
|
|
|
170
170
|
if (result?.isError) {
|
|
171
171
|
const line = firstTextLine(result?.content) ?? "Request failed";
|
|
172
172
|
let text = `${theme.fg("error", "✕")} ${theme.fg("error", line)}`;
|
|
173
|
-
const debugText =
|
|
173
|
+
const debugText = state
|
|
174
|
+
? getCachedDebugText(state, result, options?.expanded === true)
|
|
175
|
+
: getDebugText(result, options?.expanded === true);
|
|
174
176
|
if (debugText) text += `\n${theme.fg("dim", debugText)}`;
|
|
175
177
|
return new Text(text, 0, 0);
|
|
176
178
|
}
|
|
177
179
|
const detailText = summary?.(result?.details, context?.args) ?? summarizeDetails(result?.details);
|
|
178
180
|
let text = `${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}`;
|
|
179
181
|
if (detailText) text += theme.fg("dim", ` · ${detailText}`);
|
|
180
|
-
const debugText =
|
|
182
|
+
const debugText = state
|
|
183
|
+
? getCachedDebugText(state, result, options?.expanded === true)
|
|
184
|
+
: getDebugText(result, options?.expanded === true);
|
|
181
185
|
if (debugText) text += `\n${theme.fg("dim", debugText)}`;
|
|
182
186
|
return new Text(text, 0, 0);
|
|
183
187
|
};
|
|
@@ -173,6 +173,49 @@ function formatSlotRange(startMinutes: number, endMinutes: number, timeZone: str
|
|
|
173
173
|
return `${formatter.format(start)} - ${formatter.format(end)}`;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
+
function formatMeetingDateTimeRange(startsAt: string | null | undefined, endsAt: string | null | undefined) {
|
|
177
|
+
if (typeof startsAt !== "string" || !startsAt.trim()) return undefined;
|
|
178
|
+
const start = new Date(startsAt);
|
|
179
|
+
if (Number.isNaN(start.getTime())) return undefined;
|
|
180
|
+
|
|
181
|
+
const end =
|
|
182
|
+
typeof endsAt === "string" && endsAt.trim()
|
|
183
|
+
? new Date(endsAt)
|
|
184
|
+
: null;
|
|
185
|
+
|
|
186
|
+
const dateFormatter = new Intl.DateTimeFormat("en-US", {
|
|
187
|
+
weekday: "short",
|
|
188
|
+
month: "short",
|
|
189
|
+
day: "numeric",
|
|
190
|
+
});
|
|
191
|
+
const timeFormatter = new Intl.DateTimeFormat("en-US", {
|
|
192
|
+
hour: "numeric",
|
|
193
|
+
minute: "2-digit",
|
|
194
|
+
hour12: true,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const startDateLabel = dateFormatter.format(start);
|
|
198
|
+
const startTimeLabel = timeFormatter.format(start);
|
|
199
|
+
|
|
200
|
+
if (!end || Number.isNaN(end.getTime())) {
|
|
201
|
+
return `${startDateLabel} · ${startTimeLabel}`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const sameDay =
|
|
205
|
+
start.getFullYear() === end.getFullYear() &&
|
|
206
|
+
start.getMonth() === end.getMonth() &&
|
|
207
|
+
start.getDate() === end.getDate();
|
|
208
|
+
|
|
209
|
+
const endDateLabel = dateFormatter.format(end);
|
|
210
|
+
const endTimeLabel = timeFormatter.format(end);
|
|
211
|
+
|
|
212
|
+
if (sameDay) {
|
|
213
|
+
return `${startDateLabel} · ${startTimeLabel}-${endTimeLabel}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return `${startDateLabel} ${startTimeLabel} - ${endDateLabel} ${endTimeLabel}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
176
219
|
function shapeTranscriptSource(source: any, includeFullText: boolean, includePreview = true) {
|
|
177
220
|
if (!source) {
|
|
178
221
|
return null;
|
|
@@ -2092,7 +2135,13 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
2092
2135
|
producerFraming: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
2093
2136
|
}),
|
|
2094
2137
|
execute: wrapExecute(updateMeeting),
|
|
2095
|
-
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_update_meeting"), (args) =>
|
|
2138
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_update_meeting"), (args) =>
|
|
2139
|
+
firstNonEmptyString(
|
|
2140
|
+
formatMeetingDateTimeRange(args?.startsAt, args?.endsAt),
|
|
2141
|
+
args?.title,
|
|
2142
|
+
args?.meetingId,
|
|
2143
|
+
),
|
|
2144
|
+
),
|
|
2096
2145
|
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_update_meeting")),
|
|
2097
2146
|
});
|
|
2098
2147
|
|
|
@@ -9,7 +9,7 @@ export const TOOL_CALL_LABELS: Record<string, string> = {
|
|
|
9
9
|
seedclub_create_crm_task: "Creating CRM task",
|
|
10
10
|
seedclub_list_program_contacts: "Loading program contacts",
|
|
11
11
|
seedclub_search_people: "Searching people",
|
|
12
|
-
seedclub_list_meeting_availability: "
|
|
12
|
+
seedclub_list_meeting_availability: "Loading availability slots",
|
|
13
13
|
seedclub_list_meeting_calendars: "Loading booking calendars",
|
|
14
14
|
seedclub_list_personal_calendar_events: "Loading personal calendar events",
|
|
15
15
|
seedclub_create_personal_calendar_event: "Creating personal calendar event",
|
|
@@ -17,7 +17,6 @@ export const TOOL_CALL_LABELS: Record<string, string> = {
|
|
|
17
17
|
seedclub_delete_personal_calendar_event: "Deleting personal calendar event",
|
|
18
18
|
seedclub_find_common_meeting_availability: "Checking common availability",
|
|
19
19
|
seedclub_list_meetings: "Checking meeting schedule",
|
|
20
|
-
seedclub_book_meeting_full: "Booking meeting",
|
|
21
20
|
seedclub_list_show_guests: "Looking up show guests",
|
|
22
21
|
seedclub_list_guest_roster: "Building guest roster",
|
|
23
22
|
seedclub_get_guest_profile: "Resolving guest profile",
|
|
@@ -27,14 +26,6 @@ export const TOOL_CALL_LABELS: Record<string, string> = {
|
|
|
27
26
|
seedclub_list_meeting_transcripts: "Checking transcript availability",
|
|
28
27
|
seedclub_get_meeting_transcript: "Loading meeting transcript",
|
|
29
28
|
seedclub_get_meeting: "Loading meeting details",
|
|
30
|
-
seedclub_search_people: "Searching people",
|
|
31
|
-
seedclub_list_meeting_availability: "Loading availability slots",
|
|
32
|
-
seedclub_list_meeting_calendars: "Loading booking calendars",
|
|
33
|
-
seedclub_list_personal_calendar_events: "Loading personal calendar events",
|
|
34
|
-
seedclub_create_personal_calendar_event: "Creating personal calendar event",
|
|
35
|
-
seedclub_update_personal_calendar_event: "Updating personal calendar event",
|
|
36
|
-
seedclub_delete_personal_calendar_event: "Deleting personal calendar event",
|
|
37
|
-
seedclub_find_common_meeting_availability: "Checking common availability",
|
|
38
29
|
seedclub_book_meeting: "Booking meeting",
|
|
39
30
|
seedclub_reschedule_meeting: "Rescheduling meeting",
|
|
40
31
|
seedclub_cancel_meeting: "Cancelling meeting",
|
|
@@ -58,8 +49,8 @@ export const TOOL_SUCCESS_LABELS: Record<string, string> = {
|
|
|
58
49
|
seedclub_create_crm_note: "CRM note saved",
|
|
59
50
|
seedclub_create_crm_task: "CRM task created",
|
|
60
51
|
seedclub_list_program_contacts: "Program contacts loaded",
|
|
61
|
-
seedclub_search_people: "People
|
|
62
|
-
seedclub_list_meeting_availability: "Availability
|
|
52
|
+
seedclub_search_people: "People loaded",
|
|
53
|
+
seedclub_list_meeting_availability: "Availability loaded",
|
|
63
54
|
seedclub_list_meeting_calendars: "Booking calendars loaded",
|
|
64
55
|
seedclub_list_personal_calendar_events: "Personal calendar events loaded",
|
|
65
56
|
seedclub_create_personal_calendar_event: "Personal calendar event created",
|
|
@@ -67,7 +58,6 @@ export const TOOL_SUCCESS_LABELS: Record<string, string> = {
|
|
|
67
58
|
seedclub_delete_personal_calendar_event: "Personal calendar event deleted",
|
|
68
59
|
seedclub_find_common_meeting_availability: "Common availability loaded",
|
|
69
60
|
seedclub_list_meetings: "Meeting schedule loaded",
|
|
70
|
-
seedclub_book_meeting_full: "Meeting booked",
|
|
71
61
|
seedclub_list_show_guests: "Show guests loaded",
|
|
72
62
|
seedclub_list_guest_roster: "Guest roster ready",
|
|
73
63
|
seedclub_get_guest_profile: "Guest profile loaded",
|
|
@@ -77,14 +67,6 @@ export const TOOL_SUCCESS_LABELS: Record<string, string> = {
|
|
|
77
67
|
seedclub_list_meeting_transcripts: "Transcript availability checked",
|
|
78
68
|
seedclub_get_meeting_transcript: "Meeting transcript loaded",
|
|
79
69
|
seedclub_get_meeting: "Meeting details loaded",
|
|
80
|
-
seedclub_search_people: "People loaded",
|
|
81
|
-
seedclub_list_meeting_availability: "Availability loaded",
|
|
82
|
-
seedclub_list_meeting_calendars: "Booking calendars loaded",
|
|
83
|
-
seedclub_list_personal_calendar_events: "Personal calendar events loaded",
|
|
84
|
-
seedclub_create_personal_calendar_event: "Personal calendar event created",
|
|
85
|
-
seedclub_update_personal_calendar_event: "Personal calendar event updated",
|
|
86
|
-
seedclub_delete_personal_calendar_event: "Personal calendar event deleted",
|
|
87
|
-
seedclub_find_common_meeting_availability: "Common availability loaded",
|
|
88
70
|
seedclub_book_meeting: "Meeting booked",
|
|
89
71
|
seedclub_reschedule_meeting: "Meeting rescheduled",
|
|
90
72
|
seedclub_cancel_meeting: "Meeting cancelled",
|
package/package.json
CHANGED