Skip to content

Commit 049eecd

Browse files
committed
feat(bootstrap): add command to initialize Bailian workspace and activate postpaid services
1 parent 2907ad2 commit 049eecd

5 files changed

Lines changed: 194 additions & 0 deletions

File tree

packages/cli/src/commands.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import {
7474
tokenPlanCreateKey,
7575
tokenPlanAssignSeats,
7676
tokenPlanAddMember,
77+
bootstrap,
7778
} from "bailian-cli-commands";
7879

7980
// Full bailian-cli product: every command, exposed under the `bl` binary.
@@ -156,4 +157,5 @@ export const commands: Record<string, AnyCommand> = {
156157
"token-plan create-key": tokenPlanCreateKey,
157158
"token-plan assign-seats": tokenPlanAssignSeats,
158159
"token-plan add-member": tokenPlanAddMember,
160+
bootstrap: bootstrap,
159161
};
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { defineCommand, detectOutputFormat, BailianError, ExitCode } from "bailian-cli-core";
2+
import { emitResult, emitBare } from "bailian-cli-runtime";
3+
4+
const API = {
5+
loginInfo: "zeldaEasy.cornerstone-portal.cs-console.loginInfo",
6+
initSpace: "zeldaEasy.bailian-dash-workspace.space.initSpace",
7+
queryBuyResult: "zeldaEasy.broadscope-bailian.bill.queryBuyPostpaidResult",
8+
commodityOrderInfo: "zeldaEasy.broadscope-bailian.bill.postpaidCommodityOrderInfo",
9+
buyCommodity: "zeldaEasy.broadscope-bailian.bill.buyPostpaidCommodity",
10+
} as const;
11+
12+
const POLL_INTERVAL_MS = 1000;
13+
const MAX_POLL_ATTEMPTS = 120;
14+
15+
function sleep(ms: number): Promise<void> {
16+
return new Promise((resolve) => setTimeout(resolve, ms));
17+
}
18+
19+
interface CommodityItem {
20+
commodity?: string;
21+
status?: number;
22+
startDate?: string;
23+
}
24+
25+
export default defineCommand({
26+
description: "Initialize Bailian workspace and activate postpaid services",
27+
auth: "console",
28+
usageArgs: "",
29+
flags: {},
30+
exampleArgs: [],
31+
async run(ctx) {
32+
const { settings } = ctx;
33+
const format = detectOutputFormat(settings.output);
34+
35+
if (settings.dryRun) {
36+
emitResult(
37+
{
38+
apis: [
39+
{ step: 1, api: API.loginInfo, description: "Check login & workspace status" },
40+
{ step: 2, api: API.initSpace, description: "Initialize workspace (if needed)" },
41+
{ step: 3, api: API.queryBuyResult, description: "Query postpaid order status" },
42+
{
43+
step: 4,
44+
api: API.commodityOrderInfo,
45+
description: "Query commodity activation status",
46+
},
47+
{
48+
step: 5,
49+
api: API.buyCommodity,
50+
description: "Activate postpaid commodities (if needed)",
51+
},
52+
],
53+
},
54+
format,
55+
);
56+
return;
57+
}
58+
59+
const verbose = settings.verbose;
60+
const callApi = async (api: string) => {
61+
if (verbose) process.stderr.write(`> ${api}\n`);
62+
const resp = await ctx.client.console<any>(api, {});
63+
if (verbose) process.stderr.write(`< ${JSON.stringify(resp)}\n`);
64+
return resp;
65+
};
66+
67+
// Step 1: Check login info
68+
emitBare("Checking workspace status...");
69+
const loginInfo = await callApi(API.loginInfo);
70+
const spaceInited = loginInfo?.spaceInited === true;
71+
72+
// Step 2: Init space if needed
73+
if (!spaceInited) {
74+
emitBare("Initializing workspace...");
75+
await callApi(API.initSpace);
76+
emitBare("Workspace initialized.");
77+
} else {
78+
emitBare("Workspace already initialized.");
79+
}
80+
81+
// Step 3-5: Order & commodity flow
82+
await ensureCommoditiesActive(callApi, format);
83+
},
84+
});
85+
86+
type ApiCall = (api: string) => Promise<any>;
87+
88+
async function ensureCommoditiesActive(call: ApiCall, format: "text" | "json"): Promise<void> {
89+
emitBare("Checking service activation status...");
90+
let buyResult: string | undefined;
91+
for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
92+
const resp = await call(API.queryBuyResult);
93+
buyResult = resp?.result;
94+
if (buyResult !== "buying") break;
95+
if (i === 0) emitBare("Service activation in progress, polling...");
96+
await sleep(POLL_INTERVAL_MS);
97+
}
98+
99+
if (buyResult === "fail") {
100+
throw new BailianError("Service activation failed.", ExitCode.GENERAL);
101+
}
102+
103+
if (buyResult === "success") {
104+
await pollCommoditiesUntilActive(call, format);
105+
return;
106+
}
107+
108+
await checkAndActivateCommodities(call, format);
109+
}
110+
111+
async function checkAndActivateCommodities(call: ApiCall, format: "text" | "json"): Promise<void> {
112+
const resp = await call(API.commodityOrderInfo);
113+
const items: CommodityItem[] = resp?.result ?? [];
114+
115+
const overdue = items.filter((c) => c.status === 11);
116+
if (overdue.length > 0) {
117+
emitBare("Warning: Some services are overdue:");
118+
for (const c of overdue) emitBare(` - ${c.commodity}`);
119+
}
120+
121+
const notActivated = items.filter((c) => c.status === 1);
122+
if (notActivated.length > 0) {
123+
emitBare("Activating postpaid services...");
124+
await call(API.buyCommodity);
125+
await pollCommoditiesUntilActive(call, format);
126+
return;
127+
}
128+
129+
const active = items.filter((c) => c.status === 10);
130+
emitResult({ status: "ready", activeServices: active.map((c) => c.commodity) }, format);
131+
}
132+
133+
async function pollCommoditiesUntilActive(call: ApiCall, format: "text" | "json"): Promise<void> {
134+
emitBare("Waiting for services to activate...");
135+
for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
136+
const resp = await call(API.commodityOrderInfo);
137+
const items: CommodityItem[] = resp?.result ?? [];
138+
139+
const pending = items.filter((c) => c.status !== 10 && c.status !== 11);
140+
if (pending.length === 0) {
141+
const overdue = items.filter((c) => c.status === 11);
142+
if (overdue.length > 0) {
143+
emitBare("Warning: Some services are overdue:");
144+
for (const c of overdue) emitBare(` - ${c.commodity}`);
145+
}
146+
const active = items.filter((c) => c.status === 10);
147+
emitResult({ status: "ready", activeServices: active.map((c) => c.commodity) }, format);
148+
return;
149+
}
150+
await sleep(POLL_INTERVAL_MS);
151+
}
152+
153+
throw new BailianError("Timed out waiting for services to activate.", ExitCode.TIMEOUT);
154+
}

