@clubnet/seedclub 0.2.19 → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -14
- package/assets/extensions/seedclub/tools/meetings.ts +262 -0
- package/assets/extensions/seedclub-ui/welcome.ts +30 -18
- package/bin/cli.js +8 -117
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,21 +46,11 @@ curl -fsSL https://raw.githubusercontent.com/seedclub/seedclub-agent/main/instal
|
|
|
46
46
|
|
|
47
47
|
`@clubnet/seedclub` is currently a private npm package. This auth is only for installing or updating the package from npm. It is separate from `/login` and `/connect` inside the app.
|
|
48
48
|
|
|
49
|
-
Fast path:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
SEEDCLUB_NPM_TOKEN=YOUR_NPM_TOKEN curl -fsSL https://raw.githubusercontent.com/seedclub/seedclub-agent/main/install.sh | bash
|
|
53
|
-
seedclub setup-auth
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
Manual one-time `.npmrc` setup:
|
|
57
|
-
|
|
58
49
|
```bash
|
|
59
|
-
|
|
60
|
-
echo "//registry.npmjs.org/:_authToken=YOUR_NPM_TOKEN" >> ~/.npmrc
|
|
50
|
+
npm login
|
|
61
51
|
```
|
|
62
52
|
|
|
63
|
-
Then `npm install -g @clubnet/seedclub`
|
|
53
|
+
Then `npm install -g @clubnet/seedclub` and `seedclub update` work.
|
|
64
54
|
|
|
65
55
|
## Core workflow
|
|
66
56
|
|
|
@@ -80,7 +70,6 @@ The normal interactive flow is:
|
|
|
80
70
|
| `/connect` | Connect your Seed Club account |
|
|
81
71
|
| `/seedclub` | Main menu — connect, inspect access, and jump into CRM/meetings/media/headlines workflows |
|
|
82
72
|
| `/transcripts` | Export transcript VTT files with filters (date, person, time, output dir) |
|
|
83
|
-
| `seedclub setup-auth` | Configure npm auth for npmjs private package access in `~/.npmrc` |
|
|
84
73
|
|
|
85
74
|
Natural-language transcript retrieval is also supported (no slash command required). Examples: `download vibhu transcripts from 11am`, `i need transcripts for all guests on 11am last week`. Seed Club will run metadata-first export confirmation and then write VTT files.
|
|
86
75
|
|
|
@@ -147,7 +136,7 @@ seedclub pins versions in `package.json`:
|
|
|
147
136
|
|
|
148
137
|
```json
|
|
149
138
|
{
|
|
150
|
-
"version": "0.2.
|
|
139
|
+
"version": "0.2.19",
|
|
151
140
|
"dependencies": {
|
|
152
141
|
"@mariozechner/pi-coding-agent": "0.65.2"
|
|
153
142
|
}
|
|
@@ -701,6 +701,231 @@ async function listShowRecordings(args: {
|
|
|
701
701
|
}
|
|
702
702
|
}
|
|
703
703
|
|
|
704
|
+
function todayUtcDate() {
|
|
705
|
+
return new Date().toISOString().slice(0, 10);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
function normalizeDateCutoff(value?: string) {
|
|
709
|
+
if (!value || typeof value !== "string") return todayUtcDate();
|
|
710
|
+
const trimmed = value.trim();
|
|
711
|
+
if (!trimmed) return todayUtcDate();
|
|
712
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(trimmed)) return trimmed;
|
|
713
|
+
const parsed = new Date(trimmed);
|
|
714
|
+
if (!Number.isFinite(parsed.getTime())) return todayUtcDate();
|
|
715
|
+
return parsed.toISOString().slice(0, 10);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
function normalizeGuestNeedle(value?: string | null) {
|
|
719
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function includesNeedle(haystack: unknown, needle: string) {
|
|
723
|
+
if (!needle) return false;
|
|
724
|
+
if (typeof haystack !== "string") return false;
|
|
725
|
+
return haystack.trim().toLowerCase().includes(needle);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function isOnOrBeforeDate(value: string | null | undefined, beforeDate: string) {
|
|
729
|
+
if (!value || typeof value !== "string") return false;
|
|
730
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
731
|
+
return value <= beforeDate;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function transcriptMeetingPartyId(row: any) {
|
|
735
|
+
return (
|
|
736
|
+
row?.meeting?.primary_contact?.party?.id ??
|
|
737
|
+
row?.meeting?.primary_party?.id ??
|
|
738
|
+
row?.meeting?.primary_party_id ??
|
|
739
|
+
null
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function transcriptMeetingGuestLabel(row: any) {
|
|
744
|
+
return (
|
|
745
|
+
row?.transcript_for ??
|
|
746
|
+
row?.meeting?.primary_contact?.party?.display_name ??
|
|
747
|
+
row?.meeting?.primary_contact?.person?.full_name ??
|
|
748
|
+
personFromMeetingTitle(row?.meeting?.title) ??
|
|
749
|
+
null
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function sortByEventDateDesc<T>(rows: T[], eventDatePicker: (row: T) => string | null | undefined) {
|
|
754
|
+
return [...rows].sort((a, b) => {
|
|
755
|
+
const aDate = eventDatePicker(a) ?? "";
|
|
756
|
+
const bDate = eventDatePicker(b) ?? "";
|
|
757
|
+
if (aDate === bDate) return 0;
|
|
758
|
+
return aDate < bDate ? 1 : -1;
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
async function findLatestGuestTranscript(args: {
|
|
763
|
+
programSlug: string;
|
|
764
|
+
guest: string;
|
|
765
|
+
beforeDate?: string;
|
|
766
|
+
needVtt?: boolean;
|
|
767
|
+
mode?: "compact" | "detail";
|
|
768
|
+
}) {
|
|
769
|
+
try {
|
|
770
|
+
const beforeDate = normalizeDateCutoff(args.beforeDate);
|
|
771
|
+
const needVtt = args.needVtt === true;
|
|
772
|
+
const mode = args.mode === "detail" ? "detail" : "compact";
|
|
773
|
+
const guestNeedle = normalizeGuestNeedle(args.guest);
|
|
774
|
+
if (!guestNeedle) {
|
|
775
|
+
return {
|
|
776
|
+
query: {
|
|
777
|
+
programSlug: args.programSlug,
|
|
778
|
+
guest: args.guest,
|
|
779
|
+
beforeDate,
|
|
780
|
+
needVtt,
|
|
781
|
+
mode,
|
|
782
|
+
},
|
|
783
|
+
found: false,
|
|
784
|
+
result: null,
|
|
785
|
+
reason: "Guest is required.",
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const guestProfile = await getGuestProfile({
|
|
790
|
+
programSlug: args.programSlug,
|
|
791
|
+
search: args.guest,
|
|
792
|
+
includeCrm: false,
|
|
793
|
+
});
|
|
794
|
+
const resolvedPartyId = guestProfile?.contact?.partyId ?? null;
|
|
795
|
+
const resolvedGuestName = guestProfile?.contact?.displayName ?? args.guest;
|
|
796
|
+
const resolvedGuestEmail = guestProfile?.contact?.email ?? null;
|
|
797
|
+
|
|
798
|
+
let mediaCandidate: any = null;
|
|
799
|
+
if (resolvedPartyId) {
|
|
800
|
+
const mediaResponse = await api.get<any>(`/programs/${args.programSlug}/media/assets`, {
|
|
801
|
+
asset_kind: "full_conversation",
|
|
802
|
+
party_id: resolvedPartyId,
|
|
803
|
+
});
|
|
804
|
+
const mediaRows = Array.isArray(mediaResponse?.data) ? mediaResponse.data : [];
|
|
805
|
+
const mediaMatches = sortByEventDateDesc(mediaRows, (row: any) => row?.asset?.event_date).filter((row: any) =>
|
|
806
|
+
isOnOrBeforeDate(row?.asset?.event_date, beforeDate),
|
|
807
|
+
);
|
|
808
|
+
mediaCandidate = mediaMatches[0] ?? null;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (mediaCandidate) {
|
|
812
|
+
const hasText = !!(mediaCandidate?.asset?.transcript_raw && String(mediaCandidate.asset.transcript_raw).trim());
|
|
813
|
+
const hasVtt = !!(mediaCandidate?.asset?.transcript_vtt && String(mediaCandidate.asset.transcript_vtt).trim());
|
|
814
|
+
const compactResult = {
|
|
815
|
+
query: {
|
|
816
|
+
programSlug: args.programSlug,
|
|
817
|
+
guest: args.guest,
|
|
818
|
+
beforeDate,
|
|
819
|
+
needVtt,
|
|
820
|
+
mode,
|
|
821
|
+
},
|
|
822
|
+
found: needVtt ? hasVtt : hasText || hasVtt,
|
|
823
|
+
result: {
|
|
824
|
+
guest: resolvedGuestName,
|
|
825
|
+
partyId: resolvedPartyId,
|
|
826
|
+
eventDate: mediaCandidate?.asset?.event_date ?? null,
|
|
827
|
+
meetingId: mediaCandidate?.asset?.meeting_id ?? null,
|
|
828
|
+
transcriptType: "full_conversation_asset",
|
|
829
|
+
hasText,
|
|
830
|
+
hasVtt,
|
|
831
|
+
exportReady: hasVtt,
|
|
832
|
+
source: "media_asset",
|
|
833
|
+
},
|
|
834
|
+
reason: needVtt && !hasVtt ? "Latest matching appearance has transcript text but no VTT file." : null,
|
|
835
|
+
};
|
|
836
|
+
if (mode === "detail") {
|
|
837
|
+
return {
|
|
838
|
+
...compactResult,
|
|
839
|
+
detail: {
|
|
840
|
+
resolvedGuestEmail,
|
|
841
|
+
mediaAssetId: mediaCandidate?.asset?.id ?? null,
|
|
842
|
+
mediaWorkflowStatus: mediaCandidate?.asset?.workflow_status ?? null,
|
|
843
|
+
mediaVisibility: mediaCandidate?.asset?.visibility ?? null,
|
|
844
|
+
},
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
return compactResult;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const transcriptResponse = await api.get<any>("/meetings/transcripts", {
|
|
851
|
+
program_slug: args.programSlug,
|
|
852
|
+
limit: MAX_TRANSCRIPT_LIMIT,
|
|
853
|
+
});
|
|
854
|
+
const transcriptRows = shapeTranscriptList(transcriptResponse, false, false)?.data ?? [];
|
|
855
|
+
const matchingTranscriptRows = sortByEventDateDesc(transcriptRows, (row: any) => row?.transcript?.event_date).filter((row: any) => {
|
|
856
|
+
const rowEventDate = row?.transcript?.event_date;
|
|
857
|
+
if (!isOnOrBeforeDate(rowEventDate, beforeDate)) return false;
|
|
858
|
+
if (resolvedPartyId && transcriptMeetingPartyId(row) === resolvedPartyId) return true;
|
|
859
|
+
return includesNeedle(transcriptMeetingGuestLabel(row), guestNeedle);
|
|
860
|
+
});
|
|
861
|
+
const transcriptCandidate = matchingTranscriptRows[0] ?? null;
|
|
862
|
+
|
|
863
|
+
if (transcriptCandidate) {
|
|
864
|
+
const hasText = transcriptCandidate?.transcript?.has_text === true;
|
|
865
|
+
const hasVtt = transcriptCandidate?.transcript?.has_vtt === true;
|
|
866
|
+
const compactResult = {
|
|
867
|
+
query: {
|
|
868
|
+
programSlug: args.programSlug,
|
|
869
|
+
guest: args.guest,
|
|
870
|
+
beforeDate,
|
|
871
|
+
needVtt,
|
|
872
|
+
mode,
|
|
873
|
+
},
|
|
874
|
+
found: needVtt ? hasVtt : hasText || hasVtt,
|
|
875
|
+
result: {
|
|
876
|
+
guest: transcriptMeetingGuestLabel(transcriptCandidate) ?? resolvedGuestName,
|
|
877
|
+
partyId: transcriptMeetingPartyId(transcriptCandidate) ?? resolvedPartyId,
|
|
878
|
+
eventDate: transcriptCandidate?.transcript?.event_date ?? null,
|
|
879
|
+
meetingId: transcriptCandidate?.transcript?.meeting_id ?? null,
|
|
880
|
+
transcriptType: transcriptCandidate?.transcript_type ?? (transcriptCandidate?.transcript?.meeting_id ? "meeting" : "conversation"),
|
|
881
|
+
hasText,
|
|
882
|
+
hasVtt,
|
|
883
|
+
exportReady: hasVtt,
|
|
884
|
+
source: "meetings_transcripts",
|
|
885
|
+
},
|
|
886
|
+
reason: needVtt && !hasVtt ? "Latest matching transcript row has text but no VTT file." : null,
|
|
887
|
+
};
|
|
888
|
+
if (mode === "detail") {
|
|
889
|
+
return {
|
|
890
|
+
...compactResult,
|
|
891
|
+
detail: {
|
|
892
|
+
resolvedGuestEmail,
|
|
893
|
+
transcriptId: transcriptCandidate?.transcript?.id ?? null,
|
|
894
|
+
transcriptStatus: transcriptCandidate?.transcript?.status ?? null,
|
|
895
|
+
scannedRows: transcriptRows.length,
|
|
896
|
+
matchedRows: matchingTranscriptRows.length,
|
|
897
|
+
},
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
return compactResult;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return {
|
|
904
|
+
query: {
|
|
905
|
+
programSlug: args.programSlug,
|
|
906
|
+
guest: args.guest,
|
|
907
|
+
beforeDate,
|
|
908
|
+
needVtt,
|
|
909
|
+
mode,
|
|
910
|
+
},
|
|
911
|
+
found: false,
|
|
912
|
+
result: null,
|
|
913
|
+
reason: "No transcript rows found for this guest before the requested date.",
|
|
914
|
+
...(mode === "detail"
|
|
915
|
+
? {
|
|
916
|
+
detail: {
|
|
917
|
+
resolvedPartyId,
|
|
918
|
+
resolvedGuestEmail,
|
|
919
|
+
},
|
|
920
|
+
}
|
|
921
|
+
: {}),
|
|
922
|
+
};
|
|
923
|
+
} catch (error) {
|
|
924
|
+
if (error instanceof ApiError) return { error: error.message, status: error.status };
|
|
925
|
+
throw error;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
704
929
|
function dateFromTimestamp(value: string | null | undefined) {
|
|
705
930
|
if (!value || typeof value !== "string") return null;
|
|
706
931
|
const match = value.match(/\d{4}-\d{2}-\d{2}/);
|
|
@@ -915,6 +1140,43 @@ export function registerMeetingTools(pi: ExtensionAPI) {
|
|
|
915
1140
|
execute: wrapExecute(getGuestProfile),
|
|
916
1141
|
});
|
|
917
1142
|
|
|
1143
|
+
pi.registerTool({
|
|
1144
|
+
name: "seedclub_find_latest_guest_transcript",
|
|
1145
|
+
label: "Find Latest Guest Transcript",
|
|
1146
|
+
description:
|
|
1147
|
+
"Find the latest transcript context for a guest before a cutoff date with compact output. This orchestrates guest resolution, full-conversation assets, and transcript metadata so callers avoid multi-step JSON-heavy discovery.",
|
|
1148
|
+
parameters: Type.Object({
|
|
1149
|
+
programSlug: Type.String({ description: "Program slug" }),
|
|
1150
|
+
guest: Type.String({ description: "Guest name/email search string" }),
|
|
1151
|
+
beforeDate: Type.Optional(
|
|
1152
|
+
Type.String({
|
|
1153
|
+
description: "Optional inclusive YYYY-MM-DD cutoff. Defaults to today.",
|
|
1154
|
+
}),
|
|
1155
|
+
),
|
|
1156
|
+
needVtt: Type.Optional(
|
|
1157
|
+
Type.Boolean({
|
|
1158
|
+
description: "Set true when caller specifically needs VTT availability.",
|
|
1159
|
+
}),
|
|
1160
|
+
),
|
|
1161
|
+
mode: Type.Optional(
|
|
1162
|
+
Type.Union([Type.Literal("compact"), Type.Literal("detail")], {
|
|
1163
|
+
description: "Response mode. Defaults to compact.",
|
|
1164
|
+
}),
|
|
1165
|
+
),
|
|
1166
|
+
}),
|
|
1167
|
+
execute: wrapExecute(findLatestGuestTranscript),
|
|
1168
|
+
renderResult(result: any, _args: any, theme: any) {
|
|
1169
|
+
if (result.isError) return new Text(theme.fg("error", result.content?.[0]?.text || "Error"), 0, 0);
|
|
1170
|
+
const found = result.details?.found === true;
|
|
1171
|
+
if (!found) {
|
|
1172
|
+
return new Text(theme.fg("dim", result.details?.reason || "No matching transcript found"), 0, 0);
|
|
1173
|
+
}
|
|
1174
|
+
const row = result.details?.result ?? {};
|
|
1175
|
+
const status = `${row?.eventDate ?? "unknown date"} · ${row?.guest ?? "guest"} · text:${row?.hasText ? "yes" : "no"} · vtt:${row?.hasVtt ? "yes" : "no"}`;
|
|
1176
|
+
return new Text(theme.fg("muted", status), 0, 0);
|
|
1177
|
+
},
|
|
1178
|
+
});
|
|
1179
|
+
|
|
918
1180
|
pi.registerTool({
|
|
919
1181
|
name: "seedclub_prepare_clip_packet",
|
|
920
1182
|
label: "Prepare Clip Packet",
|
|
@@ -451,32 +451,44 @@ export default function (pi: ExtensionAPI, options?: { enableFrame?: boolean })
|
|
|
451
451
|
loaderTimer.unref?.();
|
|
452
452
|
tuiRef?.requestRender();
|
|
453
453
|
|
|
454
|
+
const todayPromise = fetchTodayOn11am();
|
|
454
455
|
void Promise.all([
|
|
455
456
|
getData(),
|
|
456
|
-
withTimeout(
|
|
457
|
+
withTimeout(todayPromise, TODAY_PREFETCH_TIMEOUT_MS, null),
|
|
457
458
|
]).then(([{ weather, market }, todayOn11am]) => {
|
|
458
459
|
clearInterval(loaderTimer);
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
460
|
+
const renderReadyHeader = (today: TodayOn11am | null) => {
|
|
461
|
+
const theme = ctx.ui.theme;
|
|
462
|
+
const weatherLine = ` ${weather.icon} ${theme.fg("text", weather.temp)} ${theme.fg("dim", weather.condition)} ${theme.fg("dim", "·")} ${theme.fg("dim", weather.location)}`;
|
|
463
|
+
const marketLine = ` ${market.map((quote) => formatQuote(quote, theme)).join(` ${theme.fg("dim", "·")} `)}`;
|
|
464
|
+
const todayLines = renderTodayOn11amLines(today, theme);
|
|
465
|
+
uiState.todayOn11am = today;
|
|
466
|
+
headerLines = [
|
|
467
|
+
"",
|
|
468
|
+
renderTitle(theme),
|
|
469
|
+
"",
|
|
470
|
+
weatherLine,
|
|
471
|
+
marketLine,
|
|
472
|
+
"",
|
|
473
|
+
...todayLines,
|
|
474
|
+
...(todayLines.length ? [""] : []),
|
|
475
|
+
...setupLines,
|
|
476
|
+
"",
|
|
477
|
+
];
|
|
478
|
+
};
|
|
464
479
|
|
|
465
|
-
|
|
466
|
-
"",
|
|
467
|
-
renderTitle(theme),
|
|
468
|
-
"",
|
|
469
|
-
weatherLine,
|
|
470
|
-
marketLine,
|
|
471
|
-
"",
|
|
472
|
-
...todayLines,
|
|
473
|
-
...(todayLines.length ? [""] : []),
|
|
474
|
-
...setupLines,
|
|
475
|
-
"",
|
|
476
|
-
];
|
|
480
|
+
renderReadyHeader(todayOn11am);
|
|
477
481
|
uiState.ready = true;
|
|
478
482
|
ctx.ui.setEditorText("");
|
|
479
483
|
tuiRef?.requestRender();
|
|
484
|
+
|
|
485
|
+
if (!todayOn11am?.guests.length) {
|
|
486
|
+
void todayPromise.then((freshToday) => {
|
|
487
|
+
if (!freshToday?.guests.length) return;
|
|
488
|
+
renderReadyHeader(freshToday);
|
|
489
|
+
tuiRef?.requestRender();
|
|
490
|
+
}).catch(() => {});
|
|
491
|
+
}
|
|
480
492
|
}).catch(() => {
|
|
481
493
|
clearInterval(loaderTimer);
|
|
482
494
|
headerLines = [
|
package/bin/cli.js
CHANGED
|
@@ -2,120 +2,22 @@
|
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
const { execFileSync, spawn } = require("child_process");
|
|
5
|
-
const { readFileSync, existsSync, writeFileSync,
|
|
5
|
+
const { readFileSync, existsSync, writeFileSync, mkdirSync } = require("fs");
|
|
6
6
|
const { join, dirname, basename } = require("path");
|
|
7
7
|
const { homedir } = require("os");
|
|
8
|
-
const readline = require("readline");
|
|
9
8
|
|
|
10
9
|
const SC_DIR = join(homedir(), ".seedclub", "agent");
|
|
11
10
|
const VERSION_FILE = join(SC_DIR, ".seedclub-version");
|
|
12
11
|
const SETTINGS_FILE = join(SC_DIR, "settings.json");
|
|
13
12
|
const PI_MAIN_LAUNCHER = join(__dirname, "pi-main-launcher.js");
|
|
14
13
|
process.title = "seedclub";
|
|
15
|
-
const SEEDCLUB_ENV_EXCLUDE = new Set(["
|
|
14
|
+
const SEEDCLUB_ENV_EXCLUDE = new Set(["SEEDCLUB_PI_MAIN"]);
|
|
16
15
|
|
|
17
16
|
function printPrivateRegistryHint() {
|
|
18
17
|
console.error("seedclub: install/update failed.");
|
|
19
|
-
console.error("
|
|
20
|
-
console.error("
|
|
21
|
-
console.error(" seedclub
|
|
22
|
-
console.error("Or run with an ephemeral token:");
|
|
23
|
-
console.error(" SEEDCLUB_NPM_TOKEN=YOUR_NPM_TOKEN seedclub update");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function upsertLine(lines, prefix, value) {
|
|
27
|
-
const idx = lines.findIndex((line) => line.trim().startsWith(prefix));
|
|
28
|
-
if (idx >= 0) {
|
|
29
|
-
lines[idx] = value;
|
|
30
|
-
} else {
|
|
31
|
-
lines.push(value);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function readNpmrcLines(path) {
|
|
36
|
-
if (!existsSync(path)) return [];
|
|
37
|
-
const raw = readFileSync(path, "utf-8");
|
|
38
|
-
if (!raw.trim()) return [];
|
|
39
|
-
return raw.split(/\r?\n/).filter((line) => line.length > 0);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function askHidden(prompt) {
|
|
43
|
-
return new Promise((resolve, reject) => {
|
|
44
|
-
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
45
|
-
return reject(new Error("Interactive prompt requires a TTY"));
|
|
46
|
-
}
|
|
47
|
-
const rl = readline.createInterface({
|
|
48
|
-
input: process.stdin,
|
|
49
|
-
output: process.stdout,
|
|
50
|
-
terminal: true,
|
|
51
|
-
});
|
|
52
|
-
const onData = (char) => {
|
|
53
|
-
const ch = String(char);
|
|
54
|
-
if (ch === "\n" || ch === "\r" || ch === "\u0004") {
|
|
55
|
-
process.stdout.write("\n");
|
|
56
|
-
} else if (ch === "\u0003") {
|
|
57
|
-
process.stdout.write("^C\n");
|
|
58
|
-
} else {
|
|
59
|
-
process.stdout.write("\x1b[2K\x1b[200D" + prompt + "*".repeat(rl.line.length));
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
process.stdin.on("data", onData);
|
|
63
|
-
rl.question(prompt, (value) => {
|
|
64
|
-
process.stdin.removeListener("data", onData);
|
|
65
|
-
rl.close();
|
|
66
|
-
resolve(value.trim());
|
|
67
|
-
});
|
|
68
|
-
rl.on("SIGINT", () => {
|
|
69
|
-
process.stdin.removeListener("data", onData);
|
|
70
|
-
rl.close();
|
|
71
|
-
reject(new Error("Cancelled"));
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async function setupAuth() {
|
|
77
|
-
const npmrcPath = join(homedir(), ".npmrc");
|
|
78
|
-
let token = process.env.SEEDCLUB_NPM_TOKEN || process.env.NPM_TOKEN || "";
|
|
79
|
-
if (!token) {
|
|
80
|
-
try {
|
|
81
|
-
token = await askHidden("npm token: ");
|
|
82
|
-
} catch (err) {
|
|
83
|
-
console.error(`seedclub: ${err.message}`);
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (!token) {
|
|
88
|
-
console.error("seedclub: no token provided.");
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const lines = readNpmrcLines(npmrcPath);
|
|
93
|
-
upsertLine(lines, "@clubnet:registry=", "@clubnet:registry=https://registry.npmjs.org/");
|
|
94
|
-
upsertLine(lines, "//registry.npmjs.org/:_authToken=", `//registry.npmjs.org/:_authToken=${token}`);
|
|
95
|
-
writeFileSync(npmrcPath, lines.join("\n") + "\n", { mode: 0o600 });
|
|
96
|
-
|
|
97
|
-
console.log(`Wrote npm auth config to ${npmrcPath}`);
|
|
98
|
-
console.log("You can now run:");
|
|
99
|
-
console.log(" npm install -g @clubnet/seedclub");
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function withOptionalEphemeralNpmrc(run) {
|
|
103
|
-
const token = process.env.SEEDCLUB_NPM_TOKEN || process.env.NPM_TOKEN;
|
|
104
|
-
if (!token) return run([]);
|
|
105
|
-
|
|
106
|
-
const npmrcPath = join(SC_DIR, ".npmrc.tmp");
|
|
107
|
-
try {
|
|
108
|
-
writeFileSync(
|
|
109
|
-
npmrcPath,
|
|
110
|
-
`@clubnet:registry=https://registry.npmjs.org/\n//registry.npmjs.org/:_authToken=${token}\n`,
|
|
111
|
-
{ mode: 0o600 },
|
|
112
|
-
);
|
|
113
|
-
return run(["--userconfig", npmrcPath]);
|
|
114
|
-
} finally {
|
|
115
|
-
try {
|
|
116
|
-
unlinkSync(npmrcPath);
|
|
117
|
-
} catch {}
|
|
118
|
-
}
|
|
18
|
+
console.error("If npm reports a private package or permission error, run:");
|
|
19
|
+
console.error(" npm login");
|
|
20
|
+
console.error(" seedclub update");
|
|
119
21
|
}
|
|
120
22
|
|
|
121
23
|
function findPackageRoot(fromFile, expectedName) {
|
|
@@ -533,19 +435,10 @@ if (cmd === "theme") {
|
|
|
533
435
|
process.exit(0);
|
|
534
436
|
}
|
|
535
437
|
|
|
536
|
-
if (cmd === "setup-auth") {
|
|
537
|
-
setupAuth().catch((err) => {
|
|
538
|
-
console.error(`seedclub: ${err instanceof Error ? err.message : String(err)}`);
|
|
539
|
-
process.exit(1);
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
|
|
543
438
|
if (cmd === "update") {
|
|
544
439
|
try {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
stdio: "inherit",
|
|
548
|
-
});
|
|
440
|
+
execFileSync("npm", ["install", "-g", "@clubnet/seedclub@latest"], {
|
|
441
|
+
stdio: "inherit",
|
|
549
442
|
});
|
|
550
443
|
} catch {
|
|
551
444
|
printPrivateRegistryHint();
|
|
@@ -554,8 +447,7 @@ if (cmd === "update") {
|
|
|
554
447
|
process.exit(0);
|
|
555
448
|
}
|
|
556
449
|
|
|
557
|
-
|
|
558
|
-
// ── Resolve pi binary ───────────────────────────────────────────────────
|
|
450
|
+
// ── Resolve pi binary ───────────────────────────────────────────────────
|
|
559
451
|
|
|
560
452
|
let piBin;
|
|
561
453
|
let piEntry;
|
|
@@ -618,4 +510,3 @@ if (cmd !== "setup-auth") {
|
|
|
618
510
|
}
|
|
619
511
|
});
|
|
620
512
|
});
|
|
621
|
-
}
|
package/package.json
CHANGED