@clubnet/seedclub 0.2.0
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/LICENSE +22 -0
- package/README.md +246 -0
- package/assets/extensions/seedclub/api-client.ts +102 -0
- package/assets/extensions/seedclub/auth.ts +89 -0
- package/assets/extensions/seedclub/commands/add.ts +601 -0
- package/assets/extensions/seedclub/commands/seedclub.ts +67 -0
- package/assets/extensions/seedclub/commands/signals.ts +86 -0
- package/assets/extensions/seedclub/commands/sort.ts +91 -0
- package/assets/extensions/seedclub/dia-cookies.ts +126 -0
- package/assets/extensions/seedclub/index.ts +166 -0
- package/assets/extensions/seedclub/package-lock.json +65 -0
- package/assets/extensions/seedclub/package.json +11 -0
- package/assets/extensions/seedclub/tool-utils.ts +32 -0
- package/assets/extensions/seedclub/tools/signals.ts +275 -0
- package/assets/extensions/seedclub/tools/utility.ts +31 -0
- package/assets/extensions/seedclub/twitter-client.ts +277 -0
- package/assets/extensions/seedclub-ui/editor.ts +93 -0
- package/assets/extensions/seedclub-ui/index.ts +15 -0
- package/assets/extensions/seedclub-ui/update.ts +73 -0
- package/assets/extensions/seedclub-ui/welcome.ts +250 -0
- package/assets/theme/seedclub.json +86 -0
- package/bin/cli.js +195 -0
- package/package.json +30 -0
- package/postinstall.js +175 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update check — runs on session start, notifies if a new version is available.
|
|
3
|
+
* To update, run `seedclub update` in your terminal.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
|
|
12
|
+
interface VersionInfo {
|
|
13
|
+
seedclubVersion: string;
|
|
14
|
+
piVersion: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function compareSemver(a: string, b: string): number {
|
|
18
|
+
const pa = a.trim().split(".").map((x) => Number.parseInt(x, 10));
|
|
19
|
+
const pb = b.trim().split(".").map((x) => Number.parseInt(x, 10));
|
|
20
|
+
if (pa.some(Number.isNaN) || pb.some(Number.isNaN)) return a.localeCompare(b);
|
|
21
|
+
const len = Math.max(pa.length, pb.length);
|
|
22
|
+
for (let i = 0; i < len; i++) {
|
|
23
|
+
const av = pa[i] ?? 0;
|
|
24
|
+
const bv = pb[i] ?? 0;
|
|
25
|
+
if (av > bv) return 1;
|
|
26
|
+
if (av < bv) return -1;
|
|
27
|
+
}
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getInstalledVersion(): VersionInfo | null {
|
|
32
|
+
const versionFile = join(homedir(), ".seedclub", "agent", ".seedclub-version");
|
|
33
|
+
if (!existsSync(versionFile)) return null;
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(readFileSync(versionFile, "utf-8"));
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getLatestVersion(): Promise<string | null> {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
execFile("npm", ["view", "@clubnet/seedclub", "version"], { timeout: 10000 }, (err, stdout) => {
|
|
44
|
+
if (err) return resolve(null);
|
|
45
|
+
const version = stdout.trim();
|
|
46
|
+
resolve(version || null);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default function (pi: ExtensionAPI) {
|
|
52
|
+
// Check for updates on session start (background, non-blocking)
|
|
53
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
54
|
+
if (!ctx.hasUI) return;
|
|
55
|
+
|
|
56
|
+
const installed = getInstalledVersion();
|
|
57
|
+
if (!installed) return;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const latest = await getLatestVersion();
|
|
61
|
+
if (!latest) return;
|
|
62
|
+
|
|
63
|
+
if (compareSemver(latest, installed.seedclubVersion) > 0) {
|
|
64
|
+
ctx.ui.setWidget("seedclub-update", [
|
|
65
|
+
`⚡ seedclub v${latest} available (current: v${installed.seedclubVersion})`,
|
|
66
|
+
` Run: seedclub update`,
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// Silent — no network is fine
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Welcome header with live weather + market data.
|
|
3
|
+
* /commands and /extensions still available as commands.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
8
|
+
|
|
9
|
+
const EMERALD = "\x1b[38;2;80;200;120m";
|
|
10
|
+
const DODGER = "\x1b[38;2;30;144;255m";
|
|
11
|
+
const RED = "\x1b[38;2;255;0;0m";
|
|
12
|
+
const DIM = "\x1b[38;2;85;85;85m";
|
|
13
|
+
const WHITE = "\x1b[38;2;200;200;200m";
|
|
14
|
+
const BOLD = "\x1b[1m";
|
|
15
|
+
const RESET = "\x1b[0m";
|
|
16
|
+
|
|
17
|
+
interface MarketQuote {
|
|
18
|
+
symbol: string;
|
|
19
|
+
price: string;
|
|
20
|
+
change: string;
|
|
21
|
+
pct: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface WeatherData {
|
|
25
|
+
temp: string;
|
|
26
|
+
condition: string;
|
|
27
|
+
location: string;
|
|
28
|
+
icon: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let cachedWeather: WeatherData | null = null;
|
|
32
|
+
let cachedMarket: MarketQuote[] | null = null;
|
|
33
|
+
let lastFetch = 0;
|
|
34
|
+
const CACHE_MS = 10 * 60 * 1000;
|
|
35
|
+
|
|
36
|
+
function getCityFromTimezone(): string {
|
|
37
|
+
try {
|
|
38
|
+
const tz = execFileSync("readlink", ["/etc/localtime"], { encoding: "utf-8" }).trim();
|
|
39
|
+
const parts = tz.replace(/.*\/zoneinfo\//, "").split("/");
|
|
40
|
+
return parts[parts.length - 1]!.replace(/_/g, " ");
|
|
41
|
+
} catch {
|
|
42
|
+
return "New York";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function fetchWeather(): Promise<WeatherData> {
|
|
47
|
+
try {
|
|
48
|
+
const city = getCityFromTimezone();
|
|
49
|
+
const res = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=j1`, {
|
|
50
|
+
headers: { "User-Agent": "curl/7.0" },
|
|
51
|
+
signal: AbortSignal.timeout(5000),
|
|
52
|
+
});
|
|
53
|
+
const data = (await res.json()) as any;
|
|
54
|
+
const current = data.current_condition?.[0];
|
|
55
|
+
|
|
56
|
+
const icons: Record<string, string> = {
|
|
57
|
+
Sunny: "☀",
|
|
58
|
+
Clear: "☀",
|
|
59
|
+
"Partly cloudy": "⛅",
|
|
60
|
+
"Partly Cloudy": "⛅",
|
|
61
|
+
Cloudy: "☁",
|
|
62
|
+
Overcast: "☁",
|
|
63
|
+
Mist: "🌫",
|
|
64
|
+
Fog: "🌫",
|
|
65
|
+
"Light rain": "🌧",
|
|
66
|
+
Rain: "🌧",
|
|
67
|
+
"Heavy rain": "🌧",
|
|
68
|
+
"Light snow": "❄",
|
|
69
|
+
Snow: "❄",
|
|
70
|
+
Thunderstorm: "⛈",
|
|
71
|
+
};
|
|
72
|
+
const desc = current?.weatherDesc?.[0]?.value || "Unknown";
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
temp: `${current?.temp_F || "?"}°F`,
|
|
76
|
+
condition: desc,
|
|
77
|
+
location: city,
|
|
78
|
+
icon: icons[desc] || "🌡",
|
|
79
|
+
};
|
|
80
|
+
} catch {
|
|
81
|
+
return { temp: "—", condition: "unavailable", location: "—", icon: "🌡" };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function fetchMarket(): Promise<MarketQuote[]> {
|
|
86
|
+
const symbols = [
|
|
87
|
+
{ sym: "SPY", name: "S&P 500" },
|
|
88
|
+
{ sym: "QQQ", name: "Nasdaq" },
|
|
89
|
+
{ sym: "BTC-USD", name: "BTC" },
|
|
90
|
+
{ sym: "ETH-USD", name: "ETH" },
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
const quotes: MarketQuote[] = [];
|
|
94
|
+
for (const { sym, name } of symbols) {
|
|
95
|
+
try {
|
|
96
|
+
const url = `https://query1.finance.yahoo.com/v8/finance/chart/${sym}?range=1d&interval=1d`;
|
|
97
|
+
const res = await fetch(url, {
|
|
98
|
+
headers: { "User-Agent": "Mozilla/5.0" },
|
|
99
|
+
signal: AbortSignal.timeout(5000),
|
|
100
|
+
});
|
|
101
|
+
const data = (await res.json()) as any;
|
|
102
|
+
const meta = data?.chart?.result?.[0]?.meta;
|
|
103
|
+
const price = meta?.regularMarketPrice ?? 0;
|
|
104
|
+
const prevClose = meta?.chartPreviousClose ?? meta?.previousClose ?? price;
|
|
105
|
+
const change = price - prevClose;
|
|
106
|
+
const pct = prevClose > 0 ? (change / prevClose) * 100 : 0;
|
|
107
|
+
|
|
108
|
+
quotes.push({
|
|
109
|
+
symbol: name,
|
|
110
|
+
price: price > 1000 ? price.toFixed(0) : price.toFixed(2),
|
|
111
|
+
change: change >= 0 ? `+${change.toFixed(2)}` : change.toFixed(2),
|
|
112
|
+
pct: `${pct >= 0 ? "+" : ""}${pct.toFixed(2)}%`,
|
|
113
|
+
});
|
|
114
|
+
} catch {
|
|
115
|
+
quotes.push({ symbol: name, price: "—", change: "—", pct: "—" });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return quotes;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function getData() {
|
|
122
|
+
const now = Date.now();
|
|
123
|
+
if (cachedWeather && cachedMarket && now - lastFetch < CACHE_MS) {
|
|
124
|
+
return { weather: cachedWeather, market: cachedMarket };
|
|
125
|
+
}
|
|
126
|
+
const [weather, market] = await Promise.all([fetchWeather(), fetchMarket()]);
|
|
127
|
+
cachedWeather = weather;
|
|
128
|
+
cachedMarket = market;
|
|
129
|
+
lastFetch = now;
|
|
130
|
+
return { weather, market };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function formatQuote(q: MarketQuote): string {
|
|
134
|
+
const color = q.change.startsWith("+") ? EMERALD : q.change.startsWith("-") ? RED : DIM;
|
|
135
|
+
return `${WHITE}${q.symbol}${RESET} ${DIM}${q.price}${RESET} ${color}${q.pct}${RESET}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const EMERALD_ANSI = "\x1b[38;2;80;200;120m";
|
|
139
|
+
|
|
140
|
+
export default function (pi: ExtensionAPI, options?: { enableFrame?: boolean }) {
|
|
141
|
+
let headerLines: string[] = [
|
|
142
|
+
"",
|
|
143
|
+
` ${EMERALD}${BOLD}The Human+ Venture Network${RESET}`,
|
|
144
|
+
"",
|
|
145
|
+
` ${DIM}loading...${RESET}`,
|
|
146
|
+
"",
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
150
|
+
if (!ctx.hasUI) return;
|
|
151
|
+
|
|
152
|
+
let tuiRef: any = null;
|
|
153
|
+
|
|
154
|
+
ctx.ui.setHeader((tui, _theme) => {
|
|
155
|
+
tuiRef = tui;
|
|
156
|
+
|
|
157
|
+
// Enable window frame around entire TUI (footer excluded)
|
|
158
|
+
if (options?.enableFrame && tui.setFrame) {
|
|
159
|
+
tui.setFrame({
|
|
160
|
+
enabled: true,
|
|
161
|
+
inset: 1,
|
|
162
|
+
excludeBottom: 3,
|
|
163
|
+
borderColor: (s: string) => `${EMERALD_ANSI}${s}\x1b[0m`,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
render(_width: number): string[] {
|
|
169
|
+
return headerLines;
|
|
170
|
+
},
|
|
171
|
+
invalidate() {},
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const { weather, market } = await getData();
|
|
177
|
+
const weatherLine = ` ${weather.icon} ${WHITE}${weather.temp}${RESET} ${DIM}${weather.condition}${RESET} ${DIM}·${RESET} ${DIM}${weather.location}${RESET}`;
|
|
178
|
+
const marketLine = ` ${market.map(formatQuote).join(` ${DIM}·${RESET} `)}`;
|
|
179
|
+
|
|
180
|
+
headerLines = [
|
|
181
|
+
"",
|
|
182
|
+
` ${EMERALD}${BOLD}The Human+ Venture Network${RESET}`,
|
|
183
|
+
"",
|
|
184
|
+
weatherLine,
|
|
185
|
+
marketLine,
|
|
186
|
+
"",
|
|
187
|
+
` ${DIM}type ${WHITE}/${RESET}${DIM} to see commands${RESET}`,
|
|
188
|
+
"",
|
|
189
|
+
];
|
|
190
|
+
tuiRef?.requestRender();
|
|
191
|
+
} catch {
|
|
192
|
+
headerLines = [
|
|
193
|
+
"",
|
|
194
|
+
` ${EMERALD}${BOLD}The Human+ Venture Network${RESET}`,
|
|
195
|
+
"",
|
|
196
|
+
` ${DIM}type ${WHITE}/${RESET}${DIM} to see commands${RESET}`,
|
|
197
|
+
"",
|
|
198
|
+
];
|
|
199
|
+
tuiRef?.requestRender();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
pi.registerCommand("commands", {
|
|
204
|
+
description: "List all available commands",
|
|
205
|
+
handler: async (_args, _ctx) => {
|
|
206
|
+
const commands = pi.getCommands();
|
|
207
|
+
const lines = commands
|
|
208
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
209
|
+
.map((cmd) => ` ${EMERALD}/${cmd.name}${RESET} ${DIM}${cmd.description || ""}${RESET}`)
|
|
210
|
+
.join("\n");
|
|
211
|
+
pi.sendMessage({
|
|
212
|
+
customType: "info-display",
|
|
213
|
+
content: `${BOLD}${WHITE}Commands${RESET}\n\n${lines}`,
|
|
214
|
+
display: true,
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
pi.registerCommand("extensions", {
|
|
220
|
+
description: "List loaded extensions",
|
|
221
|
+
handler: async (_args, _ctx) => {
|
|
222
|
+
const commands = pi.getCommands();
|
|
223
|
+
const extPaths = new Map<string, string[]>();
|
|
224
|
+
for (const cmd of commands) {
|
|
225
|
+
if (cmd.source === "extension" && cmd.path) {
|
|
226
|
+
const name = cmd.path.replace(/.*\/extensions\//, "").replace(/\.ts$/, "");
|
|
227
|
+
if (!extPaths.has(name)) extPaths.set(name, []);
|
|
228
|
+
extPaths.get(name)!.push(cmd.name);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
let lines: string;
|
|
232
|
+
if (extPaths.size > 0) {
|
|
233
|
+
lines = [...extPaths.entries()]
|
|
234
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
235
|
+
.map(([name, cmds]) => {
|
|
236
|
+
const cmdList = cmds.map((c) => `${DIM}/${c}${RESET}`).join(" ");
|
|
237
|
+
return ` ${DODGER}${name}${RESET} ${cmdList}`;
|
|
238
|
+
})
|
|
239
|
+
.join("\n");
|
|
240
|
+
} else {
|
|
241
|
+
lines = ` ${DIM}No extension commands registered${RESET}`;
|
|
242
|
+
}
|
|
243
|
+
pi.sendMessage({
|
|
244
|
+
customType: "info-display",
|
|
245
|
+
content: `${BOLD}${WHITE}Extensions${RESET}\n\n${lines}`,
|
|
246
|
+
display: true,
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/src/modes/interactive/theme/theme-schema.json",
|
|
3
|
+
"name": "seedclub",
|
|
4
|
+
"vars": {
|
|
5
|
+
"brand": "#00C853",
|
|
6
|
+
"brandDim": "#00963F",
|
|
7
|
+
"brandLight": "#69F0AE",
|
|
8
|
+
"warmWhite": "#E8E6E3",
|
|
9
|
+
"coolGray": "#8A8F98",
|
|
10
|
+
"dimGray": "#5C6370",
|
|
11
|
+
"darkGray": "#3E4451",
|
|
12
|
+
"deepBg": "#1A1D23",
|
|
13
|
+
"cardBg": "#21252B",
|
|
14
|
+
"surfaceBg": "#282C34",
|
|
15
|
+
"red": "#E06C75",
|
|
16
|
+
"yellow": "#E5C07B",
|
|
17
|
+
"blue": "#61AFEF",
|
|
18
|
+
"purple": "#C678DD",
|
|
19
|
+
"cyan": "#56B6C2",
|
|
20
|
+
"green": "#98C379"
|
|
21
|
+
},
|
|
22
|
+
"colors": {
|
|
23
|
+
"accent": "brand",
|
|
24
|
+
"border": "brandDim",
|
|
25
|
+
"borderAccent": "brandLight",
|
|
26
|
+
"borderMuted": "darkGray",
|
|
27
|
+
"success": "green",
|
|
28
|
+
"error": "red",
|
|
29
|
+
"warning": "yellow",
|
|
30
|
+
"muted": "coolGray",
|
|
31
|
+
"dim": "dimGray",
|
|
32
|
+
"text": "",
|
|
33
|
+
"thinkingText": "coolGray",
|
|
34
|
+
|
|
35
|
+
"selectedBg": "#2C3340",
|
|
36
|
+
"userMessageBg": "surfaceBg",
|
|
37
|
+
"userMessageText": "",
|
|
38
|
+
"customMessageBg": "cardBg",
|
|
39
|
+
"customMessageText": "",
|
|
40
|
+
"customMessageLabel": "brand",
|
|
41
|
+
"toolPendingBg": "deepBg",
|
|
42
|
+
"toolSuccessBg": "#1A2E1A",
|
|
43
|
+
"toolErrorBg": "#2E1A1A",
|
|
44
|
+
"toolTitle": "brandLight",
|
|
45
|
+
"toolOutput": "coolGray",
|
|
46
|
+
|
|
47
|
+
"mdHeading": "yellow",
|
|
48
|
+
"mdLink": "blue",
|
|
49
|
+
"mdLinkUrl": "dimGray",
|
|
50
|
+
"mdCode": "cyan",
|
|
51
|
+
"mdCodeBlock": "green",
|
|
52
|
+
"mdCodeBlockBorder": "coolGray",
|
|
53
|
+
"mdQuote": "coolGray",
|
|
54
|
+
"mdQuoteBorder": "coolGray",
|
|
55
|
+
"mdHr": "darkGray",
|
|
56
|
+
"mdListBullet": "brand",
|
|
57
|
+
|
|
58
|
+
"toolDiffAdded": "green",
|
|
59
|
+
"toolDiffRemoved": "red",
|
|
60
|
+
"toolDiffContext": "coolGray",
|
|
61
|
+
|
|
62
|
+
"syntaxComment": "#7F848E",
|
|
63
|
+
"syntaxKeyword": "purple",
|
|
64
|
+
"syntaxFunction": "blue",
|
|
65
|
+
"syntaxVariable": "red",
|
|
66
|
+
"syntaxString": "green",
|
|
67
|
+
"syntaxNumber": "#D19A66",
|
|
68
|
+
"syntaxType": "cyan",
|
|
69
|
+
"syntaxOperator": "warmWhite",
|
|
70
|
+
"syntaxPunctuation": "coolGray",
|
|
71
|
+
|
|
72
|
+
"thinkingOff": "darkGray",
|
|
73
|
+
"thinkingMinimal": "dimGray",
|
|
74
|
+
"thinkingLow": "brandDim",
|
|
75
|
+
"thinkingMedium": "brand",
|
|
76
|
+
"thinkingHigh": "brandLight",
|
|
77
|
+
"thinkingXhigh": "#B9F6CA",
|
|
78
|
+
|
|
79
|
+
"bashMode": "yellow"
|
|
80
|
+
},
|
|
81
|
+
"export": {
|
|
82
|
+
"pageBg": "#1A1D23",
|
|
83
|
+
"cardBg": "#21252B",
|
|
84
|
+
"infoBg": "#2A2D25"
|
|
85
|
+
}
|
|
86
|
+
}
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const { execFileSync, spawn } = require("child_process");
|
|
5
|
+
const { readFileSync, existsSync, writeFileSync, unlinkSync } = require("fs");
|
|
6
|
+
const { join, dirname } = require("path");
|
|
7
|
+
const { homedir } = require("os");
|
|
8
|
+
const readline = require("readline");
|
|
9
|
+
|
|
10
|
+
const SC_DIR = join(homedir(), ".seedclub", "agent");
|
|
11
|
+
const VERSION_FILE = join(SC_DIR, ".seedclub-version");
|
|
12
|
+
|
|
13
|
+
function printPrivateRegistryHint() {
|
|
14
|
+
console.error("seedclub: install/update failed.");
|
|
15
|
+
console.error("This package is private on npmjs.");
|
|
16
|
+
console.error("Configure npm auth, then retry:");
|
|
17
|
+
console.error(" seedclub setup-auth");
|
|
18
|
+
console.error("Or run with an ephemeral token:");
|
|
19
|
+
console.error(" SEEDCLUB_NPM_TOKEN=YOUR_NPM_TOKEN seedclub update");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function upsertLine(lines, prefix, value) {
|
|
23
|
+
const idx = lines.findIndex((line) => line.trim().startsWith(prefix));
|
|
24
|
+
if (idx >= 0) {
|
|
25
|
+
lines[idx] = value;
|
|
26
|
+
} else {
|
|
27
|
+
lines.push(value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function readNpmrcLines(path) {
|
|
32
|
+
if (!existsSync(path)) return [];
|
|
33
|
+
const raw = readFileSync(path, "utf-8");
|
|
34
|
+
if (!raw.trim()) return [];
|
|
35
|
+
return raw.split(/\r?\n/).filter((line) => line.length > 0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function askHidden(prompt) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
41
|
+
return reject(new Error("Interactive prompt requires a TTY"));
|
|
42
|
+
}
|
|
43
|
+
const rl = readline.createInterface({
|
|
44
|
+
input: process.stdin,
|
|
45
|
+
output: process.stdout,
|
|
46
|
+
terminal: true,
|
|
47
|
+
});
|
|
48
|
+
const onData = (char) => {
|
|
49
|
+
const ch = String(char);
|
|
50
|
+
if (ch === "\n" || ch === "\r" || ch === "\u0004") {
|
|
51
|
+
process.stdout.write("\n");
|
|
52
|
+
} else if (ch === "\u0003") {
|
|
53
|
+
process.stdout.write("^C\n");
|
|
54
|
+
} else {
|
|
55
|
+
process.stdout.write("\x1b[2K\x1b[200D" + prompt + "*".repeat(rl.line.length));
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
process.stdin.on("data", onData);
|
|
59
|
+
rl.question(prompt, (value) => {
|
|
60
|
+
process.stdin.removeListener("data", onData);
|
|
61
|
+
rl.close();
|
|
62
|
+
resolve(value.trim());
|
|
63
|
+
});
|
|
64
|
+
rl.on("SIGINT", () => {
|
|
65
|
+
process.stdin.removeListener("data", onData);
|
|
66
|
+
rl.close();
|
|
67
|
+
reject(new Error("Cancelled"));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function setupAuth() {
|
|
73
|
+
const npmrcPath = join(homedir(), ".npmrc");
|
|
74
|
+
let token = process.env.SEEDCLUB_NPM_TOKEN || process.env.NPM_TOKEN || "";
|
|
75
|
+
if (!token) {
|
|
76
|
+
try {
|
|
77
|
+
token = await askHidden("npm token: ");
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(`seedclub: ${err.message}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!token) {
|
|
84
|
+
console.error("seedclub: no token provided.");
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const lines = readNpmrcLines(npmrcPath);
|
|
89
|
+
upsertLine(lines, "@clubnet:registry=", "@clubnet:registry=https://registry.npmjs.org/");
|
|
90
|
+
upsertLine(lines, "//registry.npmjs.org/:_authToken=", `//registry.npmjs.org/:_authToken=${token}`);
|
|
91
|
+
writeFileSync(npmrcPath, lines.join("\n") + "\n", { mode: 0o600 });
|
|
92
|
+
|
|
93
|
+
console.log(`Wrote npm auth config to ${npmrcPath}`);
|
|
94
|
+
console.log("You can now run:");
|
|
95
|
+
console.log(" npm install -g @clubnet/seedclub");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function withOptionalEphemeralNpmrc(run) {
|
|
99
|
+
const token = process.env.SEEDCLUB_NPM_TOKEN || process.env.NPM_TOKEN;
|
|
100
|
+
if (!token) return run([]);
|
|
101
|
+
|
|
102
|
+
const npmrcPath = join(SC_DIR, ".npmrc.tmp");
|
|
103
|
+
try {
|
|
104
|
+
writeFileSync(
|
|
105
|
+
npmrcPath,
|
|
106
|
+
`@clubnet:registry=https://registry.npmjs.org/\n//registry.npmjs.org/:_authToken=${token}\n`,
|
|
107
|
+
{ mode: 0o600 },
|
|
108
|
+
);
|
|
109
|
+
return run(["--userconfig", npmrcPath]);
|
|
110
|
+
} finally {
|
|
111
|
+
try {
|
|
112
|
+
unlinkSync(npmrcPath);
|
|
113
|
+
} catch {}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Subcommands ──────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
const cmd = process.argv[2];
|
|
120
|
+
|
|
121
|
+
if (cmd === "version") {
|
|
122
|
+
if (existsSync(VERSION_FILE)) {
|
|
123
|
+
try {
|
|
124
|
+
const info = JSON.parse(readFileSync(VERSION_FILE, "utf-8"));
|
|
125
|
+
console.log(`seedclub v${info.seedclubVersion}`);
|
|
126
|
+
} catch {
|
|
127
|
+
console.log("seedclub (version unknown)");
|
|
128
|
+
}
|
|
129
|
+
} else {
|
|
130
|
+
console.log("seedclub (version unknown)");
|
|
131
|
+
}
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (cmd === "setup-auth") {
|
|
136
|
+
setupAuth().catch((err) => {
|
|
137
|
+
console.error(`seedclub: ${err instanceof Error ? err.message : String(err)}`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (cmd === "update") {
|
|
143
|
+
try {
|
|
144
|
+
withOptionalEphemeralNpmrc((extraArgs) => {
|
|
145
|
+
execFileSync("npm", ["install", "-g", "@clubnet/seedclub@latest", ...extraArgs], {
|
|
146
|
+
stdio: "inherit",
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
} catch {
|
|
150
|
+
printPrivateRegistryHint();
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (cmd !== "setup-auth") {
|
|
157
|
+
// ── Resolve pi binary ───────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
let piBin;
|
|
160
|
+
try {
|
|
161
|
+
const piPkgPath = require.resolve(
|
|
162
|
+
"@mariozechner/pi-coding-agent/package.json",
|
|
163
|
+
);
|
|
164
|
+
const piPkg = JSON.parse(readFileSync(piPkgPath, "utf-8"));
|
|
165
|
+
const binEntry = piPkg.bin && (piPkg.bin.pi || Object.values(piPkg.bin)[0]);
|
|
166
|
+
if (!binEntry) throw new Error("No bin entry in pi package.json");
|
|
167
|
+
piBin = join(dirname(piPkgPath), binEntry);
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error("seedclub: could not locate pi runtime. Reinstall with:");
|
|
170
|
+
console.error(" npm install -g @clubnet/seedclub");
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Environment ─────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
process.env.PI_CODING_AGENT_DIR = SC_DIR;
|
|
177
|
+
process.env.PI_SKIP_VERSION_CHECK = "1";
|
|
178
|
+
process.env.NODE_OPTIONS = `--no-warnings ${process.env.NODE_OPTIONS || ""}`.trim();
|
|
179
|
+
|
|
180
|
+
// ── Spawn pi ────────────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
const args = process.argv.slice(2);
|
|
183
|
+
const child = spawn(process.execPath, [piBin, ...args], {
|
|
184
|
+
stdio: "inherit",
|
|
185
|
+
env: process.env,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
child.on("exit", (code, signal) => {
|
|
189
|
+
if (signal) {
|
|
190
|
+
process.kill(process.pid, signal);
|
|
191
|
+
} else {
|
|
192
|
+
process.exit(code ?? 1);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clubnet/seedclub",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "The Human+ Venture Network — AI agent for deal sourcing, research, and signal tracking",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/seedclub/seedclub.git"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "restricted"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"seedclub": "bin/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"postinstall": "node postinstall.js"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@mariozechner/pi-coding-agent": "0.52.9"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=22.0.0"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"bin/",
|
|
27
|
+
"assets/",
|
|
28
|
+
"postinstall.js"
|
|
29
|
+
]
|
|
30
|
+
}
|