packages/commands/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,4 @@ export { default as tokenPlanListSeats } from "./commands/token-plan/list-seats.
7777
export { default as tokenPlanCreateKey } from "./commands/token-plan/create-key.ts";
7878
export { default as tokenPlanAssignSeats } from "./commands/token-plan/assign-seats.ts";
7979
export { default as tokenPlanAddMember } from "./commands/token-plan/add-member.ts";
80+
export { default as bootstrap } from "./commands/bootstrap/index.ts";
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# `bl bootstrap` commands
2+
3+
> Auto-generated from `packages/cli/src/commands.ts`. Do not edit by hand.
4+
> Regenerate: `pnpm --filter bailian-cli run generate:reference`.
5+
6+
Index: [index.md](index.md)
7+
8+
## Commands in this group
9+
10+
| Command | Description |
11+
| -------------- | ----------------------------------------------------------- |
12+
| `bl bootstrap` | Initialize Bailian workspace and activate postpaid services |
13+
14+
## Command details
15+
16+
### `bl bootstrap`
17+
18+
| Field | Value |
19+
| --------------- | ----------------------------------------------------------- |
20+
| **Name** | `bootstrap` |
21+
| **Description** | Initialize Bailian workspace and activate postpaid services |
22+
| **Usage** | `bl bootstrap` |
23+
24+
#### Flags
25+
26+
| Flag | Type | Required | Description |
27+
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
28+
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
29+
| `--console-site <site>` | string | no | Console site: domestic, international |
30+
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
31+
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
32+
33+
#### Examples
34+
35+
_No examples._

skills/bailian-cli/reference/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Use this index for the full quick index and global flags.
1717
| `bl auth login` | Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist) | [auth.md](auth.md) |
1818
| `bl auth logout` | Clear stored credentials | [auth.md](auth.md) |
1919
| `bl auth status` | Show current authentication state | [auth.md](auth.md) |
20+
| `bl bootstrap` | Initialize Bailian workspace and activate postpaid services | [bootstrap.md](bootstrap.md) |
2021
| `bl config set` | Set a config value | [config.md](config.md) |
2122
| `bl config show` | Display current configuration | [config.md](config.md) |
2223
| `bl console call` | Call a Bailian console API via the CLI gateway | [console.md](console.md) |
@@ -92,6 +93,7 @@ Use this index for the full quick index and global flags.
9293
| `advisor` | `recommend` | [advisor.md](advisor.md) |
9394
| `app` | `call`, `list` | [app.md](app.md) |
9495
| `auth` | `generate-access-token`, `login`, `logout`, `status` | [auth.md](auth.md) |
96+
| `bootstrap` | `(root)` | [bootstrap.md](bootstrap.md) |
9597
| `config` | `set`, `show` | [config.md](config.md) |
9698
| `console` | `call` | [console.md](console.md) |
9799
| `dataset` | `delete`, `get`, `list`, `upload`, `validate` | [dataset.md](dataset.md) |

0 commit comments

Comments
 (0)