Skip to content

Commit 4589f2d

Browse files
realuckyangclaude
andcommitted
Add 7 new preset apps: HackerNews, Crypto Market, Earthquake, Weather, GitHub Trending, Product Hunt, RSS Reader
HackerNews: HN API feed, AI summary & translation, bookmarks (DB) Crypto Market: CoinGecko top 50, 7d chart, AI market analysis Earthquake: USGS data, SVG world map, AI seismic analysis Weather: Open-Meteo forecast, city search & save (DB), AI life advice GitHub Trending: GitHub search API, language/time filter, AI repo analysis Product Hunt: daily launches, AI review RSS Reader: manage feeds, fetch/parse RSS, AI summarize, bookmarks (DB) All apps include zh/en i18n with __T_ placeholders and chatPanel integration. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent f166555 commit 4589f2d

68 files changed

Lines changed: 1841 additions & 10 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/coinmarket/api/index.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { readBody } from "../../../shared/http/readBody.js";
2+
import { json } from "../../../shared/http/json.js";
3+
import { list } from "../service/list.js";
4+
import { detail } from "../service/detail.js";
5+
import { analyze } from "../service/analyze.js";
6+
7+
const handleCoinmarketApi = async (req, res, path) => {
8+
if (path === "/apps/coinmarket/list" && req.method === "GET") {
9+
const data = await list();
10+
return json(res, data);
11+
}
12+
if (path === "/apps/coinmarket/detail" && req.method === "GET") {
13+
const url = new URL(req.url, `http://${req.headers.host}`);
14+
const id = url.searchParams.get("id") || "";
15+
const data = await detail({ id });
16+
if (data?.status) return json(res, { success: false, message: data.message }, data.status);
17+
return json(res, data);
18+
}
19+
if (path === "/apps/coinmarket/analyze" && req.method === "POST") {
20+
const body = await readBody(req);
21+
const data = await analyze(body);
22+
return json(res, data);
23+
}
24+
return false;
25+
};
26+
27+
export { handleCoinmarketApi };

apps/coinmarket/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { handleCoinmarketApi } from "./api/index.js";
2+
var stdin_default = {
3+
name: "coinmarket",
4+
match: (path) => path.startsWith("/apps/coinmarket/"),
5+
handleApi: handleCoinmarketApi
6+
};
7+
export {
8+
stdin_default as default
9+
};

apps/coinmarket/service/analyze.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { agentTask } from "../../app_shared/agentTask.js";
2+
3+
const analyze = async ({ coins, locale = "en" }) => {
4+
const lang = locale === "zh" ? "中文" : "English";
5+
const top10 = (coins || []).slice(0, 10).map((c) =>
6+
`${c.name} (${c.symbol}): $${c.price}, 24h ${c.change24h > 0 ? '+' : ''}${(c.change24h || 0).toFixed(1)}%, 7d ${c.change7d > 0 ? '+' : ''}${(c.change7d || 0).toFixed(1)}%`
7+
).join('\n');
8+
9+
const prompt = `Analyze the current crypto market based on this data, in ${lang}. Give 3-5 key observations about trends, notable movers, and market sentiment. Be concise.
10+
11+
${top10}
12+
13+
Return only the analysis, nothing else.`;
14+
15+
const result = await agentTask({ app: "coinmarket", title: "Market Analysis", prompt });
16+
return { success: true, analysis: result.response || "" };
17+
};
18+
19+
export { analyze };

apps/coinmarket/service/detail.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const CG_API = "https://api.coingecko.com/api/v3";
2+
3+
const detail = async ({ id }) => {
4+
if (!id) return { status: 400, message: "id is required" };
5+
const [info, chart] = await Promise.all([
6+
fetch(`${CG_API}/coins/${id}?localization=false&tickers=false&community_data=false&developer_data=false`).then((r) => r.json()),
7+
fetch(`${CG_API}/coins/${id}/market_chart?vs_currency=usd&days=7`).then((r) => r.json())
8+
]);
9+
return {
10+
success: true,
11+
coin: {
12+
id: info.id, symbol: info.symbol, name: info.name, image: info.image?.large,
13+
price: info.market_data?.current_price?.usd,
14+
marketCap: info.market_data?.market_cap?.usd,
15+
rank: info.market_cap_rank,
16+
change24h: info.market_data?.price_change_percentage_24h,
17+
change7d: info.market_data?.price_change_percentage_7d,
18+
high24h: info.market_data?.high_24h?.usd,
19+
low24h: info.market_data?.low_24h?.usd,
20+
ath: info.market_data?.ath?.usd,
21+
athDate: info.market_data?.ath_date?.usd,
22+
description: info.description?.en?.slice(0, 500) || ''
23+
},
24+
chart: (chart.prices || []).map(([t, p]) => ({ t, p }))
25+
};
26+
};
27+
28+
export { detail };

