@clubnet/seedclub 0.2.39 → 0.2.40
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/index.ts +2 -2
- package/assets/extensions/seedclub/memory.ts +12 -1
- package/assets/extensions/seedclub/tools/{deal-sourcing.ts → opportunity-research.ts} +30 -28
- package/assets/extensions/seedclub/ui-copy.ts +2 -0
- package/package.json +1 -1
- package/packages/seedclub-tui/src/app/interactive-mode.mjs +42 -4
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
} from "./recent-entities.js";
|
|
33
33
|
import { getCurrentUser, getSessionContext, registerUtilityTools } from "./tools/utility.js";
|
|
34
34
|
import { registerCrmTools } from "./tools/crm.js";
|
|
35
|
-
import {
|
|
35
|
+
import { registerOpportunityResearchTools } from "./tools/opportunity-research.js";
|
|
36
36
|
import { registerMeetingTools } from "./tools/meetings.js";
|
|
37
37
|
import { registerMediaTools } from "./tools/media.js";
|
|
38
38
|
import { registerWebTools } from "./tools/web.js";
|
|
@@ -209,7 +209,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
209
209
|
// Tools
|
|
210
210
|
registerUtilityTools(pi);
|
|
211
211
|
registerCrmTools(pi);
|
|
212
|
-
|
|
212
|
+
registerOpportunityResearchTools(pi);
|
|
213
213
|
registerMeetingTools(pi);
|
|
214
214
|
registerMediaTools(pi);
|
|
215
215
|
registerWebTools(pi);
|
|
@@ -27,6 +27,12 @@ const MEMORY_WRITE_TIMEOUT_MS = 20000;
|
|
|
27
27
|
const MEMORY_SESSION_STRATEGY = String(process.env.SEEDCLUB_MEMORY_SESSION_STRATEGY ?? "repo").toLowerCase();
|
|
28
28
|
const MEMORY_DEFAULT_ENABLED_KEY = "seedclubMemoryDefaultEnabled";
|
|
29
29
|
const MEMORY_DEFAULT_APPLIED_KEY = "seedclubMemoryDefaultApplied";
|
|
30
|
+
const MEMORY_COMMAND_COMPLETIONS = [
|
|
31
|
+
{ value: "status", label: "status", description: "Show memory status" },
|
|
32
|
+
{ value: "recent", label: "recent", description: "Show recent memory" },
|
|
33
|
+
{ value: "on", label: "on", description: "Turn memory on" },
|
|
34
|
+
{ value: "off", label: "off", description: "Turn memory off" },
|
|
35
|
+
];
|
|
30
36
|
|
|
31
37
|
type MemoryStatus = {
|
|
32
38
|
available: boolean;
|
|
@@ -318,9 +324,14 @@ export function registerMemory(pi: ExtensionAPI): MemoryController {
|
|
|
318
324
|
|
|
319
325
|
pi.registerCommand("memory", {
|
|
320
326
|
description: "Show Seed Club memory status, recent memory, or turn memory on/off (`status`, `recent`, `on`, `off`)",
|
|
327
|
+
getArgumentCompletions: (argumentPrefix) => {
|
|
328
|
+
const prefix = argumentPrefix.trim().toLowerCase();
|
|
329
|
+
const completions = MEMORY_COMMAND_COMPLETIONS.filter((item) => item.value.startsWith(prefix));
|
|
330
|
+
return completions.length ? completions : null;
|
|
331
|
+
},
|
|
321
332
|
handler: async (args, ctx) => {
|
|
322
333
|
const trimmedArgs = args.trim();
|
|
323
|
-
const [rawAction = "menu", ...rest] = trimmedArgs.split(/\s+/);
|
|
334
|
+
const [rawAction = "menu", ...rest] = trimmedArgs ? trimmedArgs.split(/\s+/) : ["menu"];
|
|
324
335
|
const action = rawAction.toLowerCase();
|
|
325
336
|
const actionArgs = rest.join(" ");
|
|
326
337
|
if (action === "status") {
|
|
@@ -6,9 +6,9 @@ import { ApiError, api, NotConnectedError } from "../api-client.js";
|
|
|
6
6
|
import { makeProgressCallRenderer, makeProgressResultRenderer } from "../tool-utils.js";
|
|
7
7
|
import { getToolCallLabel, getToolSuccessLabel } from "../ui-copy.js";
|
|
8
8
|
|
|
9
|
-
type
|
|
9
|
+
type OpportunityResearchDecision = "pushing" | "passing" | "holding";
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const DEFAULT_OPPORTUNITY_RESEARCH_PROGRAM_SLUG = "seed-network";
|
|
12
12
|
const MAX_DOCUMENT_PREVIEW_CHARS = 12000;
|
|
13
13
|
const MAX_IMAGE_PREVIEW_BYTES = 8 * 1024 * 1024;
|
|
14
14
|
|
|
@@ -150,7 +150,7 @@ function suggestedReviewFields() {
|
|
|
150
150
|
return [
|
|
151
151
|
"Founder info",
|
|
152
152
|
"Company info",
|
|
153
|
-
"
|
|
153
|
+
"Opportunity info, including ask or explicit ask unknown",
|
|
154
154
|
"Opportunity Summary",
|
|
155
155
|
"Founder/contact path or explicit unknown",
|
|
156
156
|
"Member relationship and excitement/vouch before push",
|
|
@@ -181,14 +181,16 @@ function inferDocumentContentType(fileName: string) {
|
|
|
181
181
|
return "application/octet-stream";
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
function normalizeDecision(value: unknown):
|
|
184
|
+
function normalizeDecision(value: unknown): OpportunityResearchDecision | null {
|
|
185
185
|
if (value === "pushing" || value === "passing" || value === "holding") return value;
|
|
186
186
|
return null;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
async function submitDraftPosition(programSlug: string, draftId: string, args: any, decision:
|
|
190
|
-
return api.post<any>(`/programs/${programSlug}/
|
|
189
|
+
async function submitDraftPosition(programSlug: string, draftId: string, args: any, decision: OpportunityResearchDecision) {
|
|
190
|
+
return api.post<any>(`/programs/${programSlug}/opportunity-research/drafts/${draftId}/member-position`, {
|
|
191
191
|
ask_unknown: args.askUnknown === true,
|
|
192
|
+
company_name: normalizeOptionalString(args.companyName),
|
|
193
|
+
company_website_url: normalizeOptionalString(args.companyWebsiteUrl),
|
|
192
194
|
contact_info: normalizeOptionalString(args.contactInfo),
|
|
193
195
|
contact_unknown: args.contactUnknown === true,
|
|
194
196
|
decision,
|
|
@@ -202,7 +204,7 @@ async function submitDraftPosition(programSlug: string, draftId: string, args: a
|
|
|
202
204
|
});
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
async function
|
|
207
|
+
async function submitOpportunityResearchDocument(args: {
|
|
206
208
|
askUnknown?: boolean | null;
|
|
207
209
|
programSlug?: string | null;
|
|
208
210
|
filePath?: string | null;
|
|
@@ -212,7 +214,7 @@ async function submitDealSourcingDeck(args: {
|
|
|
212
214
|
contentType?: string | null;
|
|
213
215
|
contactInfo?: string | null;
|
|
214
216
|
contactUnknown?: boolean | null;
|
|
215
|
-
decision?:
|
|
217
|
+
decision?: OpportunityResearchDecision | null;
|
|
216
218
|
draftId?: string | null;
|
|
217
219
|
excitementReason?: string | null;
|
|
218
220
|
fileName?: string | null;
|
|
@@ -227,15 +229,15 @@ async function submitDealSourcingDeck(args: {
|
|
|
227
229
|
vouchText?: string | null;
|
|
228
230
|
}, runtime?: any) {
|
|
229
231
|
try {
|
|
230
|
-
const programSlug = normalizeOptionalString(args.programSlug) ??
|
|
232
|
+
const programSlug = normalizeOptionalString(args.programSlug) ?? DEFAULT_OPPORTUNITY_RESEARCH_PROGRAM_SLUG;
|
|
231
233
|
const existingDraftId = normalizeOptionalString(args.draftId);
|
|
232
234
|
const existingDecision = normalizeDecision(args.decision);
|
|
233
235
|
if (existingDraftId) {
|
|
234
236
|
if (!existingDecision) {
|
|
235
|
-
return { draft: { id: existingDraftId }, next_action: "Ask the member whether they want to push
|
|
237
|
+
return { draft: { id: existingDraftId }, next_action: "Ask the member whether they want to push this opportunity for review, pass, or hold it for later." };
|
|
236
238
|
}
|
|
237
239
|
|
|
238
|
-
runtime?.setWorkingMessage?.("Saving
|
|
240
|
+
runtime?.setWorkingMessage?.("Saving opportunity draft position...");
|
|
239
241
|
const positionResponse = await submitDraftPosition(programSlug, existingDraftId, args, existingDecision);
|
|
240
242
|
return { draft: { id: existingDraftId }, draft_position_result: positionResponse };
|
|
241
243
|
}
|
|
@@ -253,8 +255,8 @@ async function submitDealSourcingDeck(args: {
|
|
|
253
255
|
const fileName = normalizeOptionalString(args.fileName) ?? basename(filePath);
|
|
254
256
|
const contentType = normalizeOptionalString(args.contentType) ?? inferDocumentContentType(fileName);
|
|
255
257
|
const assetKind = normalizeOptionalString(args.assetKind) ?? "deck";
|
|
256
|
-
runtime?.setWorkingMessage?.("Requesting
|
|
257
|
-
const ingestResponse = await api.post<any>(`/programs/${programSlug}/
|
|
258
|
+
runtime?.setWorkingMessage?.("Requesting research document upload target...");
|
|
259
|
+
const ingestResponse = await api.post<any>(`/programs/${programSlug}/opportunity-research/document-ingests`, {
|
|
258
260
|
asset_kind: assetKind,
|
|
259
261
|
content_type: contentType,
|
|
260
262
|
file_name: fileName,
|
|
@@ -267,10 +269,10 @@ async function submitDealSourcingDeck(args: {
|
|
|
267
269
|
const uploadId = typeof ingestResponse?.upload?.id === "string" ? ingestResponse.upload.id : null;
|
|
268
270
|
|
|
269
271
|
if (!uploadUrl || !objectKey || !uploadId) {
|
|
270
|
-
return { error: "The API did not return a valid
|
|
272
|
+
return { error: "The API did not return a valid research document upload target.", response: ingestResponse };
|
|
271
273
|
}
|
|
272
274
|
|
|
273
|
-
runtime?.setWorkingMessage?.("Uploading
|
|
275
|
+
runtime?.setWorkingMessage?.("Uploading research document...");
|
|
274
276
|
const bytes = await readFile(filePath);
|
|
275
277
|
const documentPreview = await buildDocumentPreview(fileName, contentType, bytes);
|
|
276
278
|
const uploadHeaders: Record<string, string> = {};
|
|
@@ -296,7 +298,7 @@ async function submitDealSourcingDeck(args: {
|
|
|
296
298
|
}
|
|
297
299
|
|
|
298
300
|
runtime?.setWorkingMessage?.("Creating private research draft...");
|
|
299
|
-
const completeResponse = await api.post<any>(`/programs/${programSlug}/
|
|
301
|
+
const completeResponse = await api.post<any>(`/programs/${programSlug}/opportunity-research/document-ingests/${uploadId}/complete`, {
|
|
300
302
|
asset_kind: assetKind,
|
|
301
303
|
company_name: normalizeOptionalString(args.companyName),
|
|
302
304
|
company_website_url: normalizeOptionalString(args.companyWebsiteUrl),
|
|
@@ -317,8 +319,8 @@ async function submitDealSourcingDeck(args: {
|
|
|
317
319
|
decision_ignored_until_review: Boolean(decision),
|
|
318
320
|
document_preview: documentPreview,
|
|
319
321
|
next_action: decision
|
|
320
|
-
? "The document is saved as a private draft. Review the material, summarize Founder info, Company info,
|
|
321
|
-
: "Review the material, summarize Founder info, Company info,
|
|
322
|
+
? "The document is saved as a private draft. Review the material, summarize Founder info, Company info, Opportunity info, and Opportunity Summary, then ask missing questions before submitting the member decision."
|
|
323
|
+
: "Review the material, summarize Founder info, Company info, Opportunity info, and Opportunity Summary, then ask whether the member wants to push, pass, or hold after missing context is resolved.",
|
|
322
324
|
suggested_review_fields: suggestedReviewFields(),
|
|
323
325
|
upload: {
|
|
324
326
|
...completeResponse?.upload,
|
|
@@ -331,7 +333,7 @@ async function submitDealSourcingDeck(args: {
|
|
|
331
333
|
}
|
|
332
334
|
}
|
|
333
335
|
|
|
334
|
-
function
|
|
336
|
+
function makeOpportunityResearchExecute(fn: typeof submitOpportunityResearchDocument) {
|
|
335
337
|
return async (_toolCallId: string, params: any, _signal: any, _onUpdate: any, ctx: any) => {
|
|
336
338
|
const runtime = {
|
|
337
339
|
ctx,
|
|
@@ -385,15 +387,15 @@ function makeDealSourcingExecute(fn: typeof submitDealSourcingDeck) {
|
|
|
385
387
|
};
|
|
386
388
|
}
|
|
387
389
|
|
|
388
|
-
export function
|
|
390
|
+
export function registerOpportunityResearchTools(pi: ExtensionAPI) {
|
|
389
391
|
pi.registerTool({
|
|
390
|
-
name: "
|
|
391
|
-
label: "
|
|
392
|
+
name: "seedclub_upload_opportunity_document",
|
|
393
|
+
label: "Upload Opportunity Document",
|
|
392
394
|
description:
|
|
393
|
-
"Upload a local deck, memo, document, or image to seedclub-api as a private/team-private
|
|
395
|
+
"Upload a local deck, memo, document, or image to seedclub-api as a private/team-private opportunity research draft, or record pushing/passing/holding for an existing draftId. programSlug defaults to seed-network. For a plain deck upload or a prompt like 'let's chat about this opportunity', create the private draft, read the preview, and discuss the opportunity without implying it has been submitted for review. Initial uploads must not push immediately. After reviewing and confirming with the member, use draftId plus a decision. If decision is pushing, provide founderNames or founderUnknown, founderRelationship, excitementReason or vouchText, contactInfo or contactUnknown, and targetAmount or askUnknown.",
|
|
394
396
|
parameters: Type.Object({
|
|
395
397
|
askUnknown: Type.Optional(Type.Union([Type.Boolean(), Type.Null()], { description: "Set true when the member explicitly says the round ask is unknown." })),
|
|
396
|
-
programSlug: Type.Optional(Type.Union([Type.String({ description: "Program slug for the
|
|
398
|
+
programSlug: Type.Optional(Type.Union([Type.String({ description: "Program slug for the opportunity research program. Defaults to seed-network." }), Type.Null()])),
|
|
397
399
|
filePath: Type.Optional(Type.Union([Type.String({ description: "Local deck, memo, document, or image path. Required unless draftId is provided." }), Type.Null()])),
|
|
398
400
|
assetKind: Type.Optional(Type.Union([Type.Literal("deck"), Type.Literal("memo"), Type.Literal("supporting_material"), Type.Null()])),
|
|
399
401
|
companyName: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
@@ -402,7 +404,7 @@ export function registerDealSourcingTools(pi: ExtensionAPI) {
|
|
|
402
404
|
contactInfo: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Founder/company contact info supplied by the member if not present in the uploaded material." })),
|
|
403
405
|
contactUnknown: Type.Optional(Type.Union([Type.Boolean(), Type.Null()], { description: "Set true when the member explicitly says contact info is unknown." })),
|
|
404
406
|
decision: Type.Optional(Type.Union([Type.Literal("pushing"), Type.Literal("passing"), Type.Literal("holding"), Type.Null()])),
|
|
405
|
-
draftId: Type.Optional(Type.Union([Type.String({ description: "Existing private
|
|
407
|
+
draftId: Type.Optional(Type.Union([Type.String({ description: "Existing private opportunity draft id to update without re-uploading the file." }), Type.Null()])),
|
|
406
408
|
excitementReason: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Why the member is excited about the company/founder." })),
|
|
407
409
|
fileName: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
408
410
|
flaggedTopics: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()])),
|
|
@@ -415,9 +417,9 @@ export function registerDealSourcingTools(pi: ExtensionAPI) {
|
|
|
415
417
|
valuation: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
|
416
418
|
vouchText: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
417
419
|
}),
|
|
418
|
-
execute:
|
|
419
|
-
renderCall: makeProgressCallRenderer(getToolCallLabel("
|
|
420
|
-
renderResult: makeProgressResultRenderer(getToolSuccessLabel("
|
|
420
|
+
execute: makeOpportunityResearchExecute(submitOpportunityResearchDocument),
|
|
421
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_upload_opportunity_document"), (args) => args?.companyName || args?.draftId || args?.filePath || undefined),
|
|
422
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_upload_opportunity_document"), (details) => {
|
|
421
423
|
const company = details?.company?.name ?? details?.draft_position_result?.funding_round?.organization_id ?? null;
|
|
422
424
|
const state = details?.draft_position_result?.funding_round?.lifecycle_state ?? details?.draft?.status ?? details?.draft_position_result?.draft?.status ?? null;
|
|
423
425
|
const id = details?.draft_position_result?.funding_round?.id ?? details?.draft?.id ?? details?.draft_position_result?.draft?.id ?? null;
|
|
@@ -11,6 +11,7 @@ export const TOOL_CALL_LABELS: Record<string, string> = {
|
|
|
11
11
|
seedclub_create_crm_task: "Creating CRM task",
|
|
12
12
|
seedclub_save_research: "Saving research",
|
|
13
13
|
seedclub_research_opportunity: "Researching opportunity",
|
|
14
|
+
seedclub_upload_opportunity_document: "Uploading opportunity document",
|
|
14
15
|
seedclub_get_research: "Loading research",
|
|
15
16
|
seedclub_list_program_contacts: "Loading program contacts",
|
|
16
17
|
seedclub_list_program_funnels: "Loading program funnels",
|
|
@@ -62,6 +63,7 @@ export const TOOL_SUCCESS_LABELS: Record<string, string> = {
|
|
|
62
63
|
seedclub_create_crm_task: "CRM task created",
|
|
63
64
|
seedclub_save_research: "Research saved",
|
|
64
65
|
seedclub_research_opportunity: "Research queued",
|
|
66
|
+
seedclub_upload_opportunity_document: "Opportunity draft ready",
|
|
65
67
|
seedclub_get_research: "Research loaded",
|
|
66
68
|
seedclub_list_program_contacts: "Program contacts loaded",
|
|
67
69
|
seedclub_list_program_funnels: "Program funnels loaded",
|
package/package.json
CHANGED
|
@@ -959,6 +959,13 @@ function shouldShowCommandInList(name) {
|
|
|
959
959
|
return !name.startsWith("skill:");
|
|
960
960
|
}
|
|
961
961
|
|
|
962
|
+
const MEMORY_COMMAND_COMPLETIONS = [
|
|
963
|
+
{ value: "status", label: "status", description: "Show memory status" },
|
|
964
|
+
{ value: "recent", label: "recent", description: "Show recent memory" },
|
|
965
|
+
{ value: "on", label: "on", description: "Turn memory on" },
|
|
966
|
+
{ value: "off", label: "off", description: "Turn memory off" },
|
|
967
|
+
];
|
|
968
|
+
|
|
962
969
|
function getBuiltInSlashCommands() {
|
|
963
970
|
return [
|
|
964
971
|
{ name: "new", description: "Start a new session" },
|
|
@@ -967,13 +974,22 @@ function getBuiltInSlashCommands() {
|
|
|
967
974
|
{ name: "calendar", description: "Open calendar connect and availability actions" },
|
|
968
975
|
{ name: "research", description: "Open a Seed Club research prompt" },
|
|
969
976
|
{ name: "source", description: "Open a source-backed research prompt" },
|
|
970
|
-
{
|
|
977
|
+
{
|
|
978
|
+
name: "memory",
|
|
979
|
+
description: "Show Seed Club memory status, recent memory, or turn memory on/off",
|
|
980
|
+
getArgumentCompletions: (argumentPrefix) => {
|
|
981
|
+
const prefix = argumentPrefix.trim().toLowerCase();
|
|
982
|
+
const completions = MEMORY_COMMAND_COMPLETIONS.filter((item) => item.value.startsWith(prefix));
|
|
983
|
+
return completions.length ? completions : null;
|
|
984
|
+
},
|
|
985
|
+
},
|
|
971
986
|
{ name: "worldview", description: "Open an investing worldview prompt" },
|
|
972
987
|
{ name: "theses", description: "Open an investment theses prompt" },
|
|
973
988
|
{ name: "concerns", description: "Open an investment concerns prompt" },
|
|
974
989
|
{ name: "model", description: "Select the current model" },
|
|
975
990
|
{ name: "login", description: "Log into a model provider" },
|
|
976
991
|
{ name: "logout", description: "Log out of a model provider" },
|
|
992
|
+
{ name: "update", description: "Show the seedclub package update menu" },
|
|
977
993
|
{ name: "more", description: "Show everything else" },
|
|
978
994
|
{ name: "quit", description: "Exit seedclub" },
|
|
979
995
|
];
|
|
@@ -984,10 +1000,23 @@ function getAuxiliaryBuiltInSlashCommands() {
|
|
|
984
1000
|
{ name: "commands", description: "List the primary command menu" },
|
|
985
1001
|
{ name: "extensions", description: "List loaded extensions" },
|
|
986
1002
|
{ name: "reload", description: "Reload keybindings, extensions, prompts, and tools" },
|
|
987
|
-
{ name: "update", description: "Show the seedclub package update menu" },
|
|
988
1003
|
];
|
|
989
1004
|
}
|
|
990
1005
|
|
|
1006
|
+
function getPrimaryExtensionCommandNames() {
|
|
1007
|
+
return new Set([
|
|
1008
|
+
"calendar",
|
|
1009
|
+
"concerns",
|
|
1010
|
+
"memory",
|
|
1011
|
+
"research",
|
|
1012
|
+
"seedclub",
|
|
1013
|
+
"seedenv",
|
|
1014
|
+
"source",
|
|
1015
|
+
"theses",
|
|
1016
|
+
"worldview",
|
|
1017
|
+
]);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
991
1020
|
class SeedclubSurfaceEditor extends CustomEditor {
|
|
992
1021
|
constructor(tui, editorTheme, keybindings, options) {
|
|
993
1022
|
super(tui, editorTheme, keybindings, {
|
|
@@ -1312,7 +1341,7 @@ export class SeedclubInteractiveModeApp {
|
|
|
1312
1341
|
configureAutocomplete() {
|
|
1313
1342
|
const builtInCommands = getBuiltInSlashCommands();
|
|
1314
1343
|
const builtInCommandNames = new Set(builtInCommands.map((command) => command.name));
|
|
1315
|
-
const extraAutocompleteCommands =
|
|
1344
|
+
const extraAutocompleteCommands = getPrimaryExtensionCommandNames();
|
|
1316
1345
|
const extensionCommands = this.session.extensionRunner?.getRegisteredCommands?.()
|
|
1317
1346
|
.filter((command) => !builtInCommandNames.has(command.invocationName))
|
|
1318
1347
|
.filter((command) => extraAutocompleteCommands.has(command.invocationName))
|
|
@@ -1340,7 +1369,16 @@ export class SeedclubInteractiveModeApp {
|
|
|
1340
1369
|
|
|
1341
1370
|
showCommandsList() {
|
|
1342
1371
|
const builtInCommands = getBuiltInSlashCommands();
|
|
1343
|
-
const
|
|
1372
|
+
const builtInCommandNames = new Set(builtInCommands.map((command) => command.name));
|
|
1373
|
+
const primaryExtensionCommandNames = getPrimaryExtensionCommandNames();
|
|
1374
|
+
const extensionCommands = this.session.extensionRunner?.getRegisteredCommands?.()
|
|
1375
|
+
.filter((command) => !builtInCommandNames.has(command.invocationName))
|
|
1376
|
+
.filter((command) => primaryExtensionCommandNames.has(command.invocationName))
|
|
1377
|
+
.map((command) => ({
|
|
1378
|
+
name: command.invocationName,
|
|
1379
|
+
description: command.description || "",
|
|
1380
|
+
})) ?? [];
|
|
1381
|
+
const lines = [...builtInCommands, ...extensionCommands]
|
|
1344
1382
|
.filter((command) => shouldShowCommandInList(command.name))
|
|
1345
1383
|
.map((command) => `- \`/${command.name}\`${command.description ? ` ${command.description}` : ""}`)
|
|
1346
1384
|
.join("\n");
|