@clubnet/seedclub 0.2.31 → 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/api-client.ts +4 -2
- package/assets/extensions/seedclub/auth.ts +21 -21
- package/assets/extensions/seedclub/commands/seedclub.ts +5 -4
- package/assets/extensions/seedclub/gate-state.ts +2 -2
- package/assets/extensions/seedclub/index.ts +4 -8
- package/assets/extensions/seedclub/tool-utils.ts +15 -11
- package/assets/extensions/seedclub/tools/crm.ts +11 -2
- package/assets/extensions/seedclub/tools/media.ts +25 -4
- package/assets/extensions/seedclub/tools/meetings.ts +180 -13
- package/assets/extensions/seedclub/ui-copy.ts +22 -0
- package/assets/extensions/seedclub-ui/footer.ts +0 -6
- package/assets/extensions/seedclub-ui/state.ts +5 -0
- package/assets/extensions/seedclub-ui/update.ts +155 -16
- package/assets/extensions/seedclub-ui/welcome.ts +131 -38
- package/package.json +1 -1
|
@@ -80,8 +80,10 @@ async function apiRequest<T>(endpoint: string, options: RequestOptions = {}): Pr
|
|
|
80
80
|
|
|
81
81
|
const response = await fetch(url.toString(), {
|
|
82
82
|
method,
|
|
83
|
-
headers:
|
|
84
|
-
|
|
83
|
+
headers: body === undefined
|
|
84
|
+
? { Authorization: `Bearer ${token}` }
|
|
85
|
+
: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
86
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
85
87
|
});
|
|
86
88
|
|
|
87
89
|
const text = await response.text();
|
|
@@ -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();
|
|
@@ -16,9 +16,9 @@ export const AUTH_GATE_ALLOWED_COMMANDS = new Set([
|
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
18
|
const state: SeedclubAuthGateState = {
|
|
19
|
-
status: "
|
|
19
|
+
status: "auth_in_progress",
|
|
20
20
|
authUrl: null,
|
|
21
|
-
message: "Seed Club
|
|
21
|
+
message: "Checking Seed Club access...",
|
|
22
22
|
error: null,
|
|
23
23
|
};
|
|
24
24
|
|
|
@@ -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}`);
|
|
@@ -139,15 +137,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
139
137
|
}
|
|
140
138
|
|
|
141
139
|
await applyConnectedStatus(ctx, user);
|
|
140
|
+
markAuthComplete(getPostAuthInstruction(ctx));
|
|
142
141
|
return user;
|
|
143
142
|
}
|
|
144
143
|
|
|
145
144
|
async function ensureSeedclubAuthenticated(ctx: any): Promise<boolean> {
|
|
146
145
|
const existing = await validateCurrentCredential(ctx);
|
|
147
|
-
if (existing)
|
|
148
|
-
markAuthComplete(getPostAuthInstruction(ctx));
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
146
|
+
if (existing) return true;
|
|
151
147
|
return connect(undefined, ctx, { autoStart: true });
|
|
152
148
|
}
|
|
153
149
|
|
|
@@ -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
|
};
|
|
@@ -195,13 +195,22 @@ export function registerCrmTools(pi: ExtensionAPI) {
|
|
|
195
195
|
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_list_program_contacts"), (args) => args?.programSlug || undefined),
|
|
196
196
|
renderResult(result: any, _args: any, theme: any) {
|
|
197
197
|
if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
|
|
198
|
+
const successLabel = getToolSuccessLabel("seedclub_list_program_contacts");
|
|
198
199
|
const details = result.details ?? {};
|
|
199
200
|
const rows = Array.isArray(details?.data) ? details.data : [];
|
|
200
201
|
const program = details?.program?.slug ?? details?.program?.name ?? "program";
|
|
201
|
-
if (!rows.length)
|
|
202
|
+
if (!rows.length) {
|
|
203
|
+
return new Text(
|
|
204
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}${theme.fg("dim", ` · No contacts found for ${program}`)}`,
|
|
205
|
+
0,
|
|
206
|
+
0,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
202
209
|
|
|
203
210
|
const shown = rows.slice(0, 8);
|
|
204
|
-
let text =
|
|
211
|
+
let text =
|
|
212
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}` +
|
|
213
|
+
theme.fg("dim", ` · ${rows.length} contact${rows.length === 1 ? "" : "s"} loaded for ${program}`);
|
|
205
214
|
for (const row of shown) {
|
|
206
215
|
const name = row?.party?.display_name ?? row?.person?.full_name ?? "Unknown";
|
|
207
216
|
const org = row?.roles?.[0]?.organization_name ?? row?.organization?.name ?? null;
|
|
@@ -222,11 +222,20 @@ export function registerMediaTools(pi: ExtensionAPI) {
|
|
|
222
222
|
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_list_program_media_assets"), (args) => args?.programSlug || undefined),
|
|
223
223
|
renderResult(result: any, { expanded }: any, theme: any) {
|
|
224
224
|
if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
|
|
225
|
+
const successLabel = getToolSuccessLabel("seedclub_list_program_media_assets");
|
|
225
226
|
const rows = Array.isArray(result.details?.data) ? result.details.data : [];
|
|
226
227
|
const program = result.details?.program?.slug ?? result.details?.program?.name ?? "program";
|
|
227
|
-
if (!rows.length)
|
|
228
|
+
if (!rows.length) {
|
|
229
|
+
return new Text(
|
|
230
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}${theme.fg("dim", ` · No media assets for ${program}`)}`,
|
|
231
|
+
0,
|
|
232
|
+
0,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
228
235
|
|
|
229
|
-
let text =
|
|
236
|
+
let text =
|
|
237
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}` +
|
|
238
|
+
theme.fg("dim", ` · ${rows.length} media asset${rows.length === 1 ? "" : "s"} for ${program}`);
|
|
230
239
|
for (const row of rows.slice(0, expanded ? 12 : 6)) {
|
|
231
240
|
const asset = row?.asset ?? row;
|
|
232
241
|
const date = asset?.event_date ?? "unknown date";
|
|
@@ -259,14 +268,26 @@ export function registerMediaTools(pi: ExtensionAPI) {
|
|
|
259
268
|
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_get_program_media_asset"), (args) => args?.assetId || undefined),
|
|
260
269
|
renderResult(result: any, _args: any, theme: any) {
|
|
261
270
|
if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
|
|
271
|
+
const successLabel = getToolSuccessLabel("seedclub_get_program_media_asset");
|
|
262
272
|
const asset = result.details?.asset ?? null;
|
|
263
|
-
if (!asset)
|
|
273
|
+
if (!asset) {
|
|
274
|
+
return new Text(
|
|
275
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}${theme.fg("dim", " · No media asset found")}`,
|
|
276
|
+
0,
|
|
277
|
+
0,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
264
280
|
const date = asset?.event_date ?? "unknown date";
|
|
265
281
|
const title = asset?.title ?? asset?.file_name ?? "untitled";
|
|
266
282
|
const kind = asset?.asset_kind ?? "asset";
|
|
267
283
|
const hasText = asset?.has_transcript_text ? "text" : "no-text";
|
|
268
284
|
const hasVtt = asset?.has_transcript_vtt ? "vtt" : "no-vtt";
|
|
269
|
-
return new Text(
|
|
285
|
+
return new Text(
|
|
286
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}` +
|
|
287
|
+
theme.fg("dim", ` · ${date} - ${title} [${kind}; ${hasText}; ${hasVtt}]`),
|
|
288
|
+
0,
|
|
289
|
+
0,
|
|
290
|
+
);
|
|
270
291
|
},
|
|
271
292
|
});
|
|
272
293
|
|
|
@@ -39,6 +39,26 @@ const GUEST_ROSTER_DEFAULT_FIELDS = [
|
|
|
39
39
|
"organizationRole",
|
|
40
40
|
];
|
|
41
41
|
|
|
42
|
+
function summarizeCount(count: number | undefined, noun: string) {
|
|
43
|
+
return typeof count === "number" ? `${count} ${noun}${count === 1 ? "" : "s"}` : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function countFromArrays(...values: any[]) {
|
|
47
|
+
for (const value of values) {
|
|
48
|
+
if (Array.isArray(value)) return value.length;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function firstNonEmptyString(...values: unknown[]) {
|
|
54
|
+
for (const value of values) {
|
|
55
|
+
if (typeof value === "string" && value.trim()) {
|
|
56
|
+
return value.trim();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
function normalizeLimit(limit: number | undefined, defaults: { fallback: number; max: number }) {
|
|
43
63
|
if (!Number.isFinite(limit)) return defaults.fallback;
|
|
44
64
|
return Math.max(1, Math.min(defaults.max, Math.trunc(limit!)));
|
|
@@ -153,6 +173,49 @@ function formatSlotRange(startMinutes: number, endMinutes: number, timeZone: str
|
|
|
153
173
|
return `${formatter.format(start)} - ${formatter.format(end)}`;
|
|
154
174
|
}
|
|
155
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
|
+
|
|
156
219
|
function shapeTranscriptSource(source: any, includeFullText: boolean, includePreview = true) {
|
|
157
220
|
if (!source) {
|
|
158
221
|
return null;
|
|
@@ -1423,7 +1486,7 @@ async function findCommonMeetingAvailability(args: {
|
|
|
1423
1486
|
}
|
|
1424
1487
|
}
|
|
1425
1488
|
|
|
1426
|
-
async function
|
|
1489
|
+
async function bookMeeting(args: {
|
|
1427
1490
|
startsAt: string;
|
|
1428
1491
|
endsAt?: string | null;
|
|
1429
1492
|
durationMinutes?: number | null;
|
|
@@ -1440,7 +1503,7 @@ async function bookMeetingFull(args: {
|
|
|
1440
1503
|
}) {
|
|
1441
1504
|
try {
|
|
1442
1505
|
const resolvedEndsAt = resolveBookingEndsAt(args.startsAt, args.endsAt, args.durationMinutes);
|
|
1443
|
-
return await api.post<any>("/meetings/book
|
|
1506
|
+
return await api.post<any>("/meetings/book", {
|
|
1444
1507
|
calendar_account_id: args.calendarAccountId,
|
|
1445
1508
|
program_slug: args.programSlug,
|
|
1446
1509
|
starts_at: args.startsAt,
|
|
@@ -1463,6 +1526,44 @@ async function bookMeetingFull(args: {
|
|
|
1463
1526
|
}
|
|
1464
1527
|
}
|
|
1465
1528
|
|
|
1529
|
+
async function rescheduleMeeting(args: {
|
|
1530
|
+
meetingId: string;
|
|
1531
|
+
startsAt: string;
|
|
1532
|
+
endsAt?: string | null;
|
|
1533
|
+
durationMinutes?: number | null;
|
|
1534
|
+
timeZone?: string;
|
|
1535
|
+
title?: string | null;
|
|
1536
|
+
description?: string | null;
|
|
1537
|
+
calendarAccountId?: string | null;
|
|
1538
|
+
}) {
|
|
1539
|
+
try {
|
|
1540
|
+
const resolvedEndsAt = resolveBookingEndsAt(args.startsAt, args.endsAt, args.durationMinutes);
|
|
1541
|
+
return await api.post<any>(`/meetings/${args.meetingId}/reschedule`, {
|
|
1542
|
+
calendar_account_id: args.calendarAccountId,
|
|
1543
|
+
starts_at: args.startsAt,
|
|
1544
|
+
ends_at: resolvedEndsAt,
|
|
1545
|
+
duration_minutes: args.durationMinutes,
|
|
1546
|
+
time_zone: args.timeZone,
|
|
1547
|
+
title: args.title,
|
|
1548
|
+
description: args.description,
|
|
1549
|
+
});
|
|
1550
|
+
} catch (error) {
|
|
1551
|
+
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
1552
|
+
throw error;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
async function cancelMeeting(args: { meetingId: string; calendarAccountId?: string | null }) {
|
|
1557
|
+
try {
|
|
1558
|
+
return await api.post<any>(`/meetings/${args.meetingId}/cancel`, {
|
|
1559
|
+
calendar_account_id: args.calendarAccountId,
|
|
1560
|
+
});
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
1563
|
+
throw error;
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1466
1567
|
async function updateMeeting(args: {
|
|
1467
1568
|
meetingId: string;
|
|
1468
1569
|
startsAt?: string | null;
|
|
@@ -1512,6 +1613,10 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1512
1613
|
),
|
|
1513
1614
|
}),
|
|
1514
1615
|
execute: wrapExecute(searchPeopleForBooking),
|
|
1616
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_search_people"), (args) => args?.query || undefined),
|
|
1617
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_search_people"), (details) =>
|
|
1618
|
+
summarizeCount(countFromArrays(details?.data), "match"),
|
|
1619
|
+
),
|
|
1515
1620
|
});
|
|
1516
1621
|
|
|
1517
1622
|
pi.registerTool({
|
|
@@ -1527,6 +1632,10 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1527
1632
|
timeZone: Type.Optional(Type.String({ description: "Optional timezone used to interpret the requested date. Defaults to America/New_York." })),
|
|
1528
1633
|
}),
|
|
1529
1634
|
execute: wrapExecute(listMeetingAvailabilitySlots),
|
|
1635
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_list_meeting_availability"), (args) => args?.date || undefined),
|
|
1636
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_list_meeting_availability"), (details) =>
|
|
1637
|
+
summarizeCount(countFromArrays(details?.slots), "slot"),
|
|
1638
|
+
),
|
|
1530
1639
|
});
|
|
1531
1640
|
|
|
1532
1641
|
pi.registerTool({
|
|
@@ -1538,6 +1647,10 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1538
1647
|
programSlug: Type.Optional(Type.String({ description: "Program slug. Defaults to 11am on the server." })),
|
|
1539
1648
|
}),
|
|
1540
1649
|
execute: wrapExecute(listMeetingCalendars),
|
|
1650
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_list_meeting_calendars"), (args) => args?.programSlug || undefined),
|
|
1651
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_list_meeting_calendars"), (details) =>
|
|
1652
|
+
summarizeCount(countFromArrays(details?.data, details?.calendars), "calendar"),
|
|
1653
|
+
),
|
|
1541
1654
|
});
|
|
1542
1655
|
|
|
1543
1656
|
pi.registerTool({
|
|
@@ -1554,6 +1667,10 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1554
1667
|
query: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Optional Google Calendar search query." })),
|
|
1555
1668
|
}),
|
|
1556
1669
|
execute: wrapExecute(listPersonalCalendarEvents),
|
|
1670
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_list_personal_calendar_events"), (args) => args?.accountId || args?.query || undefined),
|
|
1671
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_list_personal_calendar_events"), (details) =>
|
|
1672
|
+
summarizeCount(countFromArrays(details?.data, details?.events, details?.items), "event"),
|
|
1673
|
+
),
|
|
1557
1674
|
});
|
|
1558
1675
|
|
|
1559
1676
|
pi.registerTool({
|
|
@@ -1571,6 +1688,10 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1571
1688
|
attendeeEmails: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()])),
|
|
1572
1689
|
}),
|
|
1573
1690
|
execute: wrapExecute(createPersonalCalendarEvent),
|
|
1691
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_create_personal_calendar_event"), (args) => args?.summary || undefined),
|
|
1692
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_create_personal_calendar_event"), (details) =>
|
|
1693
|
+
firstNonEmptyString(details?.event?.id, details?.id, details?.event_id, details?.eventId, details?.summary),
|
|
1694
|
+
),
|
|
1574
1695
|
});
|
|
1575
1696
|
|
|
1576
1697
|
pi.registerTool({
|
|
@@ -1589,6 +1710,10 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1589
1710
|
attendeeEmails: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()])),
|
|
1590
1711
|
}),
|
|
1591
1712
|
execute: wrapExecute(updatePersonalCalendarEvent),
|
|
1713
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_update_personal_calendar_event"), (args) => args?.eventId || undefined),
|
|
1714
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_update_personal_calendar_event"), (details, args) =>
|
|
1715
|
+
firstNonEmptyString(details?.event?.id, details?.id, details?.event_id, details?.eventId, args?.eventId),
|
|
1716
|
+
),
|
|
1592
1717
|
});
|
|
1593
1718
|
|
|
1594
1719
|
pi.registerTool({
|
|
@@ -1601,6 +1726,10 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1601
1726
|
eventId: Type.String({ description: "Google Calendar event id." }),
|
|
1602
1727
|
}),
|
|
1603
1728
|
execute: wrapExecute(deletePersonalCalendarEvent),
|
|
1729
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_delete_personal_calendar_event"), (args) => args?.eventId || undefined),
|
|
1730
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_delete_personal_calendar_event"), (_details, args) =>
|
|
1731
|
+
firstNonEmptyString(args?.eventId, "deleted"),
|
|
1732
|
+
),
|
|
1604
1733
|
});
|
|
1605
1734
|
|
|
1606
1735
|
pi.registerTool({
|
|
@@ -1628,6 +1757,12 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1628
1757
|
),
|
|
1629
1758
|
}),
|
|
1630
1759
|
execute: wrapExecute(findCommonMeetingAvailability),
|
|
1760
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_find_common_meeting_availability"), (args) =>
|
|
1761
|
+
summarizeCount(Array.isArray(args?.calendarAccountIds) ? args.calendarAccountIds.length : undefined, "calendar"),
|
|
1762
|
+
),
|
|
1763
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_find_common_meeting_availability"), (details) =>
|
|
1764
|
+
summarizeCount(countFromArrays(details?.slots, details?.data, details?.availability, details?.matches), "slot"),
|
|
1765
|
+
),
|
|
1631
1766
|
});
|
|
1632
1767
|
|
|
1633
1768
|
pi.registerTool({
|
|
@@ -1755,13 +1890,23 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1755
1890
|
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_find_latest_guest_transcript"), (args) => args?.guest || undefined),
|
|
1756
1891
|
renderResult(result: any, _args: any, theme: any) {
|
|
1757
1892
|
if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
|
|
1893
|
+
const successLabel = getToolSuccessLabel("seedclub_find_latest_guest_transcript");
|
|
1758
1894
|
const found = result.details?.found === true;
|
|
1759
1895
|
if (!found) {
|
|
1760
|
-
return new Text(
|
|
1896
|
+
return new Text(
|
|
1897
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}` +
|
|
1898
|
+
theme.fg("dim", ` · ${result.details?.reason || "No matching transcript found"}`),
|
|
1899
|
+
0,
|
|
1900
|
+
0,
|
|
1901
|
+
);
|
|
1761
1902
|
}
|
|
1762
1903
|
const row = result.details?.result ?? {};
|
|
1763
1904
|
const status = `${row?.eventDate ?? "unknown date"} · ${row?.guest ?? "guest"} · text:${row?.hasText ? "yes" : "no"} · vtt:${row?.hasVtt ? "yes" : "no"}`;
|
|
1764
|
-
return new Text(
|
|
1905
|
+
return new Text(
|
|
1906
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}` + theme.fg("dim", ` · ${status}`),
|
|
1907
|
+
0,
|
|
1908
|
+
0,
|
|
1909
|
+
);
|
|
1765
1910
|
},
|
|
1766
1911
|
});
|
|
1767
1912
|
|
|
@@ -1783,6 +1928,7 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1783
1928
|
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_prepare_clip_packet"), (args) => args?.meetingId || undefined),
|
|
1784
1929
|
renderResult(result: any, _args: any, theme: any) {
|
|
1785
1930
|
if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
|
|
1931
|
+
const successLabel = getToolSuccessLabel("seedclub_prepare_clip_packet");
|
|
1786
1932
|
|
|
1787
1933
|
const details = result.details ?? {};
|
|
1788
1934
|
const meeting = details.meeting ?? {};
|
|
@@ -1799,10 +1945,12 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1799
1945
|
? details.clipReadiness.reasons.filter((reason: any) => typeof reason === "string")
|
|
1800
1946
|
: [];
|
|
1801
1947
|
|
|
1802
|
-
let text =
|
|
1803
|
-
"muted",
|
|
1804
|
-
|
|
1805
|
-
|
|
1948
|
+
let text =
|
|
1949
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}` +
|
|
1950
|
+
theme.fg(
|
|
1951
|
+
"dim",
|
|
1952
|
+
` · ${eventDate} · ${guestName} · transcript:${hasTranscript ? "yes" : "no"} · recording:${hasRecording ? "yes" : "no"} · ready:${ready ? "yes" : "no"}`,
|
|
1953
|
+
);
|
|
1806
1954
|
if (!ready && reasons.length) {
|
|
1807
1955
|
text += theme.fg("dim", `\n blockers: ${reasons.join(", ")}`);
|
|
1808
1956
|
}
|
|
@@ -1864,15 +2012,24 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1864
2012
|
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_list_meeting_transcripts"), (args) => args?.programSlug || undefined),
|
|
1865
2013
|
renderResult(result: any, renderArgs: any, theme: any) {
|
|
1866
2014
|
if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
|
|
2015
|
+
const successLabel = getToolSuccessLabel("seedclub_list_meeting_transcripts");
|
|
1867
2016
|
const expanded = renderArgs?.expanded === true;
|
|
1868
2017
|
const requestedLimit = Number.isFinite(renderArgs?.limit) ? Math.trunc(renderArgs.limit) : DEFAULT_TRANSCRIPT_LIMIT;
|
|
1869
2018
|
const rows = Array.isArray(result.details?.data) ? result.details.data : [];
|
|
1870
2019
|
const program = result.details?.program?.slug ?? result.details?.program?.name ?? "program";
|
|
1871
|
-
if (!rows.length)
|
|
2020
|
+
if (!rows.length) {
|
|
2021
|
+
return new Text(
|
|
2022
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}${theme.fg("dim", ` · No transcripts found for ${program}`)}`,
|
|
2023
|
+
0,
|
|
2024
|
+
0,
|
|
2025
|
+
);
|
|
2026
|
+
}
|
|
1872
2027
|
|
|
1873
2028
|
const visibleCount = expanded ? 20 : 8;
|
|
1874
2029
|
const shown = Math.min(visibleCount, rows.length);
|
|
1875
|
-
let text =
|
|
2030
|
+
let text =
|
|
2031
|
+
`${theme.fg("success", "✓")} ${theme.fg("muted", successLabel)}` +
|
|
2032
|
+
theme.fg("dim", ` · ${rows.length} transcript${rows.length === 1 ? "" : "s"} loaded for ${program}`);
|
|
1876
2033
|
for (const row of rows.slice(0, shown)) {
|
|
1877
2034
|
const date = row?.transcript?.event_date ?? "unknown date";
|
|
1878
2035
|
const name = row?.transcript_for ?? "Unknown";
|
|
@@ -1928,7 +2085,7 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1928
2085
|
});
|
|
1929
2086
|
|
|
1930
2087
|
pi.registerTool({
|
|
1931
|
-
name: "
|
|
2088
|
+
name: "seedclub_book_meeting",
|
|
1932
2089
|
label: "Book Meeting",
|
|
1933
2090
|
description:
|
|
1934
2091
|
"Book one guest meeting end-to-end: create the meeting, create the studio link, and create the Google Calendar invite. Always fetch availability first with seedclub_list_meeting_availability and use one of the returned startsAt/endsAt pairs. For common meetings, use seedclub_list_meeting_calendars and seedclub_find_common_meeting_availability first, then pass the chosen calendarAccountId. Only use this after you know guestName, guestEmail, and startsAt. Prefer passing endsAt when using an availability slot. If they only give a slot or duration, pass durationMinutes instead; if neither is available, this tool defaults to a 20-minute meeting, but the backend will still reject times that do not exactly match a configured slot. Search for an existing guest first and pass partyId when you have a confident match; otherwise proceed without partyId and the backend will find or create a contact using guestEmail before booking.",
|
|
@@ -1959,7 +2116,11 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1959
2116
|
profileImageUrl: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
1960
2117
|
programSlug: Type.Optional(Type.String({ description: "Program slug. Defaults to 11am on the server." })),
|
|
1961
2118
|
}),
|
|
1962
|
-
execute: wrapExecute(
|
|
2119
|
+
execute: wrapExecute(bookMeeting),
|
|
2120
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_book_meeting_full"), (args) => args?.guestName || undefined),
|
|
2121
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_book_meeting_full"), (details, args) =>
|
|
2122
|
+
firstNonEmptyString(details?.meeting?.id, details?.meeting_id, details?.meetingId, details?.meeting?.title, args?.guestName),
|
|
2123
|
+
),
|
|
1963
2124
|
});
|
|
1964
2125
|
|
|
1965
2126
|
pi.registerTool({
|
|
@@ -1974,7 +2135,13 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
1974
2135
|
producerFraming: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
1975
2136
|
}),
|
|
1976
2137
|
execute: wrapExecute(updateMeeting),
|
|
1977
|
-
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
|
+
),
|
|
1978
2145
|
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_update_meeting")),
|
|
1979
2146
|
});
|
|
1980
2147
|
|
|
@@ -8,6 +8,14 @@ export const TOOL_CALL_LABELS: Record<string, string> = {
|
|
|
8
8
|
seedclub_create_crm_note: "Saving CRM note",
|
|
9
9
|
seedclub_create_crm_task: "Creating CRM task",
|
|
10
10
|
seedclub_list_program_contacts: "Loading program contacts",
|
|
11
|
+
seedclub_search_people: "Searching people",
|
|
12
|
+
seedclub_list_meeting_availability: "Loading availability slots",
|
|
13
|
+
seedclub_list_meeting_calendars: "Loading booking calendars",
|
|
14
|
+
seedclub_list_personal_calendar_events: "Loading personal calendar events",
|
|
15
|
+
seedclub_create_personal_calendar_event: "Creating personal calendar event",
|
|
16
|
+
seedclub_update_personal_calendar_event: "Updating personal calendar event",
|
|
17
|
+
seedclub_delete_personal_calendar_event: "Deleting personal calendar event",
|
|
18
|
+
seedclub_find_common_meeting_availability: "Checking common availability",
|
|
11
19
|
seedclub_list_meetings: "Checking meeting schedule",
|
|
12
20
|
seedclub_list_show_guests: "Looking up show guests",
|
|
13
21
|
seedclub_list_guest_roster: "Building guest roster",
|
|
@@ -18,6 +26,9 @@ export const TOOL_CALL_LABELS: Record<string, string> = {
|
|
|
18
26
|
seedclub_list_meeting_transcripts: "Checking transcript availability",
|
|
19
27
|
seedclub_get_meeting_transcript: "Loading meeting transcript",
|
|
20
28
|
seedclub_get_meeting: "Loading meeting details",
|
|
29
|
+
seedclub_book_meeting: "Booking meeting",
|
|
30
|
+
seedclub_reschedule_meeting: "Rescheduling meeting",
|
|
31
|
+
seedclub_cancel_meeting: "Cancelling meeting",
|
|
21
32
|
seedclub_update_meeting: "Updating meeting",
|
|
22
33
|
seedclub_assign_meeting: "Assigning meeting",
|
|
23
34
|
seedclub_list_program_media_assets: "Checking media assets",
|
|
@@ -38,6 +49,14 @@ export const TOOL_SUCCESS_LABELS: Record<string, string> = {
|
|
|
38
49
|
seedclub_create_crm_note: "CRM note saved",
|
|
39
50
|
seedclub_create_crm_task: "CRM task created",
|
|
40
51
|
seedclub_list_program_contacts: "Program contacts loaded",
|
|
52
|
+
seedclub_search_people: "People loaded",
|
|
53
|
+
seedclub_list_meeting_availability: "Availability loaded",
|
|
54
|
+
seedclub_list_meeting_calendars: "Booking calendars loaded",
|
|
55
|
+
seedclub_list_personal_calendar_events: "Personal calendar events loaded",
|
|
56
|
+
seedclub_create_personal_calendar_event: "Personal calendar event created",
|
|
57
|
+
seedclub_update_personal_calendar_event: "Personal calendar event updated",
|
|
58
|
+
seedclub_delete_personal_calendar_event: "Personal calendar event deleted",
|
|
59
|
+
seedclub_find_common_meeting_availability: "Common availability loaded",
|
|
41
60
|
seedclub_list_meetings: "Meeting schedule loaded",
|
|
42
61
|
seedclub_list_show_guests: "Show guests loaded",
|
|
43
62
|
seedclub_list_guest_roster: "Guest roster ready",
|
|
@@ -48,6 +67,9 @@ export const TOOL_SUCCESS_LABELS: Record<string, string> = {
|
|
|
48
67
|
seedclub_list_meeting_transcripts: "Transcript availability checked",
|
|
49
68
|
seedclub_get_meeting_transcript: "Meeting transcript loaded",
|
|
50
69
|
seedclub_get_meeting: "Meeting details loaded",
|
|
70
|
+
seedclub_book_meeting: "Meeting booked",
|
|
71
|
+
seedclub_reschedule_meeting: "Meeting rescheduled",
|
|
72
|
+
seedclub_cancel_meeting: "Meeting cancelled",
|
|
51
73
|
seedclub_update_meeting: "Meeting updated",
|
|
52
74
|
seedclub_assign_meeting: "Meeting assigned",
|
|
53
75
|
seedclub_list_program_media_assets: "Media assets checked",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export const uiState = {
|
|
2
2
|
ready: false,
|
|
3
|
+
requestRender: undefined as undefined | (() => void),
|
|
3
4
|
todayOn11am: null as null | {
|
|
4
5
|
date: string;
|
|
5
6
|
guests: Array<{
|
|
@@ -10,4 +11,8 @@ export const uiState = {
|
|
|
10
11
|
transcriptAvailable: boolean;
|
|
11
12
|
}>;
|
|
12
13
|
},
|
|
14
|
+
update: null as null | {
|
|
15
|
+
installedVersion: string;
|
|
16
|
+
latestVersion: string;
|
|
17
|
+
},
|
|
13
18
|
};
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Update
|
|
3
|
-
* To update, run `seedclub update` in your terminal.
|
|
2
|
+
* Update prompt — checks npm for a newer package version and offers actions.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
7
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
8
5
|
import { execFile } from "node:child_process";
|
|
9
|
-
import {
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
10
8
|
import { homedir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
11
|
+
import { isAuthGateBlocking } from "../seedclub/gate-state.js";
|
|
12
|
+
import { uiState } from "./state.js";
|
|
11
13
|
|
|
12
14
|
interface VersionInfo {
|
|
13
15
|
seedclubVersion: string;
|
|
14
16
|
piVersion: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
interface UpdatePrefs {
|
|
20
|
+
skipVersion?: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const UPDATE_PREFS_FILE = join(homedir(), ".seedclub", "agent", ".seedclub-update-prefs.json");
|
|
24
|
+
const UPDATE_COMMAND = "npm install -g @clubnet/seedclub@latest";
|
|
25
|
+
|
|
17
26
|
function compareSemver(a: string, b: string): number {
|
|
18
27
|
const pa = a.trim().split(".").map((x) => Number.parseInt(x, 10));
|
|
19
28
|
const pb = b.trim().split(".").map((x) => Number.parseInt(x, 10));
|
|
@@ -38,9 +47,23 @@ function getInstalledVersion(): VersionInfo | null {
|
|
|
38
47
|
}
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
function getUpdatePrefs(): UpdatePrefs {
|
|
51
|
+
if (!existsSync(UPDATE_PREFS_FILE)) return {};
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(readFileSync(UPDATE_PREFS_FILE, "utf-8"));
|
|
54
|
+
} catch {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function storeUpdatePrefs(prefs: UpdatePrefs): Promise<void> {
|
|
60
|
+
await mkdir(join(homedir(), ".seedclub", "agent"), { recursive: true });
|
|
61
|
+
await writeFile(UPDATE_PREFS_FILE, JSON.stringify(prefs, null, 2) + "\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
41
64
|
function getLatestVersion(): Promise<string | null> {
|
|
42
65
|
return new Promise((resolve) => {
|
|
43
|
-
execFile("npm", ["view", "@clubnet/seedclub", "version"], { timeout:
|
|
66
|
+
execFile("npm", ["view", "@clubnet/seedclub", "version"], { timeout: 10_000 }, (err, stdout) => {
|
|
44
67
|
if (err) return resolve(null);
|
|
45
68
|
const version = stdout.trim();
|
|
46
69
|
resolve(version || null);
|
|
@@ -48,24 +71,140 @@ function getLatestVersion(): Promise<string | null> {
|
|
|
48
71
|
});
|
|
49
72
|
}
|
|
50
73
|
|
|
74
|
+
async function waitForReadyWindow(): Promise<boolean> {
|
|
75
|
+
const startedAt = Date.now();
|
|
76
|
+
while (Date.now() - startedAt < 30_000) {
|
|
77
|
+
if (uiState.ready && !isAuthGateBlocking()) return true;
|
|
78
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function runUpdateCommand(pi: ExtensionAPI): Promise<void> {
|
|
84
|
+
await new Promise<void>((resolve, reject) => {
|
|
85
|
+
execFile("npm", ["install", "-g", "@clubnet/seedclub@latest"], { timeout: 120_000 }, (error) => {
|
|
86
|
+
if (error) {
|
|
87
|
+
reject(error);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
resolve();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Trigger a pi-side version refresh if available in the current runtime.
|
|
95
|
+
try {
|
|
96
|
+
await pi.exec("seedclub", ["version"]);
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function setAvailableUpdate(installedVersion: string | null, latestVersion: string | null) {
|
|
101
|
+
if (installedVersion && latestVersion) {
|
|
102
|
+
uiState.update = { installedVersion, latestVersion };
|
|
103
|
+
} else {
|
|
104
|
+
uiState.update = null;
|
|
105
|
+
}
|
|
106
|
+
uiState.requestRender?.();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function notifyAvailableUpdate(ctx: any, latestVersion: string) {
|
|
110
|
+
ctx.ui.notify(`New update available (v${latestVersion}). Use /update to update seedclub.`, "info");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function openUpdatePrompt(
|
|
114
|
+
pi: ExtensionAPI,
|
|
115
|
+
ctx: any,
|
|
116
|
+
installedVersion: string,
|
|
117
|
+
latestVersion: string,
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
const choice = await ctx.ui.select(`Update available (${installedVersion} -> ${latestVersion})`, [
|
|
120
|
+
`Update now (${UPDATE_COMMAND})`,
|
|
121
|
+
"Skip",
|
|
122
|
+
"Skip until next version",
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
switch (choice) {
|
|
126
|
+
case `Update now (${UPDATE_COMMAND})`:
|
|
127
|
+
ctx.ui.notify(`Updating seedclub...\nRunning: ${UPDATE_COMMAND}`, "info");
|
|
128
|
+
try {
|
|
129
|
+
await runUpdateCommand(pi);
|
|
130
|
+
await storeUpdatePrefs({ skipVersion: null });
|
|
131
|
+
setAvailableUpdate(null, null);
|
|
132
|
+
ctx.ui.notify("seedclub was updated. Restart the app to load the new version.", "info");
|
|
133
|
+
} catch (error) {
|
|
134
|
+
const message = error instanceof Error ? error.message : "Update failed.";
|
|
135
|
+
ctx.ui.notify(`Update failed: ${message}`, "error");
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
case "Skip until next version":
|
|
139
|
+
await storeUpdatePrefs({ skipVersion: latestVersion });
|
|
140
|
+
setAvailableUpdate(null, null);
|
|
141
|
+
break;
|
|
142
|
+
default:
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
51
147
|
export default function (pi: ExtensionAPI) {
|
|
52
148
|
let checked = false;
|
|
149
|
+
let announced = false;
|
|
53
150
|
|
|
54
|
-
pi.on("session_start",
|
|
151
|
+
pi.on("session_start", (_event, ctx) => {
|
|
55
152
|
if (!ctx.hasUI || checked) return;
|
|
56
153
|
checked = true;
|
|
154
|
+
announced = false;
|
|
155
|
+
setAvailableUpdate(null, null);
|
|
156
|
+
|
|
157
|
+
void (async () => {
|
|
158
|
+
const installed = getInstalledVersion();
|
|
159
|
+
if (!installed?.seedclubVersion) return;
|
|
160
|
+
|
|
161
|
+
const latest = await getLatestVersion();
|
|
162
|
+
if (!latest) return;
|
|
163
|
+
if (compareSemver(latest, installed.seedclubVersion) <= 0) return;
|
|
164
|
+
|
|
165
|
+
const prefs = getUpdatePrefs();
|
|
166
|
+
if (prefs.skipVersion === latest) return;
|
|
167
|
+
|
|
168
|
+
if (isAuthGateBlocking()) {
|
|
169
|
+
const ready = await waitForReadyWindow();
|
|
170
|
+
if (!ready) return;
|
|
171
|
+
}
|
|
172
|
+
setAvailableUpdate(installed.seedclubVersion, latest);
|
|
173
|
+
if (!announced) {
|
|
174
|
+
notifyAvailableUpdate(ctx, latest);
|
|
175
|
+
announced = true;
|
|
176
|
+
}
|
|
177
|
+
})().catch(() => {});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
pi.registerCommand("update", {
|
|
181
|
+
description: "Show the seedclub package update menu",
|
|
182
|
+
handler: async (_args, ctx) => {
|
|
183
|
+
const installed = getInstalledVersion();
|
|
184
|
+
if (!installed?.seedclubVersion) {
|
|
185
|
+
ctx.ui.notify("Unable to determine the installed seedclub version.", "error");
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
57
188
|
|
|
58
|
-
|
|
59
|
-
|
|
189
|
+
const latest = await getLatestVersion();
|
|
190
|
+
if (!latest) {
|
|
191
|
+
ctx.ui.notify("Unable to check npm for the latest seedclub version.", "error");
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
60
194
|
|
|
61
|
-
|
|
62
|
-
|
|
195
|
+
if (compareSemver(latest, installed.seedclubVersion) <= 0) {
|
|
196
|
+
setAvailableUpdate(null, null);
|
|
197
|
+
await storeUpdatePrefs({ skipVersion: null });
|
|
198
|
+
ctx.ui.notify(`seedclub is up to date (v${installed.seedclubVersion}).`, "info");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
63
201
|
|
|
64
|
-
|
|
202
|
+
const prefs = getUpdatePrefs();
|
|
203
|
+
if (prefs.skipVersion !== latest) {
|
|
204
|
+
setAvailableUpdate(installed.seedclubVersion, latest);
|
|
205
|
+
}
|
|
65
206
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"info",
|
|
69
|
-
);
|
|
207
|
+
await openUpdatePrompt(pi, ctx, installed.seedclubVersion, latest);
|
|
208
|
+
},
|
|
70
209
|
});
|
|
71
210
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Welcome header with live weather + market data.
|
|
3
|
-
* /
|
|
3
|
+
* /extensions is available as a command.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { execFileSync } from "node:child_process";
|
|
7
|
-
import {
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { basename, join } from "node:path";
|
|
8
10
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
9
11
|
import { ApiError, NotConnectedError, api } from "../seedclub/api-client.js";
|
|
10
12
|
import { getAuthGateState, isAuthGateBlocking, subscribeToAuthGate } from "../seedclub/gate-state.js";
|
|
@@ -311,6 +313,15 @@ function renderSetupLines(setupHints: string[], theme: ThemeLike): string[] {
|
|
|
311
313
|
});
|
|
312
314
|
}
|
|
313
315
|
|
|
316
|
+
function renderUpdateLines(theme: ThemeLike): string[] {
|
|
317
|
+
const update = uiState.update;
|
|
318
|
+
if (!update) return [];
|
|
319
|
+
return [
|
|
320
|
+
` ${theme.fg("accent", "New update available")}`,
|
|
321
|
+
` ${theme.fg("text", "/update")} ${theme.fg("dim", "open the package update menu")}`,
|
|
322
|
+
];
|
|
323
|
+
}
|
|
324
|
+
|
|
314
325
|
function renderAuthGateLines(theme: ThemeLike): string[] {
|
|
315
326
|
const gate = getAuthGateState();
|
|
316
327
|
const lines = [
|
|
@@ -334,11 +345,42 @@ function renderAuthGateLines(theme: ThemeLike): string[] {
|
|
|
334
345
|
|
|
335
346
|
lines.push("");
|
|
336
347
|
lines.push(` ${theme.fg("text", "/connect")} ${theme.fg("dim", "retry sign-in")}`);
|
|
337
|
-
lines.push(` ${theme.fg("text", "/commands")} ${theme.fg("dim", "list commands available during setup")}`);
|
|
338
348
|
lines.push("");
|
|
339
349
|
return lines;
|
|
340
350
|
}
|
|
341
351
|
|
|
352
|
+
function renderAuthCheckingLines(theme: ThemeLike, frame: number): string[] {
|
|
353
|
+
const gate = getAuthGateState();
|
|
354
|
+
const lines = [
|
|
355
|
+
"",
|
|
356
|
+
renderTitle(theme),
|
|
357
|
+
"",
|
|
358
|
+
...renderCoinLoaderLines(frame, theme, { showLabel: false }),
|
|
359
|
+
"",
|
|
360
|
+
` ${theme.fg("accent", "Checking access")}`,
|
|
361
|
+
` ${theme.fg("dim", gate.message || "Checking Seed Club access...")}`,
|
|
362
|
+
"",
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
if (gate.error) {
|
|
366
|
+
lines.push(` ${theme.fg("error", gate.error)}`);
|
|
367
|
+
lines.push("");
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (gate.authUrl) {
|
|
371
|
+
lines.push(` ${theme.fg("text", "Auth URL")}`);
|
|
372
|
+
lines.push(` ${theme.fg("mdLink", gate.authUrl)}`);
|
|
373
|
+
lines.push("");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return lines;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function hasSeedclubCredential(): boolean {
|
|
380
|
+
if (process.env.SEEDCLUB_ACCESS_TOKEN?.trim()) return true;
|
|
381
|
+
return existsSync(join(homedir(), ".config", "seedclub", "token"));
|
|
382
|
+
}
|
|
383
|
+
|
|
342
384
|
function renderTodayOn11amLines(today: TodayOn11am | null, theme: ThemeLike): string[] {
|
|
343
385
|
if (!today || !today.guests.length) return [];
|
|
344
386
|
const lines = [` ${theme.fg("text", "Today on 11AM")}`];
|
|
@@ -354,7 +396,7 @@ function renderTodayOn11amLines(today: TodayOn11am | null, theme: ThemeLike): st
|
|
|
354
396
|
return lines;
|
|
355
397
|
}
|
|
356
398
|
|
|
357
|
-
function renderCoinLoaderLines(frame: number, theme: ThemeLike): string[] {
|
|
399
|
+
function renderCoinLoaderLines(frame: number, theme: ThemeLike, options?: { showLabel?: boolean }): string[] {
|
|
358
400
|
const rawShape = [
|
|
359
401
|
" XXXXXX XXXXXX",
|
|
360
402
|
" XXXXXX XXXXXX",
|
|
@@ -410,6 +452,9 @@ function renderCoinLoaderLines(frame: number, theme: ThemeLike): string[] {
|
|
|
410
452
|
|
|
411
453
|
const loadingLabel = "loading...";
|
|
412
454
|
const loadingIndent = " ".repeat(2 + Math.max(0, Math.floor((cols - loadingLabel.length) / 2)));
|
|
455
|
+
if (options?.showLabel === false) {
|
|
456
|
+
return lines;
|
|
457
|
+
}
|
|
413
458
|
return [...lines, "", `${loadingIndent}${theme.fg("dim", loadingLabel)}`];
|
|
414
459
|
}
|
|
415
460
|
|
|
@@ -434,8 +479,15 @@ export default function (pi: ExtensionAPI, options?: { enableFrame?: boolean })
|
|
|
434
479
|
unsubscribeAuthGate?.();
|
|
435
480
|
|
|
436
481
|
let tuiRef: any = null;
|
|
482
|
+
let animatedHeaderTimer: ReturnType<typeof setInterval> | undefined;
|
|
483
|
+
let animatedHeaderFrame = 0;
|
|
484
|
+
let repaintHeader: (() => void) | undefined;
|
|
437
485
|
ctx.ui.setHeader((tui, theme) => {
|
|
438
486
|
tuiRef = tui;
|
|
487
|
+
uiState.requestRender = () => {
|
|
488
|
+
repaintHeader?.();
|
|
489
|
+
tui.requestRender();
|
|
490
|
+
};
|
|
439
491
|
tui.setClearOnShrink(true);
|
|
440
492
|
|
|
441
493
|
// Enable window frame around entire TUI (footer excluded)
|
|
@@ -456,6 +508,31 @@ export default function (pi: ExtensionAPI, options?: { enableFrame?: boolean })
|
|
|
456
508
|
};
|
|
457
509
|
});
|
|
458
510
|
|
|
511
|
+
const stopAnimatedHeader = () => {
|
|
512
|
+
if (!animatedHeaderTimer) return;
|
|
513
|
+
clearInterval(animatedHeaderTimer);
|
|
514
|
+
animatedHeaderTimer = undefined;
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const startAnimatedHeader = (renderFrame: (frame: number) => string[]) => {
|
|
518
|
+
stopAnimatedHeader();
|
|
519
|
+
animatedHeaderFrame = 0;
|
|
520
|
+
repaintHeader = () => {
|
|
521
|
+
headerLines = renderFrame(animatedHeaderFrame);
|
|
522
|
+
};
|
|
523
|
+
const render = () => {
|
|
524
|
+
headerLines = renderFrame(animatedHeaderFrame);
|
|
525
|
+
};
|
|
526
|
+
render();
|
|
527
|
+
tuiRef?.requestRender();
|
|
528
|
+
animatedHeaderTimer = setInterval(() => {
|
|
529
|
+
animatedHeaderFrame += 1;
|
|
530
|
+
render();
|
|
531
|
+
tuiRef?.requestRender();
|
|
532
|
+
}, 120);
|
|
533
|
+
animatedHeaderTimer.unref?.();
|
|
534
|
+
};
|
|
535
|
+
|
|
459
536
|
let loadStarted = false;
|
|
460
537
|
|
|
461
538
|
const startReadyHeader = () => {
|
|
@@ -470,33 +547,23 @@ export default function (pi: ExtensionAPI, options?: { enableFrame?: boolean })
|
|
|
470
547
|
if (!hasSelectedModel) setupHints.push("/model");
|
|
471
548
|
const setupLines = renderSetupLines(setupHints, ctx.ui.theme);
|
|
472
549
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
"",
|
|
481
|
-
];
|
|
482
|
-
};
|
|
483
|
-
renderLoadingHeader();
|
|
484
|
-
const loaderTimer = setInterval(() => {
|
|
485
|
-
loaderFrame += 1;
|
|
486
|
-
renderLoadingHeader();
|
|
487
|
-
tuiRef?.requestRender();
|
|
488
|
-
}, 120);
|
|
489
|
-
loaderTimer.unref?.();
|
|
490
|
-
tuiRef?.requestRender();
|
|
550
|
+
startAnimatedHeader((frame) => [
|
|
551
|
+
"",
|
|
552
|
+
renderTitle(ctx.ui.theme),
|
|
553
|
+
"",
|
|
554
|
+
...renderCoinLoaderLines(frame, ctx.ui.theme),
|
|
555
|
+
"",
|
|
556
|
+
]);
|
|
491
557
|
|
|
492
558
|
const todayPromise = fetchTodayOn11am();
|
|
493
559
|
void Promise.all([
|
|
494
560
|
getData(),
|
|
495
561
|
withTimeout(todayPromise, TODAY_PREFETCH_TIMEOUT_MS, null),
|
|
496
562
|
]).then(([{ weather, market }, todayOn11am]) => {
|
|
497
|
-
|
|
563
|
+
stopAnimatedHeader();
|
|
498
564
|
const renderReadyHeader = (today: TodayOn11am | null) => {
|
|
499
565
|
const theme = ctx.ui.theme;
|
|
566
|
+
const updateLines = renderUpdateLines(theme);
|
|
500
567
|
const weatherLine = ` ${weather.icon} ${theme.fg("text", weather.temp)} ${theme.fg("dim", weather.condition)} ${theme.fg("dim", "·")} ${theme.fg("dim", weather.location)}`;
|
|
501
568
|
const marketLine = ` ${market.map((quote) => formatQuote(quote, theme)).join(` ${theme.fg("dim", "·")} `)}`;
|
|
502
569
|
const todayLines = renderTodayOn11amLines(today, theme);
|
|
@@ -508,12 +575,16 @@ export default function (pi: ExtensionAPI, options?: { enableFrame?: boolean })
|
|
|
508
575
|
weatherLine,
|
|
509
576
|
marketLine,
|
|
510
577
|
"",
|
|
578
|
+
...updateLines,
|
|
579
|
+
...(updateLines.length ? [""] : []),
|
|
511
580
|
...todayLines,
|
|
512
581
|
...(todayLines.length ? [""] : []),
|
|
513
582
|
...setupLines,
|
|
583
|
+
...(setupLines.length ? [""] : []),
|
|
514
584
|
"",
|
|
515
585
|
];
|
|
516
586
|
};
|
|
587
|
+
repaintHeader = () => renderReadyHeader(uiState.todayOn11am);
|
|
517
588
|
|
|
518
589
|
renderReadyHeader(todayOn11am);
|
|
519
590
|
uiState.ready = true;
|
|
@@ -528,14 +599,21 @@ export default function (pi: ExtensionAPI, options?: { enableFrame?: boolean })
|
|
|
528
599
|
}).catch(() => {});
|
|
529
600
|
}
|
|
530
601
|
}).catch(() => {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
602
|
+
stopAnimatedHeader();
|
|
603
|
+
repaintHeader = () => {
|
|
604
|
+
const updateLines = renderUpdateLines(ctx.ui.theme);
|
|
605
|
+
headerLines = [
|
|
606
|
+
"",
|
|
607
|
+
renderTitle(ctx.ui.theme),
|
|
608
|
+
"",
|
|
609
|
+
...updateLines,
|
|
610
|
+
...(updateLines.length ? [""] : []),
|
|
611
|
+
...setupLines,
|
|
612
|
+
...(setupLines.length ? [""] : []),
|
|
613
|
+
"",
|
|
614
|
+
];
|
|
615
|
+
};
|
|
616
|
+
repaintHeader();
|
|
539
617
|
uiState.ready = true;
|
|
540
618
|
ctx.ui.setEditorText("");
|
|
541
619
|
tuiRef?.requestRender();
|
|
@@ -543,10 +621,28 @@ export default function (pi: ExtensionAPI, options?: { enableFrame?: boolean })
|
|
|
543
621
|
};
|
|
544
622
|
|
|
545
623
|
const renderCurrentHeader = () => {
|
|
624
|
+
const gate = getAuthGateState();
|
|
625
|
+
if (gate.status === "auth_in_progress") {
|
|
626
|
+
if (hasSeedclubCredential()) {
|
|
627
|
+
startReadyHeader();
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
loadStarted = false;
|
|
631
|
+
uiState.todayOn11am = null;
|
|
632
|
+
startAnimatedHeader((frame) => renderAuthCheckingLines(ctx.ui.theme, frame));
|
|
633
|
+
uiState.ready = true;
|
|
634
|
+
ctx.ui.setEditorText("");
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
|
|
546
638
|
if (isAuthGateBlocking()) {
|
|
639
|
+
stopAnimatedHeader();
|
|
547
640
|
loadStarted = false;
|
|
548
641
|
uiState.todayOn11am = null;
|
|
549
|
-
|
|
642
|
+
repaintHeader = () => {
|
|
643
|
+
headerLines = renderAuthGateLines(ctx.ui.theme);
|
|
644
|
+
};
|
|
645
|
+
repaintHeader();
|
|
550
646
|
uiState.ready = true;
|
|
551
647
|
ctx.ui.setEditorText("");
|
|
552
648
|
tuiRef?.requestRender();
|
|
@@ -571,10 +667,6 @@ export default function (pi: ExtensionAPI, options?: { enableFrame?: boolean })
|
|
|
571
667
|
applyTerminalTitle(ctx);
|
|
572
668
|
});
|
|
573
669
|
|
|
574
|
-
pi.on("session_switch", (_event, ctx) => {
|
|
575
|
-
applyTerminalTitle(ctx);
|
|
576
|
-
});
|
|
577
|
-
|
|
578
670
|
pi.on("before_agent_start", async (event) => {
|
|
579
671
|
const today = uiState.todayOn11am;
|
|
580
672
|
if (!today?.guests.length) return;
|
|
@@ -627,8 +719,9 @@ ${rows.join("\n")}`,
|
|
|
627
719
|
const commands = pi.getCommands();
|
|
628
720
|
const extPaths = new Map<string, string[]>();
|
|
629
721
|
for (const cmd of commands) {
|
|
630
|
-
|
|
631
|
-
|
|
722
|
+
const cmdPath = cmd.sourceInfo?.path;
|
|
723
|
+
if (cmd.source === "extension" && cmdPath) {
|
|
724
|
+
const name = cmdPath.replace(/.*\/extensions\//, "").replace(/\.ts$/, "");
|
|
632
725
|
if (!extPaths.has(name)) extPaths.set(name, []);
|
|
633
726
|
extPaths.get(name)!.push(cmd.name);
|
|
634
727
|
}
|
package/package.json
CHANGED