apps/coinmarket/service/list.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const CG_API = "https://api.coingecko.com/api/v3";
2+
3+
const list = async ({ currency = "usd", limit = 50 } = {}) => {
4+
const res = await fetch(
5+
`${CG_API}/coins/markets?vs_currency=${currency}&order=market_cap_desc&per_page=${limit}&page=1&sparkline=true&price_change_percentage=1h,24h,7d`
6+
);
7+
if (!res.ok) throw new Error(`CoinGecko API error ${res.status}`);
8+
const data = await res.json();
9+
return {
10+
success: true,
11+
coins: data.map((c) => ({
12+
id: c.id, symbol: c.symbol, name: c.name, image: c.image,
13+
price: c.current_price, marketCap: c.market_cap, rank: c.market_cap_rank,
14+
change1h: c.price_change_percentage_1h_in_currency,
15+
change24h: c.price_change_percentage_24h_in_currency,
16+
change7d: c.price_change_percentage_7d_in_currency,
17+
sparkline: c.sparkline_in_7d?.price || [],
18+
high24h: c.high_24h, low24h: c.low_24h, volume: c.total_volume
19+
}))
20+
};
21+
};
22+
23+
export { list };

apps/earthquake/api/index.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { readBody } from "../../../shared/http/readBody.js";
2+
import { json } from "../../../shared/http/json.js";
3+
import { list } from "../service/list.js";
4+
import { analyze } from "../service/analyze.js";
5+
6+
const handleEarthquakeApi = async (req, res, path) => {
7+
if (path === "/apps/earthquake/list" && req.method === "GET") {
8+
const url = new URL(req.url, `http://${req.headers.host}`);
9+
const minMag = Number(url.searchParams.get("minMagnitude") || 2.5);
10+
const days = Number(url.searchParams.get("days") || 7);
11+
const data = await list({ minMagnitude: minMag, days });
12+
return json(res, data);
13+
}
14+
if (path === "/apps/earthquake/analyze" && req.method === "POST") {
15+
const body = await readBody(req);
16+
const data = await analyze(body);
17+
return json(res, data);
18+
}
19+
return false;
20+
};
21+
22+
export { handleEarthquakeApi };

apps/earthquake/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { handleEarthquakeApi } from "./api/index.js";
2+
var stdin_default = {
3+
name: "earthquake",
4+
match: (path) => path.startsWith("/apps/earthquake/"),
5+
handleApi: handleEarthquakeApi
6+
};
7+
export {
8+
stdin_default as default
9+
};

apps/earthquake/service/analyze.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { agentTask } from "../../app_shared/agentTask.js";
2+
3+
const analyze = async ({ quakes, locale = "en" }) => {
4+
const lang = locale === "zh" ? "中文" : "English";
5+
const summary = (quakes || []).slice(0, 20).map((q) =>
6+
`M${q.mag} - ${q.place} (depth ${q.depth}km, ${new Date(q.time).toISOString().slice(0, 16)})`
7+
).join('\n');
8+
9+
const prompt = `Analyze recent global earthquake activity based on this data, in ${lang}. Identify patterns: active regions, magnitude trends, any notable events. Give 3-5 concise observations.
10+
11+
${summary}
12+
13+
Return only the analysis, nothing else.`;
14+
15+
const result = await agentTask({ app: "earthquake", title: "Seismic Analysis", prompt });
16+
return { success: true, analysis: result.response || "" };
17+
};
18+
19+
export { analyze };

apps/earthquake/service/list.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const USGS_API = "https://earthquake.usgs.gov/fdsnws/event/1/query";
2+
3+
const list = async ({ minMagnitude = 2.5, days = 7, limit = 200 } = {}) => {
4+
const end = new Date();
5+
const start = new Date(end.getTime() - days * 86400000);
6+
const url = `${USGS_API}?format=geojson&starttime=${start.toISOString()}&endtime=${end.toISOString()}&minmagnitude=${minMagnitude}&limit=${limit}&orderby=time`;
7+
const res = await fetch(url);
8+
if (!res.ok) throw new Error(`USGS API error ${res.status}`);
9+
const data = await res.json();
10+
return {
11+
success: true,
12+
quakes: (data.features || []).map((f) => ({
13+
id: f.id,
14+
mag: f.properties.mag,
15+
place: f.properties.place || '',
16+
time: f.properties.time,
17+
url: f.properties.url,
18+
tsunami: f.properties.tsunami,
19+
lng: f.geometry?.coordinates?.[0],
20+
lat: f.geometry?.coordinates?.[1],
21+
depth: f.geometry?.coordinates?.[2]
22+
})),
23+
count: data.metadata?.count || 0
24+
};
25+
};
26+
27+
export { list };

apps/ghtrending/api/index.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { readBody } from "../../../shared/http/readBody.js";
2+
import { json } from "../../../shared/http/json.js";
3+
import { list } from "../service/list.js";
4+
import { analyze } from "../service/analyze.js";
5+
6+
const handleGhtrendingApi = async (req, res, path) => {
7+
if (path === "/apps/ghtrending/list" && req.method === "GET") {
8+
const url = new URL(req.url, `http://${req.headers.host}`);
9+
const language = url.searchParams.get("language") || "";
10+
const since = url.searchParams.get("since") || "daily";
11+
const data = await list({ language, since });
12+
return json(res, data);
13+
}
14+
if (path === "/apps/ghtrending/analyze" && req.method === "POST") {
15+
const body = await readBody(req);
16+
const data = await analyze(body);
17+
return json(res, data);
18+
}
19+
return false;
20+
};
21+
22+
export { handleGhtrendingApi };

0 commit comments

Comments
 (0)