mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
Merge branch 'main' into feat/auto-update
This commit is contained in:
@@ -3,6 +3,13 @@ name: Publish
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
package:
|
||||
description: "Which package set to publish"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- bailian-cli
|
||||
- knowledge-studio-cli
|
||||
mode:
|
||||
description: "Publish mode"
|
||||
required: true
|
||||
@@ -16,13 +23,13 @@ on:
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: publish-${{ inputs.mode }}-${{ inputs.channel }}
|
||||
group: publish-${{ inputs.package }}-${{ inputs.mode }}-${{ inputs.channel }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
publish-stable:
|
||||
if: inputs.mode == 'stable'
|
||||
name: publish stable to npm + tag
|
||||
name: publish stable (${{ inputs.package }}) to npm + tag
|
||||
runs-on: ubuntu-latest
|
||||
environment: production # Required Reviewers gate
|
||||
permissions:
|
||||
@@ -51,11 +58,11 @@ jobs:
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: publish-stable
|
||||
run: node tools/release/publish-stable.mjs
|
||||
run: node tools/release/publish-stable.mjs ${{ inputs.package == 'knowledge-studio-cli' && '--knowledge' || '' }}
|
||||
|
||||
publish-channel:
|
||||
if: inputs.mode == 'channel'
|
||||
name: publish beta to npm
|
||||
name: publish channel (${{ inputs.package }}) to npm
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read # no tag, no Release; just publish
|
||||
@@ -83,4 +90,4 @@ jobs:
|
||||
- run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: publish-channel
|
||||
run: node tools/release/publish-channel.mjs --channel "${{ inputs.channel }}"
|
||||
run: node tools/release/publish-channel.mjs ${{ inputs.package == 'knowledge-studio-cli' && '--knowledge' || '' }} --channel "${{ inputs.channel }}"
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
24.16.0
|
||||
24
|
||||
|
||||
@@ -122,6 +122,12 @@ bl quota check # 查看当前用量 vs
|
||||
bl quota check --model qwen3.6-plus --period 5 # 查看最近 5 分钟用量
|
||||
bl quota request --model qwen3.6-plus --tpm 6000000 # 申请临时 TPM 提额
|
||||
bl quota history # 查看提额历史记录
|
||||
|
||||
# Token Plan 团队版管理(需 AK/SK,见下方认证说明)
|
||||
bl token-plan list-seats # 查看订阅席位明细
|
||||
bl token-plan add-member --account-name dev --org-id org_xxx
|
||||
bl token-plan assign-seats --workspace-id ws_xxx --seat-type standard --account-id acc_xxx
|
||||
bl token-plan create-key --account-id acc_xxx --workspace-id ws_xxx
|
||||
```
|
||||
|
||||
> 更多案例与使用场景:[阿里云百炼 CLI 官方主页](https://bailian.console.aliyun.com/cli?source_channel=cli_github&)
|
||||
@@ -151,9 +157,9 @@ bl text chat --api-key sk-xxxxx --message "你好"
|
||||
bl auth login --console
|
||||
```
|
||||
|
||||
### 阿里云 AK/SK(仅知识库检索)
|
||||
### 阿里云 AK/SK(知识库检索与 Token Plan)
|
||||
|
||||
`knowledge retrieve` 命令需要阿里云 AccessKey。前往 [RAM 控制台](https://ram.console.aliyun.com/manage/ak) 获取。
|
||||
`knowledge retrieve` 与 `token-plan` 命令组需要阿里云 AccessKey。前往 [RAM 控制台](https://ram.console.aliyun.com/manage/ak) 获取。
|
||||
|
||||
> 建议:创建 RAM 子账号并授予最小权限,避免使用主账号 AK/SK。
|
||||
|
||||
|
||||
@@ -46,6 +46,10 @@ import quotaList from "./quota/list.ts";
|
||||
import quotaRequest from "./quota/request.ts";
|
||||
import quotaHistory from "./quota/history.ts";
|
||||
import quotaCheck from "./quota/check.ts";
|
||||
import tokenPlanListSeats from "./token-plan/list-seats.ts";
|
||||
import tokenPlanCreateKey from "./token-plan/create-key.ts";
|
||||
import tokenPlanAssignSeats from "./token-plan/assign-seats.ts";
|
||||
import tokenPlanAddMember from "./token-plan/add-member.ts";
|
||||
|
||||
/** Command registry map (no dependency on registry.ts — safe for build-time import). */
|
||||
export const commands: Record<string, Command> = {
|
||||
@@ -94,5 +98,9 @@ export const commands: Record<string, Command> = {
|
||||
"quota request": quotaRequest,
|
||||
"quota history": quotaHistory,
|
||||
"quota check": quotaCheck,
|
||||
"token-plan list-seats": tokenPlanListSeats,
|
||||
"token-plan create-key": tokenPlanCreateKey,
|
||||
"token-plan assign-seats": tokenPlanAssignSeats,
|
||||
"token-plan add-member": tokenPlanAddMember,
|
||||
update: update,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "../../output/output.ts";
|
||||
import { padEnd } from "../../output/cjk-width.ts";
|
||||
import type { AddOrganizationMemberResponse } from "./types.ts";
|
||||
import {
|
||||
TOKEN_PLAN_AK_OPTIONS,
|
||||
TOKEN_PLAN_COMMON_QUERY_OPTIONS,
|
||||
appendCommonQueryParams,
|
||||
callTokenPlanApi,
|
||||
prepareTokenPlanRequest,
|
||||
resolveTokenPlanCredentials,
|
||||
type TokenPlanQueryParams,
|
||||
} from "./utils.ts";
|
||||
|
||||
const API_ACTION = "AddOrganizationMember";
|
||||
const API_PATH = "/tokenplan/organization/member-additions";
|
||||
|
||||
const DEFAULT_ORG_ROLE = "ORG_MEMBER";
|
||||
|
||||
export default defineCommand({
|
||||
name: "token-plan add-member",
|
||||
description: "Add a member to a Token Plan organization",
|
||||
usage: "bl token-plan add-member --account-name <name> --org-id <id> [flags]",
|
||||
options: [
|
||||
{ flag: "--account-name <name>", description: "Member display name", required: true },
|
||||
{ flag: "--org-id <id>", description: "Organization ID", required: true },
|
||||
{
|
||||
flag: "--org-role-code <code>",
|
||||
description: "Organization role: ORG_ADMIN or ORG_MEMBER (default: ORG_MEMBER)",
|
||||
},
|
||||
{
|
||||
flag: "--spec-type <type>",
|
||||
description: "Seat tier to assign on creation: standard, pro, or max",
|
||||
},
|
||||
...TOKEN_PLAN_COMMON_QUERY_OPTIONS,
|
||||
...TOKEN_PLAN_AK_OPTIONS,
|
||||
],
|
||||
examples: [
|
||||
"bl token-plan add-member --account-name dev_user --org-id org_123",
|
||||
"bl token-plan add-member --account-name admin_user --org-id org_123 --org-role-code ORG_ADMIN",
|
||||
"bl token-plan add-member --account-name member1 --org-id org_123 --spec-type standard",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const format = detectOutputFormat(config.output);
|
||||
const credentials = resolveTokenPlanCredentials(config, flags);
|
||||
|
||||
const accountName = flags.accountName as string | undefined;
|
||||
const orgId = flags.orgId as string | undefined;
|
||||
if (!accountName) {
|
||||
throw new BailianError("Missing required argument --account-name.", ExitCode.USAGE);
|
||||
}
|
||||
if (!orgId) {
|
||||
throw new BailianError("Missing required argument --org-id.", ExitCode.USAGE);
|
||||
}
|
||||
|
||||
const queryParams = buildQueryParams(flags);
|
||||
|
||||
if (config.dryRun) {
|
||||
const { endpoint, queryParams: query } = prepareTokenPlanRequest(
|
||||
config,
|
||||
API_PATH,
|
||||
queryParams,
|
||||
);
|
||||
emitResult({ endpoint, query }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await callTokenPlanApi<AddOrganizationMemberResponse>({
|
||||
config,
|
||||
credentials,
|
||||
action: API_ACTION,
|
||||
path: API_PATH,
|
||||
method: "POST",
|
||||
queryParams,
|
||||
});
|
||||
|
||||
if (config.quiet || format === "text") {
|
||||
emitTextMember(data);
|
||||
} else {
|
||||
emitResult(data, format);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function buildQueryParams(flags: GlobalFlags): TokenPlanQueryParams {
|
||||
const params: TokenPlanQueryParams = {};
|
||||
|
||||
if (flags.accountName) params.AccountName = flags.accountName as string;
|
||||
if (flags.orgId) params.OrgId = flags.orgId as string;
|
||||
params.OrgRoleCode =
|
||||
typeof flags.orgRoleCode === "string" && flags.orgRoleCode.length > 0
|
||||
? flags.orgRoleCode
|
||||
: DEFAULT_ORG_ROLE;
|
||||
if (flags.specType) params.SpecType = flags.specType as string;
|
||||
appendCommonQueryParams(params, flags);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function emitTextMember(data: AddOrganizationMemberResponse): void {
|
||||
const item = data.Data;
|
||||
if (!item) {
|
||||
emitBare("Member added.");
|
||||
return;
|
||||
}
|
||||
|
||||
emitBare(`${padEnd("AccountId", 14)} ${item.AccountId ?? "-"}`);
|
||||
emitBare(`${padEnd("SeatAssigned", 14)} ${String(item.SeatAssigned ?? "-")}`);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* ACS3-HMAC-SHA256 signing for ModelStudio Token Plan POP APIs (query-string style).
|
||||
*
|
||||
* Extends the core ROA signer with canonical query string support required by
|
||||
* Token Plan endpoints that pass parameters in the URL query.
|
||||
*/
|
||||
|
||||
import { createHmac, createHash, randomUUID } from "crypto";
|
||||
|
||||
export interface TokenPlanAkSignConfig {
|
||||
accessKeyId: string;
|
||||
accessKeySecret: string;
|
||||
action: string;
|
||||
version: string;
|
||||
body: string;
|
||||
host: string;
|
||||
pathname: string;
|
||||
method?: string;
|
||||
/** ACS3 canonical query string (sorted, encoded, no leading `?`). Empty for POST body-only APIs. */
|
||||
queryString?: string;
|
||||
}
|
||||
|
||||
/** Build ACS3 canonical query string from POP query parameters. */
|
||||
export function buildCanonicalQuery(params: Record<string, string | string[] | undefined>): string {
|
||||
const pairs: Array<[string, string]> = [];
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value === undefined || value === "") continue;
|
||||
if (Array.isArray(value)) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const v = value[i];
|
||||
if (v !== "") pairs.push([`${key}.${i + 1}`, v]);
|
||||
}
|
||||
} else {
|
||||
pairs.push([key, value]);
|
||||
}
|
||||
}
|
||||
pairs.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
||||
return pairs.map(([k, v]) => `${encodeRFC3986(k)}=${encodeRFC3986(v)}`).join("&");
|
||||
}
|
||||
|
||||
function encodeRFC3986(str: string): string {
|
||||
return encodeURIComponent(str).replace(
|
||||
/[!'()*]/g,
|
||||
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function signTokenPlanRequest(cfg: TokenPlanAkSignConfig): Record<string, string> {
|
||||
const method = cfg.method ?? "POST";
|
||||
const now = new Date();
|
||||
const dateISO = now.toISOString().replace(/\.\d{3}Z$/, "Z");
|
||||
const nonce = randomUUID();
|
||||
|
||||
const hashedBody = sha256Hex(cfg.body);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
host: cfg.host,
|
||||
"x-acs-action": cfg.action,
|
||||
"x-acs-version": cfg.version,
|
||||
"x-acs-date": dateISO,
|
||||
"x-acs-signature-nonce": nonce,
|
||||
"x-acs-content-sha256": hashedBody,
|
||||
"content-type": "application/json",
|
||||
};
|
||||
|
||||
const signedHeaderKeys = Object.keys(headers)
|
||||
.filter((k) => k === "host" || k === "content-type" || k.startsWith("x-acs-"))
|
||||
.sort();
|
||||
|
||||
const canonicalHeaders = signedHeaderKeys.map((k) => `${k}:${headers[k]}`).join("\n") + "\n";
|
||||
|
||||
const signedHeadersStr = signedHeaderKeys.join(";");
|
||||
|
||||
const queryString = cfg.queryString ?? "";
|
||||
|
||||
const canonicalRequest = [
|
||||
method,
|
||||
cfg.pathname,
|
||||
queryString,
|
||||
canonicalHeaders,
|
||||
signedHeadersStr,
|
||||
hashedBody,
|
||||
].join("\n");
|
||||
|
||||
const algorithm = "ACS3-HMAC-SHA256";
|
||||
const hashedCanonical = sha256Hex(canonicalRequest);
|
||||
const stringToSign = `${algorithm}\n${hashedCanonical}`;
|
||||
|
||||
const signature = hmacSHA256Hex(cfg.accessKeySecret, stringToSign);
|
||||
|
||||
headers["authorization"] =
|
||||
`${algorithm} Credential=${cfg.accessKeyId},SignedHeaders=${signedHeadersStr},Signature=${signature}`;
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
function sha256Hex(data: string): string {
|
||||
return createHash("sha256").update(data, "utf8").digest("hex");
|
||||
}
|
||||
|
||||
function hmacSHA256Hex(key: string, data: string): string {
|
||||
return createHmac("sha256", key).update(data, "utf8").digest("hex");
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "../../output/output.ts";
|
||||
import type { BatchAssignSeatsResponse } from "./types.ts";
|
||||
import {
|
||||
TOKEN_PLAN_AK_OPTIONS,
|
||||
TOKEN_PLAN_COMMON_QUERY_OPTIONS,
|
||||
TOKEN_PLAN_WORKSPACE_OPTION,
|
||||
appendCommonQueryParams,
|
||||
callTokenPlanApi,
|
||||
prepareTokenPlanRequest,
|
||||
requireWorkspaceId,
|
||||
resolveTokenPlanCredentials,
|
||||
type TokenPlanQueryParams,
|
||||
} from "./utils.ts";
|
||||
|
||||
const API_ACTION = "BatchAssignSeats";
|
||||
const API_PATH = "/tokenplan/subscription/seat-assignments";
|
||||
|
||||
export default defineCommand({
|
||||
name: "token-plan assign-seats",
|
||||
description: "Batch assign Token Plan seats to members",
|
||||
usage:
|
||||
"bl token-plan assign-seats --workspace-id <id> --seat-type <type> --account-id <id> [flags]",
|
||||
options: [
|
||||
TOKEN_PLAN_WORKSPACE_OPTION,
|
||||
{
|
||||
flag: "--seat-type <type>",
|
||||
description: "Seat tier: standard, pro, or max",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
flag: "--account-id <id>",
|
||||
description: "Target member account ID (repeatable)",
|
||||
type: "array",
|
||||
},
|
||||
...TOKEN_PLAN_COMMON_QUERY_OPTIONS,
|
||||
{
|
||||
flag: "--locale <locale>",
|
||||
description: "Language: zh-CN or en-US",
|
||||
},
|
||||
...TOKEN_PLAN_AK_OPTIONS,
|
||||
],
|
||||
examples: [
|
||||
"bl token-plan assign-seats --workspace-id ws_456 --seat-type standard --account-id acc_123",
|
||||
"bl token-plan assign-seats --workspace-id ws_456 --seat-type pro --account-id acc_1 --account-id acc_2",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const format = detectOutputFormat(config.output);
|
||||
const credentials = resolveTokenPlanCredentials(config, flags);
|
||||
|
||||
const workspaceId = requireWorkspaceId(config, flags);
|
||||
const seatType = flags.seatType as string | undefined;
|
||||
if (!seatType) {
|
||||
throw new BailianError("Missing required argument --seat-type.", ExitCode.USAGE);
|
||||
}
|
||||
|
||||
const accountIds = flags.accountId as string[] | undefined;
|
||||
if (!accountIds || accountIds.length === 0) {
|
||||
throw new BailianError("Missing required argument --account-id.", ExitCode.USAGE);
|
||||
}
|
||||
|
||||
const queryParams = buildQueryParams(flags, workspaceId);
|
||||
|
||||
if (config.dryRun) {
|
||||
const { endpoint, queryParams: query } = prepareTokenPlanRequest(
|
||||
config,
|
||||
API_PATH,
|
||||
queryParams,
|
||||
);
|
||||
emitResult({ endpoint, query }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await callTokenPlanApi<BatchAssignSeatsResponse>({
|
||||
config,
|
||||
credentials,
|
||||
action: API_ACTION,
|
||||
path: API_PATH,
|
||||
method: "POST",
|
||||
queryParams,
|
||||
});
|
||||
|
||||
if (config.quiet || format === "text") {
|
||||
emitBare("Seats assigned successfully.");
|
||||
} else {
|
||||
emitResult(data, format);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function buildQueryParams(flags: GlobalFlags, workspaceId: string): TokenPlanQueryParams {
|
||||
const params: TokenPlanQueryParams = {};
|
||||
|
||||
params.WorkspaceId = workspaceId;
|
||||
if (flags.seatType) params.SeatType = flags.seatType as string;
|
||||
appendCommonQueryParams(params, flags);
|
||||
if (flags.locale) params.Locale = flags.locale as string;
|
||||
|
||||
const accountIds = flags.accountId as string[] | undefined;
|
||||
if (accountIds && accountIds.length > 0) {
|
||||
params.AccountIds = accountIds;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "../../output/output.ts";
|
||||
import { padEnd } from "../../output/cjk-width.ts";
|
||||
import type { CreateTokenPlanKeyResponse } from "./types.ts";
|
||||
import {
|
||||
TOKEN_PLAN_AK_OPTIONS,
|
||||
TOKEN_PLAN_COMMON_QUERY_OPTIONS,
|
||||
TOKEN_PLAN_WORKSPACE_OPTION,
|
||||
appendCommonQueryParams,
|
||||
callTokenPlanApi,
|
||||
prepareTokenPlanRequest,
|
||||
requireWorkspaceId,
|
||||
resolveTokenPlanCredentials,
|
||||
type TokenPlanQueryParams,
|
||||
} from "./utils.ts";
|
||||
|
||||
const API_ACTION = "CreateTokenPlanKey";
|
||||
const API_PATH = "/tokenplan/api-keys";
|
||||
|
||||
export default defineCommand({
|
||||
name: "token-plan create-key",
|
||||
description: "Create a Token Plan API key for a seat",
|
||||
usage: "bl token-plan create-key --account-id <id> --workspace-id <id> [flags]",
|
||||
options: [
|
||||
{ flag: "--account-id <id>", description: "Target member account ID", required: true },
|
||||
TOKEN_PLAN_WORKSPACE_OPTION,
|
||||
{ flag: "--description <text>", description: "API key description" },
|
||||
...TOKEN_PLAN_COMMON_QUERY_OPTIONS,
|
||||
...TOKEN_PLAN_AK_OPTIONS,
|
||||
],
|
||||
examples: [
|
||||
"bl token-plan create-key --account-id acc_123 --workspace-id ws_456",
|
||||
"bl token-plan create-key --account-id acc_123 --workspace-id ws_456 --description 'Dev key'",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const format = detectOutputFormat(config.output);
|
||||
const credentials = resolveTokenPlanCredentials(config, flags);
|
||||
|
||||
const accountId = flags.accountId as string | undefined;
|
||||
const workspaceId = requireWorkspaceId(config, flags);
|
||||
if (!accountId) {
|
||||
throw new BailianError("Missing required argument --account-id.", ExitCode.USAGE);
|
||||
}
|
||||
|
||||
const queryParams = buildQueryParams(flags, { accountId, workspaceId });
|
||||
|
||||
if (config.dryRun) {
|
||||
const { endpoint, queryParams: query } = prepareTokenPlanRequest(
|
||||
config,
|
||||
API_PATH,
|
||||
queryParams,
|
||||
);
|
||||
emitResult({ endpoint, query }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await callTokenPlanApi<CreateTokenPlanKeyResponse>({
|
||||
config,
|
||||
credentials,
|
||||
action: API_ACTION,
|
||||
path: API_PATH,
|
||||
method: "POST",
|
||||
queryParams,
|
||||
});
|
||||
|
||||
if (config.quiet || format === "text") {
|
||||
emitTextKey(data);
|
||||
} else {
|
||||
emitResult(data, format);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function buildQueryParams(
|
||||
flags: GlobalFlags,
|
||||
resolved: { accountId: string; workspaceId: string },
|
||||
): TokenPlanQueryParams {
|
||||
const params: TokenPlanQueryParams = {};
|
||||
|
||||
params.AccountId = resolved.accountId;
|
||||
params.WorkspaceId = resolved.workspaceId;
|
||||
if (flags.description) params.Description = flags.description as string;
|
||||
appendCommonQueryParams(params, flags);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function emitTextKey(data: CreateTokenPlanKeyResponse): void {
|
||||
const item = data.Data;
|
||||
if (!item) {
|
||||
emitBare("API key created.");
|
||||
return;
|
||||
}
|
||||
|
||||
emitBare(`${padEnd("ApiKeyId", 14)} ${item.ApiKeyId ?? "-"}`);
|
||||
emitBare(`${padEnd("MaskedApiKey", 14)} ${item.MaskedApiKey ?? "-"}`);
|
||||
if (item.Description) {
|
||||
emitBare(`${padEnd("Description", 14)} ${item.Description}`);
|
||||
}
|
||||
if (item.PlainApiKey) {
|
||||
emitBare("");
|
||||
emitBare(`PlainApiKey (shown once): ${item.PlainApiKey}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import {
|
||||
defineCommand,
|
||||
detectOutputFormat,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
} from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "../../output/output.ts";
|
||||
import { padEnd } from "../../output/cjk-width.ts";
|
||||
import type { GetSubscriptionSeatDetailsResponse, TokenPlanSeatDetail } from "./types.ts";
|
||||
import {
|
||||
TOKEN_PLAN_AK_OPTIONS,
|
||||
TOKEN_PLAN_COMMON_QUERY_OPTIONS,
|
||||
appendCommonQueryParams,
|
||||
callTokenPlanApi,
|
||||
prepareTokenPlanRequest,
|
||||
resolveTokenPlanCredentials,
|
||||
type TokenPlanQueryParams,
|
||||
} from "./utils.ts";
|
||||
|
||||
const API_ACTION = "GetSubscriptionSeatDetails";
|
||||
const API_PATH = "/tokenplan/subscription/seat-detail";
|
||||
|
||||
export default defineCommand({
|
||||
name: "token-plan list-seats",
|
||||
description: "List Token Plan subscription seat details",
|
||||
usage: "bl token-plan list-seats [flags]",
|
||||
options: [
|
||||
{ flag: "--page-no <n>", description: "Page number (default: 1)", type: "number" },
|
||||
{ flag: "--page-size <n>", description: "Page size (default: 10)", type: "number" },
|
||||
...TOKEN_PLAN_COMMON_QUERY_OPTIONS,
|
||||
{
|
||||
flag: "--status <status>",
|
||||
description:
|
||||
"Seat status filter (repeatable): CREATING, NORMAL, LIMIT, RELEASE, STOP, REFUNDED",
|
||||
type: "array",
|
||||
},
|
||||
{
|
||||
flag: "--status-list-str <json>",
|
||||
description: "StatusList as JSON string, e.g. '[\"NORMAL\"]'",
|
||||
},
|
||||
{ flag: "--seat-id <id>", description: "Filter by seat ID" },
|
||||
{
|
||||
flag: "--seat-type <type>",
|
||||
description: "Seat tier: standard, pro, or max",
|
||||
},
|
||||
{
|
||||
flag: "--query-assigned <bool>",
|
||||
description: "Filter by assignment: true=assigned, false=unassigned",
|
||||
},
|
||||
...TOKEN_PLAN_AK_OPTIONS,
|
||||
],
|
||||
examples: [
|
||||
"bl token-plan list-seats",
|
||||
"bl token-plan list-seats --page-size 20 --status NORMAL",
|
||||
"bl token-plan list-seats --query-assigned true --seat-type standard",
|
||||
],
|
||||
async run(config: Config, flags: GlobalFlags) {
|
||||
const format = detectOutputFormat(config.output);
|
||||
const credentials = resolveTokenPlanCredentials(config, flags);
|
||||
const queryParams = buildQueryParams(flags);
|
||||
|
||||
if (config.dryRun) {
|
||||
const { endpoint, queryParams: query } = prepareTokenPlanRequest(
|
||||
config,
|
||||
API_PATH,
|
||||
queryParams,
|
||||
);
|
||||
emitResult({ endpoint, query }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await callTokenPlanApi<GetSubscriptionSeatDetailsResponse>({
|
||||
config,
|
||||
credentials,
|
||||
action: API_ACTION,
|
||||
path: API_PATH,
|
||||
method: "GET",
|
||||
queryParams,
|
||||
});
|
||||
|
||||
const items = data.Data?.Items ?? [];
|
||||
if (config.quiet || format === "text") {
|
||||
emitTextSeats(items, data.Data?.Total, data.Data?.PageNo, data.Data?.PageSize);
|
||||
} else {
|
||||
emitResult(data, format);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function buildQueryParams(flags: GlobalFlags): TokenPlanQueryParams {
|
||||
const params: TokenPlanQueryParams = {};
|
||||
|
||||
if (flags.pageNo !== undefined) params.PageNo = String(flags.pageNo as number);
|
||||
if (flags.pageSize !== undefined) params.PageSize = String(flags.pageSize as number);
|
||||
appendCommonQueryParams(params, flags);
|
||||
if (flags.statusListStr) params.StatusListStr = flags.statusListStr as string;
|
||||
|
||||
const status = flags.status as string[] | undefined;
|
||||
if (status && status.length > 0) {
|
||||
params.StatusList = status;
|
||||
}
|
||||
|
||||
if (flags.seatId) params.SeatId = flags.seatId as string;
|
||||
if (flags.seatType) params.SeatType = flags.seatType as string;
|
||||
|
||||
if (typeof flags.queryAssigned === "string" && flags.queryAssigned.length > 0) {
|
||||
const val = flags.queryAssigned.toLowerCase();
|
||||
if (val !== "true" && val !== "false") {
|
||||
throw new BailianError("--query-assigned must be 'true' or 'false'.", ExitCode.USAGE);
|
||||
}
|
||||
params.QueryAssigned = val;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
function emitTextSeats(
|
||||
items: TokenPlanSeatDetail[],
|
||||
total?: number,
|
||||
pageNo?: number,
|
||||
pageSize?: number,
|
||||
): void {
|
||||
if (items.length === 0) {
|
||||
emitBare("No seats found.");
|
||||
return;
|
||||
}
|
||||
|
||||
const header = [
|
||||
padEnd("SeatId", 18),
|
||||
padEnd("Type", 10),
|
||||
padEnd("Status", 10),
|
||||
padEnd("Assigned", 12),
|
||||
padEnd("Account", 20),
|
||||
].join(" ");
|
||||
emitBare(header);
|
||||
emitBare("-".repeat(header.length));
|
||||
|
||||
for (const item of items) {
|
||||
const row = [
|
||||
padEnd(item.SeatId ?? "-", 18),
|
||||
padEnd(item.SpecType ?? "-", 10),
|
||||
padEnd(item.Status ?? "-", 10),
|
||||
padEnd(item.AssignedStatus ?? "-", 12),
|
||||
padEnd(item.AccountName ?? item.AccountId ?? "-", 20),
|
||||
].join(" ");
|
||||
emitBare(row);
|
||||
}
|
||||
|
||||
if (total !== undefined) {
|
||||
emitBare("");
|
||||
emitBare(
|
||||
`Total: ${total}${pageNo !== undefined ? ` | Page: ${pageNo}` : ""}${pageSize !== undefined ? ` | PageSize: ${pageSize}` : ""}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// ---- Token Plan / ModelStudio POP (2026-02-10) ----
|
||||
|
||||
export interface TokenPlanSeatEquity {
|
||||
EquityType?: string;
|
||||
CycleInstanceId?: string;
|
||||
CycleStartTime?: number;
|
||||
CycleEndTime?: number;
|
||||
CycleTotalValue?: number;
|
||||
CycleSurplusValue?: number;
|
||||
CycleVersion?: number;
|
||||
}
|
||||
|
||||
export interface TokenPlanSeatDetail {
|
||||
InstanceCode?: string;
|
||||
EquityList?: TokenPlanSeatEquity[];
|
||||
EndTime?: number;
|
||||
SeatId?: string;
|
||||
SpecType?: string;
|
||||
StartTime?: number;
|
||||
AssignedStatus?: string;
|
||||
AccountId?: string;
|
||||
AccountName?: string;
|
||||
AccountEmail?: string;
|
||||
Status?: string;
|
||||
}
|
||||
|
||||
export interface GetSubscriptionSeatDetailsResponse {
|
||||
Success?: boolean;
|
||||
Code?: string;
|
||||
Message?: string;
|
||||
Data?: {
|
||||
Items?: TokenPlanSeatDetail[];
|
||||
Total?: number;
|
||||
PageNo?: number;
|
||||
PageSize?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateTokenPlanKeyResponse {
|
||||
Success?: boolean;
|
||||
Code?: string;
|
||||
Message?: string;
|
||||
Data?: {
|
||||
ApiKeyId?: string;
|
||||
PlainApiKey?: string;
|
||||
MaskedApiKey?: string;
|
||||
Description?: string;
|
||||
CreatedAt?: string;
|
||||
SourceId?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BatchAssignSeatsResponse {
|
||||
Success?: boolean;
|
||||
Code?: string;
|
||||
Message?: string;
|
||||
}
|
||||
|
||||
export interface AddOrganizationMemberResponse {
|
||||
Success?: boolean;
|
||||
Code?: string;
|
||||
Message?: string;
|
||||
RequestId?: string;
|
||||
HttpStatusCode?: number;
|
||||
Data?: {
|
||||
AccountId?: string;
|
||||
SeatAssigned?: boolean;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
import {
|
||||
REGIONS,
|
||||
maskToken,
|
||||
trackingHeaders,
|
||||
type Config,
|
||||
type GlobalFlags,
|
||||
type OptionDef,
|
||||
type Region,
|
||||
BailianError,
|
||||
ExitCode,
|
||||
} from "bailian-cli-core";
|
||||
import { buildCanonicalQuery, signTokenPlanRequest } from "./ak-sign.ts";
|
||||
|
||||
export const TOKEN_PLAN_API_VERSION = "2026-02-10";
|
||||
|
||||
export const TOKEN_PLAN_AK_OPTIONS: OptionDef[] = [
|
||||
{ flag: "--access-key-id <key>", description: "Alibaba Cloud Access Key ID (deprecated)" },
|
||||
{
|
||||
flag: "--access-key-secret <key>",
|
||||
description: "Alibaba Cloud Access Key Secret (deprecated)",
|
||||
},
|
||||
];
|
||||
|
||||
export const TOKEN_PLAN_COMMON_QUERY_OPTIONS: OptionDef[] = [
|
||||
{
|
||||
flag: "--caller-uac-account-id <id>",
|
||||
description: "Caller UAC account ID",
|
||||
},
|
||||
{
|
||||
flag: "--namespace-id <id>",
|
||||
description: "Product namespace ID (Token Plan default: namespace-1)",
|
||||
},
|
||||
];
|
||||
|
||||
export const TOKEN_PLAN_WORKSPACE_OPTION: OptionDef = {
|
||||
flag: "--workspace-id <id>",
|
||||
description: "Workspace ID (env: BAILIAN_WORKSPACE_ID, config: workspace_id)",
|
||||
};
|
||||
|
||||
const MODEL_STUDIO_HOSTS: Partial<Record<Region, string>> = {
|
||||
cn: "modelstudio.cn-beijing.aliyuncs.com",
|
||||
intl: "modelstudio.ap-southeast-1.aliyuncs.com",
|
||||
};
|
||||
|
||||
function resolveRegion(baseUrl: string): Region {
|
||||
for (const [region, url] of Object.entries(REGIONS) as Array<[Region, string]>) {
|
||||
if (baseUrl === url || baseUrl.startsWith(`${url}/`)) return region;
|
||||
}
|
||||
return "cn";
|
||||
}
|
||||
|
||||
/** ModelStudio POP OpenAPI host for the given DashScope base URL preset. */
|
||||
function modelStudioHost(baseUrl: string): string {
|
||||
const region = resolveRegion(baseUrl);
|
||||
return MODEL_STUDIO_HOSTS[region] ?? MODEL_STUDIO_HOSTS.cn!;
|
||||
}
|
||||
|
||||
export interface TokenPlanApiResponse {
|
||||
Success?: boolean;
|
||||
Code?: string;
|
||||
Message?: string;
|
||||
}
|
||||
|
||||
export type TokenPlanQueryParams = Record<string, string | string[] | undefined>;
|
||||
|
||||
export function resolveTokenPlanCredentials(
|
||||
config: Config,
|
||||
flags: GlobalFlags,
|
||||
): { accessKeyId: string; accessKeySecret: string } {
|
||||
const accessKeyId = (flags.accessKeyId as string) || config.accessKeyId;
|
||||
const accessKeySecret = (flags.accessKeySecret as string) || config.accessKeySecret;
|
||||
|
||||
if (!accessKeyId || !accessKeySecret) {
|
||||
throw new BailianError(
|
||||
"No credentials found.\n" +
|
||||
"Set ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET.",
|
||||
ExitCode.AUTH,
|
||||
);
|
||||
}
|
||||
|
||||
return { accessKeyId, accessKeySecret };
|
||||
}
|
||||
|
||||
export function requireWorkspaceId(config: Config, flags: GlobalFlags): string {
|
||||
const workspaceId = (flags.workspaceId as string) || config.workspaceId;
|
||||
if (!workspaceId) {
|
||||
throw new BailianError(
|
||||
"Missing workspace ID.\n" +
|
||||
"Set via: --workspace-id flag, env: BAILIAN_WORKSPACE_ID, or config: bl config set workspace_id <id>",
|
||||
ExitCode.USAGE,
|
||||
);
|
||||
}
|
||||
return workspaceId;
|
||||
}
|
||||
|
||||
export function appendCommonQueryParams(params: TokenPlanQueryParams, flags: GlobalFlags): void {
|
||||
if (flags.callerUacAccountId) params.CallerUacAccountId = flags.callerUacAccountId as string;
|
||||
if (flags.namespaceId) params.NamespaceId = flags.namespaceId as string;
|
||||
}
|
||||
|
||||
export function prepareTokenPlanRequest(
|
||||
config: Config,
|
||||
path: string,
|
||||
queryParams: TokenPlanQueryParams,
|
||||
): { host: string; endpoint: string; queryString: string; queryParams: TokenPlanQueryParams } {
|
||||
const queryString = buildCanonicalQuery(queryParams);
|
||||
const host = modelStudioHost(config.baseUrl);
|
||||
const endpoint = `https://${host}${path}${queryString ? `?${queryString}` : ""}`;
|
||||
return { host, endpoint, queryString, queryParams };
|
||||
}
|
||||
|
||||
export async function callTokenPlanApi<T extends TokenPlanApiResponse>(opts: {
|
||||
config: Config;
|
||||
credentials: { accessKeyId: string; accessKeySecret: string };
|
||||
action: string;
|
||||
path: string;
|
||||
method: "GET" | "POST";
|
||||
queryParams: TokenPlanQueryParams;
|
||||
}): Promise<T> {
|
||||
const { config, credentials, action, path, method, queryParams } = opts;
|
||||
const { host, endpoint, queryString } = prepareTokenPlanRequest(config, path, queryParams);
|
||||
|
||||
const headers = signTokenPlanRequest({
|
||||
accessKeyId: credentials.accessKeyId,
|
||||
accessKeySecret: credentials.accessKeySecret,
|
||||
action,
|
||||
version: TOKEN_PLAN_API_VERSION,
|
||||
body: "",
|
||||
host,
|
||||
pathname: path,
|
||||
method,
|
||||
queryString,
|
||||
});
|
||||
|
||||
if (config.verbose) {
|
||||
process.stderr.write(`> ${method} ${endpoint}\n`);
|
||||
process.stderr.write(`> AK: ${maskToken(credentials.accessKeyId)}\n`);
|
||||
}
|
||||
|
||||
const timeoutMs = config.timeout * 1000;
|
||||
const res = await fetch(endpoint, {
|
||||
method,
|
||||
headers: { ...headers, ...trackingHeaders() },
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
|
||||
if (config.verbose) {
|
||||
process.stderr.write(`< ${res.status} ${res.statusText}\n`);
|
||||
}
|
||||
|
||||
const data = (await res.json()) as T;
|
||||
|
||||
if (!res.ok || data.Success === false) {
|
||||
throw new BailianError(
|
||||
`${data.Code || res.status} - ${data.Message || res.statusText}`,
|
||||
ExitCode.GENERAL,
|
||||
);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -8,80 +8,85 @@ Use this index for the full quick index and global flags.
|
||||
|
||||
## Quick index
|
||||
|
||||
| Command | Description | Detail |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| `bl advisor recommend` | Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking) | [advisor.md](advisor.md) |
|
||||
| `bl app call` | Call a Bailian application (agent or workflow) | [app.md](app.md) |
|
||||
| `bl app list` | List Bailian applications | [app.md](app.md) |
|
||||
| `bl auth login` | Authenticate with API key or console browser login (credentials can coexist) | [auth.md](auth.md) |
|
||||
| `bl auth logout` | Clear stored credentials | [auth.md](auth.md) |
|
||||
| `bl auth status` | Show current authentication state | [auth.md](auth.md) |
|
||||
| `bl config export-schema` | Export all (or one) CLI command(s) as Anthropic/OpenAI-compatible JSON tool schemas | [config.md](config.md) |
|
||||
| `bl config set` | Set a config value | [config.md](config.md) |
|
||||
| `bl config show` | Display current configuration | [config.md](config.md) |
|
||||
| `bl console call` | Call a Bailian console API via the CLI gateway | [console.md](console.md) |
|
||||
| `bl file upload` | Upload a local file to DashScope temporary storage (48h) | [file.md](file.md) |
|
||||
| `bl image edit` | Edit an existing image with text instructions (Qwen-Image) | [image.md](image.md) |
|
||||
| `bl image generate` | Generate images (Qwen-Image / wan2.x) | [image.md](image.md) |
|
||||
| `bl knowledge retrieve` | Retrieve from a Bailian knowledge base | [knowledge.md](knowledge.md) |
|
||||
| `bl mcp call` | Call a tool on an MCP server (tools/call) | [mcp.md](mcp.md) |
|
||||
| `bl mcp list` | List MCP servers activated under your Bailian account | [mcp.md](mcp.md) |
|
||||
| `bl mcp tools` | List tools exposed by an MCP server (tools/list) | [mcp.md](mcp.md) |
|
||||
| `bl memory add` | Add memory from messages or custom content | [memory.md](memory.md) |
|
||||
| `bl memory delete` | Delete a memory node | [memory.md](memory.md) |
|
||||
| `bl memory list` | List memory nodes for a user | [memory.md](memory.md) |
|
||||
| `bl memory profile create` | Create a user profile schema for memory profiling | [memory.md](memory.md) |
|
||||
| `bl memory profile get` | Get user profile by schema ID and user ID | [memory.md](memory.md) |
|
||||
| `bl memory search` | Search memory nodes by query or messages | [memory.md](memory.md) |
|
||||
| `bl memory update` | Update a memory node content | [memory.md](memory.md) |
|
||||
| `bl omni` | Multimodal chat with text + audio output (Qwen-Omni) | [omni.md](omni.md) |
|
||||
| `bl pipeline run` | Run a pipeline workflow definition | [pipeline.md](pipeline.md) |
|
||||
| `bl pipeline validate` | Validate a pipeline definition without executing | [pipeline.md](pipeline.md) |
|
||||
| `bl quota check` | Check current usage against rate limits | [quota.md](quota.md) |
|
||||
| `bl quota history` | View quota change history | [quota.md](quota.md) |
|
||||
| `bl quota list` | View model RPM/TPM rate limits | [quota.md](quota.md) |
|
||||
| `bl quota request` | Request a temporary quota increase | [quota.md](quota.md) |
|
||||
| `bl search web` | Search the web using DashScope MCP WebSearch service | [search.md](search.md) |
|
||||
| `bl speech recognize` | Recognize speech from audio files (FunAudio-ASR) | [speech.md](speech.md) |
|
||||
| `bl speech synthesize` | Synthesize speech from text (CosyVoice TTS) | [speech.md](speech.md) |
|
||||
| `bl text chat` | Send a chat completion (OpenAI compatible, DashScope) | [text.md](text.md) |
|
||||
| `bl update` | Update bl to the latest version | [update.md](update.md) |
|
||||
| `bl usage free` | Query free-tier quota for models (all models if --model is omitted) | [usage.md](usage.md) |
|
||||
| `bl usage freetier` | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | [usage.md](usage.md) |
|
||||
| `bl usage stats` | Query model usage statistics | [usage.md](usage.md) |
|
||||
| `bl video download` | Download a completed video by task ID | [video.md](video.md) |
|
||||
| `bl video edit` | Edit a video with happyhorse-1.0-video-edit (style transfer, object replacement, etc.) | [video.md](video.md) |
|
||||
| `bl video generate` | Generate a video from text or image (happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v) | [video.md](video.md) |
|
||||
| `bl video ref` | Reference-to-video generation (happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice | [video.md](video.md) |
|
||||
| `bl video task get` | Query async task status | [video.md](video.md) |
|
||||
| `bl vision describe` | Describe an image or video using Qwen-VL | [vision.md](vision.md) |
|
||||
| `bl workspace list` | List all workspaces | [workspace.md](workspace.md) |
|
||||
| Command | Description | Detail |
|
||||
| ---------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------ |
|
||||
| `bl advisor recommend` | Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking) | [advisor.md](advisor.md) |
|
||||
| `bl app call` | Call a Bailian application (agent or workflow) | [app.md](app.md) |
|
||||
| `bl app list` | List Bailian applications | [app.md](app.md) |
|
||||
| `bl auth login` | Authenticate with API key or console browser login (credentials can coexist) | [auth.md](auth.md) |
|
||||
| `bl auth logout` | Clear stored credentials | [auth.md](auth.md) |
|
||||
| `bl auth status` | Show current authentication state | [auth.md](auth.md) |
|
||||
| `bl config export-schema` | Export all (or one) CLI command(s) as Anthropic/OpenAI-compatible JSON tool schemas | [config.md](config.md) |
|
||||
| `bl config set` | Set a config value | [config.md](config.md) |
|
||||
| `bl config show` | Display current configuration | [config.md](config.md) |
|
||||
| `bl console call` | Call a Bailian console API via the CLI gateway | [console.md](console.md) |
|
||||
| `bl file upload` | Upload a local file to DashScope temporary storage (48h) | [file.md](file.md) |
|
||||
| `bl image edit` | Edit an existing image with text instructions (Qwen-Image) | [image.md](image.md) |
|
||||
| `bl image generate` | Generate images (Qwen-Image / wan2.x) | [image.md](image.md) |
|
||||
| `bl knowledge retrieve` | Retrieve from a Bailian knowledge base | [knowledge.md](knowledge.md) |
|
||||
| `bl mcp call` | Call a tool on an MCP server (tools/call) | [mcp.md](mcp.md) |
|
||||
| `bl mcp list` | List MCP servers activated under your Bailian account | [mcp.md](mcp.md) |
|
||||
| `bl mcp tools` | List tools exposed by an MCP server (tools/list) | [mcp.md](mcp.md) |
|
||||
| `bl memory add` | Add memory from messages or custom content | [memory.md](memory.md) |
|
||||
| `bl memory delete` | Delete a memory node | [memory.md](memory.md) |
|
||||
| `bl memory list` | List memory nodes for a user | [memory.md](memory.md) |
|
||||
| `bl memory profile create` | Create a user profile schema for memory profiling | [memory.md](memory.md) |
|
||||
| `bl memory profile get` | Get user profile by schema ID and user ID | [memory.md](memory.md) |
|
||||
| `bl memory search` | Search memory nodes by query or messages | [memory.md](memory.md) |
|
||||
| `bl memory update` | Update a memory node content | [memory.md](memory.md) |
|
||||
| `bl omni` | Multimodal chat with text + audio output (Qwen-Omni) | [omni.md](omni.md) |
|
||||
| `bl pipeline run` | Run a pipeline workflow definition | [pipeline.md](pipeline.md) |
|
||||
| `bl pipeline validate` | Validate a pipeline definition without executing | [pipeline.md](pipeline.md) |
|
||||
| `bl quota check` | Check current usage against rate limits | [quota.md](quota.md) |
|
||||
| `bl quota history` | View quota change history | [quota.md](quota.md) |
|
||||
| `bl quota list` | View model RPM/TPM rate limits | [quota.md](quota.md) |
|
||||
| `bl quota request` | Request a temporary quota increase | [quota.md](quota.md) |
|
||||
| `bl search web` | Search the web using DashScope MCP WebSearch service | [search.md](search.md) |
|
||||
| `bl speech recognize` | Recognize speech from audio files (FunAudio-ASR) | [speech.md](speech.md) |
|
||||
| `bl speech synthesize` | Synthesize speech from text (CosyVoice TTS) | [speech.md](speech.md) |
|
||||
| `bl text chat` | Send a chat completion (OpenAI compatible, DashScope) | [text.md](text.md) |
|
||||
| `bl token-plan add-member` | Add a member to a Token Plan organization | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan assign-seats` | Batch assign Token Plan seats to members | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan create-key` | Create a Token Plan API key for a seat | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan list-seats` | List Token Plan subscription seat details | [token-plan.md](token-plan.md) |
|
||||
| `bl update` | Update bl to the latest version | [update.md](update.md) |
|
||||
| `bl usage free` | Query free-tier quota for models (all models if --model is omitted) | [usage.md](usage.md) |
|
||||
| `bl usage freetier` | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | [usage.md](usage.md) |
|
||||
| `bl usage stats` | Query model usage statistics | [usage.md](usage.md) |
|
||||
| `bl video download` | Download a completed video by task ID | [video.md](video.md) |
|
||||
| `bl video edit` | Edit a video with happyhorse-1.0-video-edit (style transfer, object replacement, etc.) | [video.md](video.md) |
|
||||
| `bl video generate` | Generate a video from text or image (happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v) | [video.md](video.md) |
|
||||
| `bl video ref` | Reference-to-video generation (happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice | [video.md](video.md) |
|
||||
| `bl video task get` | Query async task status | [video.md](video.md) |
|
||||
| `bl vision describe` | Describe an image or video using Qwen-VL | [vision.md](vision.md) |
|
||||
| `bl workspace list` | List all workspaces | [workspace.md](workspace.md) |
|
||||
|
||||
## By group
|
||||
|
||||
| Group | Commands | Reference |
|
||||
| ----------- | ---------------------------------------------------------------------------- | ---------------------------- |
|
||||
| `advisor` | `recommend` | [advisor.md](advisor.md) |
|
||||
| `app` | `call`, `list` | [app.md](app.md) |
|
||||
| `auth` | `login`, `logout`, `status` | [auth.md](auth.md) |
|
||||
| `config` | `export-schema`, `set`, `show` | [config.md](config.md) |
|
||||
| `console` | `call` | [console.md](console.md) |
|
||||
| `file` | `upload` | [file.md](file.md) |
|
||||
| `image` | `edit`, `generate` | [image.md](image.md) |
|
||||
| `knowledge` | `retrieve` | [knowledge.md](knowledge.md) |
|
||||
| `mcp` | `call`, `list`, `tools` | [mcp.md](mcp.md) |
|
||||
| `memory` | `add`, `delete`, `list`, `profile create`, `profile get`, `search`, `update` | [memory.md](memory.md) |
|
||||
| `omni` | `(root)` | [omni.md](omni.md) |
|
||||
| `pipeline` | `run`, `validate` | [pipeline.md](pipeline.md) |
|
||||
| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) |
|
||||
| `search` | `web` | [search.md](search.md) |
|
||||
| `speech` | `recognize`, `synthesize` | [speech.md](speech.md) |
|
||||
| `text` | `chat` | [text.md](text.md) |
|
||||
| `update` | `(root)` | [update.md](update.md) |
|
||||
| `usage` | `free`, `freetier`, `stats` | [usage.md](usage.md) |
|
||||
| `video` | `download`, `edit`, `generate`, `ref`, `task get` | [video.md](video.md) |
|
||||
| `vision` | `describe` | [vision.md](vision.md) |
|
||||
| `workspace` | `list` | [workspace.md](workspace.md) |
|
||||
| Group | Commands | Reference |
|
||||
| ------------ | ---------------------------------------------------------------------------- | ------------------------------ |
|
||||
| `advisor` | `recommend` | [advisor.md](advisor.md) |
|
||||
| `app` | `call`, `list` | [app.md](app.md) |
|
||||
| `auth` | `login`, `logout`, `status` | [auth.md](auth.md) |
|
||||
| `config` | `export-schema`, `set`, `show` | [config.md](config.md) |
|
||||
| `console` | `call` | [console.md](console.md) |
|
||||
| `file` | `upload` | [file.md](file.md) |
|
||||
| `image` | `edit`, `generate` | [image.md](image.md) |
|
||||
| `knowledge` | `retrieve` | [knowledge.md](knowledge.md) |
|
||||
| `mcp` | `call`, `list`, `tools` | [mcp.md](mcp.md) |
|
||||
| `memory` | `add`, `delete`, `list`, `profile create`, `profile get`, `search`, `update` | [memory.md](memory.md) |
|
||||
| `omni` | `(root)` | [omni.md](omni.md) |
|
||||
| `pipeline` | `run`, `validate` | [pipeline.md](pipeline.md) |
|
||||
| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) |
|
||||
| `search` | `web` | [search.md](search.md) |
|
||||
| `speech` | `recognize`, `synthesize` | [speech.md](speech.md) |
|
||||
| `text` | `chat` | [text.md](text.md) |
|
||||
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) |
|
||||
| `update` | `(root)` | [update.md](update.md) |
|
||||
| `usage` | `free`, `freetier`, `stats` | [usage.md](usage.md) |
|
||||
| `video` | `download`, `edit`, `generate`, `ref`, `task get` | [video.md](video.md) |
|
||||
| `vision` | `describe` | [vision.md](vision.md) |
|
||||
| `workspace` | `list` | [workspace.md](workspace.md) |
|
||||
|
||||
## Global flags
|
||||
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
# `bl token-plan` commands
|
||||
|
||||
> Auto-generated from `packages/cli/src/commands/catalog.ts`. Do not edit by hand.
|
||||
> Regenerate: `pnpm --filter bailian-cli run generate:reference`.
|
||||
|
||||
Index: [index.md](index.md)
|
||||
|
||||
## Commands in this group
|
||||
|
||||
| Command | Description |
|
||||
| ---------------------------- | ----------------------------------------- |
|
||||
| `bl token-plan add-member` | Add a member to a Token Plan organization |
|
||||
| `bl token-plan assign-seats` | Batch assign Token Plan seats to members |
|
||||
| `bl token-plan create-key` | Create a Token Plan API key for a seat |
|
||||
| `bl token-plan list-seats` | List Token Plan subscription seat details |
|
||||
|
||||
## Command details
|
||||
|
||||
### `bl token-plan add-member`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ---------------------------------------------------------------------- |
|
||||
| **Name** | `token-plan add-member` |
|
||||
| **Description** | Add a member to a Token Plan organization |
|
||||
| **Usage** | `bl token-plan add-member --account-name <name> --org-id <id> [flags]` |
|
||||
|
||||
#### Options
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | ---------------------------------------------------------------- |
|
||||
| `--account-name <name>` | string | yes | Member display name |
|
||||
| `--org-id <id>` | string | yes | Organization ID |
|
||||
| `--org-role-code <code>` | string | no | Organization role: ORG_ADMIN or ORG_MEMBER (default: ORG_MEMBER) |
|
||||
| `--spec-type <type>` | string | no | Seat tier to assign on creation: standard, pro, or max |
|
||||
| `--caller-uac-account-id <id>` | string | no | Caller UAC account ID |
|
||||
| `--namespace-id <id>` | string | no | Product namespace ID (Token Plan default: namespace-1) |
|
||||
| `--access-key-id <key>` | string | no | Alibaba Cloud Access Key ID (deprecated) |
|
||||
| `--access-key-secret <key>` | string | no | Alibaba Cloud Access Key Secret (deprecated) |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl token-plan add-member --account-name dev_user --org-id org_123
|
||||
```
|
||||
|
||||
```bash
|
||||
bl token-plan add-member --account-name admin_user --org-id org_123 --org-role-code ORG_ADMIN
|
||||
```
|
||||
|
||||
```bash
|
||||
bl token-plan add-member --account-name member1 --org-id org_123 --spec-type standard
|
||||
```
|
||||
|
||||
### `bl token-plan assign-seats`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | --------------------------------------------------------------------------------------------- |
|
||||
| **Name** | `token-plan assign-seats` |
|
||||
| **Description** | Batch assign Token Plan seats to members |
|
||||
| **Usage** | `bl token-plan assign-seats --workspace-id <id> --seat-type <type> --account-id <id> [flags]` |
|
||||
|
||||
#### Options
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | -------------------------------------------------------------- |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID, config: workspace_id) |
|
||||
| `--seat-type <type>` | string | yes | Seat tier: standard, pro, or max |
|
||||
| `--account-id <id>` | array | no | Target member account ID (repeatable) |
|
||||
| `--caller-uac-account-id <id>` | string | no | Caller UAC account ID |
|
||||
| `--namespace-id <id>` | string | no | Product namespace ID (Token Plan default: namespace-1) |
|
||||
| `--locale <locale>` | string | no | Language: zh-CN or en-US |
|
||||
| `--access-key-id <key>` | string | no | Alibaba Cloud Access Key ID (deprecated) |
|
||||
| `--access-key-secret <key>` | string | no | Alibaba Cloud Access Key Secret (deprecated) |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl token-plan assign-seats --workspace-id ws_456 --seat-type standard --account-id acc_123
|
||||
```
|
||||
|
||||
```bash
|
||||
bl token-plan assign-seats --workspace-id ws_456 --seat-type pro --account-id acc_1 --account-id acc_2
|
||||
```
|
||||
|
||||
### `bl token-plan create-key`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------------------------ |
|
||||
| **Name** | `token-plan create-key` |
|
||||
| **Description** | Create a Token Plan API key for a seat |
|
||||
| **Usage** | `bl token-plan create-key --account-id <id> --workspace-id <id> [flags]` |
|
||||
|
||||
#### Options
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | -------------------------------------------------------------- |
|
||||
| `--account-id <id>` | string | yes | Target member account ID |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID, config: workspace_id) |
|
||||
| `--description <text>` | string | no | API key description |
|
||||
| `--caller-uac-account-id <id>` | string | no | Caller UAC account ID |
|
||||
| `--namespace-id <id>` | string | no | Product namespace ID (Token Plan default: namespace-1) |
|
||||
| `--access-key-id <key>` | string | no | Alibaba Cloud Access Key ID (deprecated) |
|
||||
| `--access-key-secret <key>` | string | no | Alibaba Cloud Access Key Secret (deprecated) |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl token-plan create-key --account-id acc_123 --workspace-id ws_456
|
||||
```
|
||||
|
||||
```bash
|
||||
bl token-plan create-key --account-id acc_123 --workspace-id ws_456 --description 'Dev key'
|
||||
```
|
||||
|
||||
### `bl token-plan list-seats`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ----------------------------------------- |
|
||||
| **Name** | `token-plan list-seats` |
|
||||
| **Description** | List Token Plan subscription seat details |
|
||||
| **Usage** | `bl token-plan list-seats [flags]` |
|
||||
|
||||
#### Options
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | --------------------------------------------------------------------------------- |
|
||||
| `--page-no <n>` | number | no | Page number (default: 1) |
|
||||
| `--page-size <n>` | number | no | Page size (default: 10) |
|
||||
| `--caller-uac-account-id <id>` | string | no | Caller UAC account ID |
|
||||
| `--namespace-id <id>` | string | no | Product namespace ID (Token Plan default: namespace-1) |
|
||||
| `--status <status>` | array | no | Seat status filter (repeatable): CREATING, NORMAL, LIMIT, RELEASE, STOP, REFUNDED |
|
||||
| `--status-list-str <json>` | string | no | StatusList as JSON string, e.g. '["NORMAL"]' |
|
||||
| `--seat-id <id>` | string | no | Filter by seat ID |
|
||||
| `--seat-type <type>` | string | no | Seat tier: standard, pro, or max |
|
||||
| `--query-assigned <bool>` | string | no | Filter by assignment: true=assigned, false=unassigned |
|
||||
| `--access-key-id <key>` | string | no | Alibaba Cloud Access Key ID (deprecated) |
|
||||
| `--access-key-secret <key>` | string | no | Alibaba Cloud Access Key Secret (deprecated) |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl token-plan list-seats
|
||||
```
|
||||
|
||||
```bash
|
||||
bl token-plan list-seats --page-size 20 --status NORMAL
|
||||
```
|
||||
|
||||
```bash
|
||||
bl token-plan list-seats --query-assigned true --seat-type standard
|
||||
```
|
||||
+13
-3
@@ -4,6 +4,7 @@ import { fileURLToPath } from "url";
|
||||
import { packAndScan } from "./lib/pack-scan.mjs";
|
||||
import { run } from "./lib/proc.mjs";
|
||||
import { assertReadmeSync, loadAndValidatePackages } from "./lib/validate.mjs";
|
||||
import { ALL_PACKAGES, PACKAGES } from "./lib/packages.mjs";
|
||||
|
||||
function log(msg = "") {
|
||||
process.stdout.write(`${msg}\n`);
|
||||
@@ -17,20 +18,24 @@ function step(msg) {
|
||||
* Pure-validation pipeline. Reusable from publish-stable / publish-channel.
|
||||
* Returns { coreJson, cliJson } for callers that need the parsed package.jsons.
|
||||
*
|
||||
* @param {{ channel?: boolean }} [options]
|
||||
* @param {{ channel?: boolean, knowledge?: boolean }} [options]
|
||||
* @param {boolean} [options.channel] — When true (publish-channel): regenerate
|
||||
* `reference/` and assert it matches git, but do not sync `SKILL.md` from the
|
||||
* temporary beta `package.json` version (repo skill stays aligned with stable).
|
||||
* @param {boolean} [options.knowledge] — When true: also build and validate
|
||||
* knowledge-studio-cli alongside the base packages.
|
||||
*/
|
||||
export async function runCheck(options = {}) {
|
||||
const channel = options.channel === true;
|
||||
const knowledge = options.knowledge === true;
|
||||
const packages = knowledge ? ALL_PACKAGES : PACKAGES;
|
||||
|
||||
step("pnpm install --frozen-lockfile");
|
||||
run("pnpm", ["install", "--frozen-lockfile"]);
|
||||
|
||||
step("metadata: README sync, version consistency, workspace:* dep");
|
||||
assertReadmeSync();
|
||||
const { coreJson, cliJson } = loadAndValidatePackages();
|
||||
const { coreJson, cliJson } = loadAndValidatePackages({ packages });
|
||||
log(`bailian-cli-core@${coreJson.version}`);
|
||||
log(`bailian-cli@${cliJson.version}`);
|
||||
|
||||
@@ -63,8 +68,13 @@ export async function runCheck(options = {}) {
|
||||
step("build bailian-cli");
|
||||
run("pnpm", ["--filter", "bailian-cli", "run", "build"]);
|
||||
|
||||
if (knowledge) {
|
||||
step("build knowledge-studio-cli");
|
||||
run("pnpm", ["--filter", "knowledge-studio-cli", "run", "build"]);
|
||||
}
|
||||
|
||||
step("pack + scan (publint, gitleaks)");
|
||||
packAndScan({ log });
|
||||
packAndScan({ log, packages });
|
||||
|
||||
log("\nrelease check passed.");
|
||||
return { coreJson, cliJson };
|
||||
|
||||
@@ -13,10 +13,11 @@ function extractTarball(tarball, tempDir, key) {
|
||||
return extractDir;
|
||||
}
|
||||
|
||||
export function packAndScan({ log }) {
|
||||
export function packAndScan({ log, packages }) {
|
||||
const pkgs = packages ?? PACKAGES;
|
||||
const tempDir = mkdtempSync(join(tmpdir(), "bailian-release-"));
|
||||
try {
|
||||
for (const pkg of PACKAGES) {
|
||||
for (const pkg of pkgs) {
|
||||
const json = readPackageJson(pkg);
|
||||
log(`packing ${pkg.name}@${json.version}`);
|
||||
const tarball = pnpmPack(pkg, tempDir, json);
|
||||
|
||||
@@ -9,6 +9,11 @@ export const PACKAGES = [
|
||||
{ key: "cli", dir: "packages/cli", name: "bailian-cli" },
|
||||
];
|
||||
|
||||
// knowledge-studio-cli shares the same library deps as bailian-cli.
|
||||
// Published via publish.yml with package=knowledge-studio-cli (passes --knowledge flag).
|
||||
export const KSCLI_PACKAGE = { key: "kscli", dir: "packages/kscli", name: "knowledge-studio-cli" };
|
||||
export const ALL_PACKAGES = [...PACKAGES, KSCLI_PACKAGE];
|
||||
|
||||
export function readJson(path) {
|
||||
return JSON.parse(readFileSync(path, "utf-8"));
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ export function assertReadmeSync() {
|
||||
}
|
||||
}
|
||||
|
||||
export function loadAndValidatePackages() {
|
||||
export function loadAndValidatePackages({ packages } = {}) {
|
||||
const pkgs = packages ?? PACKAGES;
|
||||
const internalNames = new Set(pkgs.map((p) => p.name));
|
||||
const jsonByKey = new Map();
|
||||
for (const pkg of PACKAGES) {
|
||||
for (const pkg of pkgs) {
|
||||
const json = readPackageJson(pkg);
|
||||
if (json.name !== pkg.name) {
|
||||
throw new Error(`${pkg.dir} name must be ${pkg.name}, got ${json.name}`);
|
||||
@@ -30,18 +32,21 @@ export function loadAndValidatePackages() {
|
||||
|
||||
const coreJson = jsonByKey.get("core");
|
||||
const cliJson = jsonByKey.get("cli");
|
||||
const version = coreJson.version;
|
||||
|
||||
if (cliJson.version !== coreJson.version) {
|
||||
throw new Error(
|
||||
`core and cli versions must match, got ${coreJson.version} and ${cliJson.version}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const cliCoreDep = cliJson.dependencies?.["bailian-cli-core"];
|
||||
if (cliCoreDep !== "workspace:*") {
|
||||
throw new Error(
|
||||
`packages/cli source dependency on bailian-cli-core must be "workspace:*", got ${cliCoreDep}.`,
|
||||
);
|
||||
for (const pkg of pkgs) {
|
||||
const json = jsonByKey.get(pkg.key);
|
||||
if (json.version !== version) {
|
||||
throw new Error(
|
||||
`all package versions must match ${version} (bailian-cli-core), ` +
|
||||
`but ${pkg.name} is ${json.version}.`,
|
||||
);
|
||||
}
|
||||
for (const [dep, range] of Object.entries(json.dependencies ?? {})) {
|
||||
if (internalNames.has(dep) && range !== "workspace:*") {
|
||||
throw new Error(`${pkg.name} dependency on ${dep} must be "workspace:*", got ${range}.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { coreJson, cliJson };
|
||||
|
||||
@@ -6,7 +6,8 @@ import { runCheck } from "./check.mjs";
|
||||
import { headSha7, utcDateStamp } from "./lib/git.mjs";
|
||||
import { npmViewExists, pnpmPublish } from "./lib/npm.mjs";
|
||||
import {
|
||||
findPackage,
|
||||
ALL_PACKAGES,
|
||||
PACKAGES,
|
||||
packageJsonPath,
|
||||
readPackageJson,
|
||||
writePackageJson,
|
||||
@@ -25,11 +26,14 @@ const { values } = parseArgs({
|
||||
options: {
|
||||
channel: { type: "string" },
|
||||
"dry-run": { type: "boolean", default: false },
|
||||
knowledge: { type: "boolean", default: false },
|
||||
},
|
||||
allowPositionals: false,
|
||||
});
|
||||
const channel = values.channel;
|
||||
const dryRun = values["dry-run"];
|
||||
const knowledge = values.knowledge;
|
||||
const packages = knowledge ? ALL_PACKAGES : PACKAGES;
|
||||
assertChannel(channel);
|
||||
|
||||
if (!dryRun && !process.env.CI) {
|
||||
@@ -37,16 +41,15 @@ if (!dryRun && !process.env.CI) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const core = findPackage("core");
|
||||
const cli = findPackage("cli");
|
||||
const corePath = packageJsonPath(core);
|
||||
const cliPath = packageJsonPath(cli);
|
||||
const coreOriginal = readFileSync(corePath, "utf-8");
|
||||
const cliOriginal = readFileSync(cliPath, "utf-8");
|
||||
// Snapshot every package.json so the temporary version bump is reverted in
|
||||
// `finally`, even when the release fails midway.
|
||||
const originals = packages.map((pkg) => {
|
||||
const path = packageJsonPath(pkg);
|
||||
return { pkg, path, content: readFileSync(path, "utf-8") };
|
||||
});
|
||||
|
||||
function restoreOriginals() {
|
||||
writeFileSync(corePath, coreOriginal);
|
||||
writeFileSync(cliPath, cliOriginal);
|
||||
for (const { path, content } of originals) writeFileSync(path, content);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -57,32 +60,29 @@ try {
|
||||
log(`channel=${channel} version=${betaVersion}`);
|
||||
|
||||
step("temporarily bump package.json (not committed)");
|
||||
const coreJson = readPackageJson(core);
|
||||
const cliJson = readPackageJson(cli);
|
||||
coreJson.version = betaVersion;
|
||||
cliJson.version = betaVersion;
|
||||
writePackageJson(core, coreJson);
|
||||
writePackageJson(cli, cliJson);
|
||||
// pnpm pack resolves `workspace:*` to the in-tree version, so CLI tarball
|
||||
// will depend on bailian-cli-core@<betaVersion> after this bump.
|
||||
for (const pkg of packages) {
|
||||
const json = readPackageJson(pkg);
|
||||
json.version = betaVersion;
|
||||
writePackageJson(pkg, json);
|
||||
}
|
||||
|
||||
await runCheck({ channel: true });
|
||||
await runCheck({ channel: true, knowledge });
|
||||
|
||||
step(`idempotency: check ${betaVersion} against registry`);
|
||||
const corePublished = npmViewExists(core.name, betaVersion);
|
||||
const cliPublished = npmViewExists(cli.name, betaVersion);
|
||||
log(`${core.name}@${betaVersion}: ${corePublished ? "already published" : "to publish"}`);
|
||||
log(`${cli.name}@${betaVersion}: ${cliPublished ? "already published" : "to publish"}`);
|
||||
if (corePublished && cliPublished) {
|
||||
log("\nboth packages already published; nothing to do.");
|
||||
const published = new Map();
|
||||
for (const pkg of packages) {
|
||||
const exists = npmViewExists(pkg.name, betaVersion);
|
||||
published.set(pkg.key, exists);
|
||||
log(`${pkg.name}@${betaVersion}: ${exists ? "already published" : "to publish"}`);
|
||||
}
|
||||
if (packages.every((pkg) => published.get(pkg.key))) {
|
||||
log("\nall packages already published; nothing to do.");
|
||||
} else {
|
||||
if (!corePublished) {
|
||||
step(`publish ${core.name}@${betaVersion} (tag=${channel}, provenance)`);
|
||||
pnpmPublish(core, { tag: channel, provenance: true, dryRun });
|
||||
}
|
||||
if (!cliPublished) {
|
||||
step(`publish ${cli.name}@${betaVersion} (tag=${channel}, provenance)`);
|
||||
pnpmPublish(cli, { tag: channel, provenance: true, dryRun });
|
||||
// Publish in dependency order.
|
||||
for (const pkg of packages) {
|
||||
if (published.get(pkg.key)) continue;
|
||||
step(`publish ${pkg.name}@${betaVersion} (tag=${channel}, provenance)`);
|
||||
pnpmPublish(pkg, { tag: channel, provenance: true, dryRun });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { parseArgs } from "util";
|
||||
import { runCheck } from "./check.mjs";
|
||||
import { createTag, currentBranch, isWorkingTreeClean, pushTag, tagExists } from "./lib/git.mjs";
|
||||
import { npmViewExists, pnpmPublish } from "./lib/npm.mjs";
|
||||
import { findPackage } from "./lib/packages.mjs";
|
||||
import { ALL_PACKAGES, findPackage, PACKAGES } from "./lib/packages.mjs";
|
||||
|
||||
function log(msg = "") {
|
||||
process.stdout.write(`${msg}\n`);
|
||||
@@ -17,10 +17,13 @@ function step(msg) {
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
"dry-run": { type: "boolean", default: false },
|
||||
knowledge: { type: "boolean", default: false },
|
||||
},
|
||||
allowPositionals: false,
|
||||
});
|
||||
const dryRun = values["dry-run"];
|
||||
const knowledge = values.knowledge;
|
||||
const packages = knowledge ? ALL_PACKAGES : PACKAGES;
|
||||
|
||||
try {
|
||||
if (!dryRun && !process.env.CI) {
|
||||
@@ -40,28 +43,26 @@ try {
|
||||
log("[dry-run] skipping working-tree + branch preflight");
|
||||
}
|
||||
|
||||
const { coreJson } = await runCheck();
|
||||
const version = coreJson.version; // === cliJson.version, asserted by runCheck
|
||||
const { coreJson } = await runCheck({ knowledge });
|
||||
const version = coreJson.version; // all packages share this, asserted by runCheck
|
||||
|
||||
step(`idempotency: check ${version} against registry`);
|
||||
const core = findPackage("core");
|
||||
const cli = findPackage("cli");
|
||||
const corePublished = npmViewExists(core.name, version);
|
||||
const cliPublished = npmViewExists(cli.name, version);
|
||||
log(`${core.name}@${version}: ${corePublished ? "already published" : "to publish"}`);
|
||||
log(`${cli.name}@${version}: ${cliPublished ? "already published" : "to publish"}`);
|
||||
if (corePublished && cliPublished) {
|
||||
log("\nboth packages already published; nothing to do.");
|
||||
const published = new Map();
|
||||
for (const pkg of packages) {
|
||||
const exists = npmViewExists(pkg.name, version);
|
||||
published.set(pkg.key, exists);
|
||||
log(`${pkg.name}@${version}: ${exists ? "already published" : "to publish"}`);
|
||||
}
|
||||
if (packages.every((pkg) => published.get(pkg.key))) {
|
||||
log("\nall packages already published; nothing to do.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!corePublished) {
|
||||
step(`publish ${core.name}@${version} (tag=latest, provenance)`);
|
||||
pnpmPublish(core, { tag: "latest", provenance: true, dryRun });
|
||||
}
|
||||
if (!cliPublished) {
|
||||
step(`publish ${cli.name}@${version} (tag=latest, provenance)`);
|
||||
pnpmPublish(cli, { tag: "latest", provenance: true, dryRun });
|
||||
// Publish in dependency order.
|
||||
for (const pkg of packages) {
|
||||
if (published.get(pkg.key)) continue;
|
||||
step(`publish ${pkg.name}@${version} (tag=latest, provenance)`);
|
||||
pnpmPublish(pkg, { tag: "latest", provenance: true, dryRun });
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
|
||||
Reference in New Issue
Block a user