Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 55 additions & 6 deletions scripts/publication-contract.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import MarkdownIt from "markdown-it";
import { EXPECTED_DESCRIPTION } from "./artifact-contract.mjs";

const issuesUrl = "https://github.com/TiantianFlow/ai-limits/issues";
const supportedProvidersUrl =
"https://github.com/TiantianFlow/ai-limits/blob/main/SUPPORTED_PROVIDERS.md";
const supportedProvidersUrlZh =
"https://github.com/TiantianFlow/ai-limits/blob/main/SUPPORTED_PROVIDERS.zh-CN.md";
const storeUrl =
"https://chromewebstore.google.com/detail/ai-limits/hcfdchpajckemcdflcjhigngpipdkdeo";
const releaseUrl = "https://github.com/TiantianFlow/ai-limits/releases/latest";
Expand All @@ -29,6 +33,8 @@ const publicRouteGate =
"After the repository is public and before Chrome Web Store submission, verify the homepage, privacy policy, and support URLs are reachable in a signed-out browser.";
const paceAvailabilityStatement =
"For quota windows with a reliable reset time plus either a start time or window duration, a pace signal compares quota consumed with elapsed time.";
const providerBrandPattern =
/\b(?:ChatGPT|Claude|Kimi|Cursor|Grok|Mistral|Perplexity|ElevenLabs|New API|LiteLLM|ClawRouter|sub2api|LLM Proxy|DeepSeek|Moonshot|DeepInfra|Fireworks|OpenAI|GroqCloud|OpenRouter)\b/i;
const requiredVisibleFaqStatements = [
{
key: "faq",
Expand Down Expand Up @@ -306,12 +312,6 @@ const requiredVisibleV030Statements = [
"Disconnect deletes that instance's credential, configuration, usage, refresh status, and History before permission cleanup; a shared origin remains granted while another active instance owns it, and final-owner removal is best-effort with durable retry evidence.",
error: "Privacy policy is missing the instance disconnect and final-owner behavior.",
},
{
key: "listing",
statement:
"AI Limits supports multiple independent New API instances, including multiple separately labeled keys on the same origin.",
error: "Store listing is missing multi-instance New API support.",
},
{
key: "listing",
statement:
Expand Down Expand Up @@ -562,6 +562,16 @@ function extractQualifyingMarkdownProse(document) {
return normalizeWhitespace(visible.join(" "));
}

function extractMarkdownSection(document, startHeading, endHeading) {
const source = document ?? "";
const start = source.indexOf(startHeading);
if (start === -1) return "";

const afterStart = source.slice(start + startHeading.length);
const end = afterStart.indexOf(endHeading);
return end === -1 ? afterStart : afterStart.slice(0, end);
}

export function validatePublicationDocuments(documents) {
const errors = [];
if (Object.values(documents).some((source) => {
Expand Down Expand Up @@ -656,6 +666,21 @@ export function validatePublicationDocuments(documents) {
const listing = normalizeWhitespace(listingSource);
const visibleListing = extractQualifyingMarkdownProse(listingSource);
const listingDestinations = extractInlineMarkdownLinkDestinations(listingSource);
const listingZhSource = documents.listingZh ?? "";
const detailedListing = extractMarkdownSection(
listingSource,
"## Detailed description",
"## Permission justifications",
);
const detailedListingZh = extractMarkdownSection(
listingZhSource,
"## 详细说明",
"## 权限说明",
);
const visibleDetailedListing = extractQualifyingMarkdownProse(detailedListing);
const visibleDetailedListingZh = extractQualifyingMarkdownProse(
detailedListingZh,
);

if (!privacy.includes(limitedUseStatement)) {
errors.push("Privacy policy is missing the Limited Use compliance statement.");
Expand Down Expand Up @@ -719,6 +744,30 @@ export function validatePublicationDocuments(documents) {
);
}

if (!visibleDetailedListing.includes(supportedProvidersUrl)) {
errors.push(
"English Store detailed description is missing the direct supported-providers URL.",
);
}

if (providerBrandPattern.test(visibleDetailedListing)) {
errors.push(
"English Store detailed description must not enumerate provider brands.",
);
}

if (!visibleDetailedListingZh.includes(supportedProvidersUrlZh)) {
errors.push(
"Simplified Chinese Store detailed description is missing the direct supported-providers URL.",
);
}

if (providerBrandPattern.test(visibleDetailedListingZh)) {
errors.push(
"Simplified Chinese Store detailed description must not enumerate provider brands.",
);
}

if (!documents.license?.includes("Copyright (c) 2026 TiantianFlow")) {
errors.push("LICENSE is missing the 2026 TiantianFlow copyright notice.");
}
Expand Down
48 changes: 42 additions & 6 deletions scripts/publication-contract.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import { validatePublicationDocuments } from "./publication-contract.mjs";

const issuesUrl = "https://github.com/TiantianFlow/ai-limits/issues";
const issuesLink = `[GitHub Issues](${issuesUrl})`;
const supportedProvidersUrl =
"https://github.com/TiantianFlow/ai-limits/blob/main/SUPPORTED_PROVIDERS.md";
const supportedProvidersUrlZh =
"https://github.com/TiantianFlow/ai-limits/blob/main/SUPPORTED_PROVIDERS.zh-CN.md";
const storeUrl =
"https://chromewebstore.google.com/detail/ai-limits/hcfdchpajckemcdflcjhigngpipdkdeo";
const storeLink = `[Chrome Web Store](${storeUrl})`;
Expand Down Expand Up @@ -294,12 +298,6 @@ const v030PublicationStatements = [
"Disconnect deletes that instance's credential, configuration, usage, refresh status, and History before permission cleanup; a shared origin remains granted while another active instance owns it, and final-owner removal is best-effort with durable retry evidence.",
error: "Privacy policy is missing the instance disconnect and final-owner behavior.",
},
{
key: "listing",
statement:
"AI Limits supports multiple independent New API instances, including multiple separately labeled keys on the same origin.",
error: "Store listing is missing multi-instance New API support.",
},
{
key: "listing",
statement:
Expand Down Expand Up @@ -412,6 +410,14 @@ const valid = {
...v030PublicationStatements
.filter(({ key }) => key === "listing")
.map(({ statement }) => statement),
"## Detailed description",
supportedProvidersUrl,
"## Permission justifications",
].join("\n"),
listingZh: [
"## 详细说明",
supportedProvidersUrlZh,
"## 权限说明",
].join("\n"),
license: "MIT License\n\nCopyright (c) 2026 TiantianFlow",
};
Expand Down Expand Up @@ -615,6 +621,36 @@ describe("publication content", () => {
);
});

it("requires direct compatibility links and rejects provider inventories in Store descriptions", () => {
expect(
validatePublicationDocuments({
...valid,
listing: valid.listing.replace(supportedProvidersUrl, ""),
}),
).toContain(
"English Store detailed description is missing the direct supported-providers URL.",
);
expect(
validatePublicationDocuments({
...valid,
listingZh: valid.listingZh.replace(supportedProvidersUrlZh, ""),
}),
).toContain(
"Simplified Chinese Store detailed description is missing the direct supported-providers URL.",
);
expect(
validatePublicationDocuments({
...valid,
listing: valid.listing.replace(
supportedProvidersUrl,
`${supportedProvidersUrl}\nChatGPT`,
),
}),
).toContain(
"English Store detailed description must not enumerate provider brands.",
);
});

it.each(elevenLabsPublicationStatements)(
"requires visible ElevenLabs disclosure in $key: $error",
({ key, statement, error }) => {
Expand Down
4 changes: 4 additions & 0 deletions scripts/verify-publication.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const documents = {
resolve("store-assets/chrome-web-store/listing-en.md"),
"utf8",
),
listingZh: await readFile(
resolve("store-assets/chrome-web-store/listing-zh_CN.md"),
"utf8",
),
license: await readFile(resolve("LICENSE"), "utf8"),
};

Expand Down
36 changes: 9 additions & 27 deletions store-assets/chrome-web-store/listing-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@ Regeneration and validation instructions are in

AI Limits gives a user one Chrome side panel for viewing current subscription
usage as **Used** or **Left**, reset timing, pace, and local quota-history
graphs across twenty supported providers (see the supported-providers document
for the full roster). Browser-session providers use an account already signed in within the
user's browser profile; API-key providers use a key the user connects through
guided setup.
graphs across twenty supported services. Browser-session services use an account
already signed in within the user's browser profile; API-key services use a key
the user connects through guided setup.

## Short description

Expand All @@ -65,29 +64,12 @@ compares quota consumed with elapsed time.
Every quota window can open its own local History screen, so you can see how
your usage has trended over time, right on your device.

**ChatGPT** and **Claude** show your plan, usage windows, resets, and available
credits straight from your signed-in account. **Kimi** reads your usage and
subscription status directly, with a brief, visible recovery step if your
session ever needs refreshing. **Cursor** shows monthly Cursor-model and
Other-model limits plus weekly Grok Bot usage, and a per-model included-usage
breakdown grouped into Cursor Models and Other Models with input, output, cost,
and provider totals. That detail view is a generic per-provider surface; today
only Cursor fills it. **Grok** reads your signed-in consumer plan and
weekly or monthly usage pool; short per-mode windows are not shown.
**Mistral** shows month-to-date spend and available credits,
while **Perplexity** shows recurring, purchased, and promotional credit pools.
**ElevenLabs** connects with a scoped, read-only API key you create yourself.
**New API** connects self-hosted or third-party API-compatible instances with a
key you provide. AI Limits supports multiple independent New API instances,
including multiple separately labeled keys on the same origin.
**LiteLLM**, **ClawRouter**, **sub2api**, and **LLM Proxy** connect configurable
instances with a key you provide. **DeepSeek** and **Moonshot** show developer
platform balances, **DeepInfra** shows monthly spend against a limit or its
reported balance, and **Fireworks** shows rated spend over the last 30 days.
**OpenAI** reads the Platform organization and developer billing surface
(organization costs and completion usage, with a credit-grants fallback),
**GroqCloud** shows five-minute usage rates, and **OpenRouter** shows credits and
an optional key budget.
AI Limits supports 20 AI services. Connect only the services you use: every
connection is opt-in. Browser-session services use the account already signed
in to Chrome, while API services use a key you provide.

Full compatibility details, connection methods, and known limitations:
https://github.com/TiantianFlow/ai-limits/blob/main/SUPPORTED_PROVIDERS.md

The side panel supports English and Simplified Chinese. It follows Chrome when
Chrome is set to one of those languages, and falls back to English for other
Expand Down
7 changes: 5 additions & 2 deletions store-assets/chrome-web-store/listing-zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

## 单一用途

AI Limits 在 Chrome 侧边栏中为你提供一个统一视图,用来跟踪已连接 AI 服务的订阅用量(已用或剩余)、重置时间、消耗节奏和本地用量历史,覆盖二十个受支持的服务(完整名单见支持的服务文档)。基于浏览器会话的服务使用你在浏览器配置中已经登录的账号;基于 API 密钥的服务通过引导流程连接你自己创建的密钥。
AI Limits 在 Chrome 侧边栏中为你提供一个统一视图,用来跟踪已连接 AI 服务的订阅用量(已用或剩余)、重置时间、消耗节奏和本地用量历史,覆盖二十个受支持的服务。基于浏览器会话的服务使用你在浏览器配置中已经登录的账号;基于 API 密钥的服务通过引导流程连接你自己创建的密钥。

## 简短说明

Expand All @@ -42,7 +42,10 @@ AI Limits 把你的 AI 订阅用量集中显示在同一个 Chrome 侧边栏中

每个用量周期都可以打开自己的本地历史界面,方便你直接在自己的设备上查看用量随时间的变化趋势。

**ChatGPT** 和 **Claude** 直接从你已登录的账号显示套餐、用量周期、重置时间和可用额度。**Kimi** 直接读取你的用量和订阅状态;如果会话需要刷新,会有一个简短、可见的恢复步骤。**Cursor** 会显示 Cursor 模型与其他模型的月度限额以及 Grok Bot 每周用量,并按 Cursor Models 和 Other Models 分组列出各模型的包含用量(输入、输出、费用和服务商合计)。该明细视图是通用的分服务界面;目前只有 Cursor 会填充它。**Grok** 读取你已登录的消费者版套餐和每周或每月用量池,不显示按模式返回的短窗。**Mistral** 显示本月至今支出和可用额度,**Perplexity** 显示周期性、已购买和促销额度池。**ElevenLabs** 通过你自己创建的、权限受限的只读 API 密钥连接。**New API** 用你提供的密钥连接自建或第三方 API 兼容实例。AI Limits 支持多个相互独立的 New API 实例,包括同一来源下多个分别标注的密钥。**LiteLLM**、**ClawRouter**、**sub2api** 和 **LLM Proxy** 使用你提供的密钥连接可配置实例。**DeepSeek** 和 **Moonshot** 显示开发平台余额,**DeepInfra** 显示当月支出相对上限的配额或服务商报告的余额,**Fireworks** 显示最近 30 天已计价支出。**OpenAI** 读取平台组织/开发者账单(组织支出和补全用量,并在需要时回退到额度接口),**GroqCloud** 显示五分钟用量速率,**OpenRouter** 显示额度和可选密钥预算。
AI Limits 支持 20 个 AI 服务。你可以逐个连接实际使用的服务;每项连接都需要主动启用。基于浏览器会话的服务使用你已登录的 Chrome 账号;基于 API 的服务使用你自行提供的密钥。

完整的受支持服务、连接方式和已知限制:
https://github.com/TiantianFlow/ai-limits/blob/main/SUPPORTED_PROVIDERS.zh-CN.md

侧边栏仅支持英文和简体中文。跟随 Chrome 时,仅在 Chrome 为英文或简体中文时使用对应语言,其他 Chrome 语言会回退到英文。也可以在 Settings 中为侧边栏固定英文或简体中文。Chrome 自身的菜单和扩展名称仍跟随 Chrome 的语言。

Expand Down
Loading