@aiform/cli-workspace 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/bin/hive-workspace.mjs +129 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @aiform/cli-workspace
|
|
2
|
+
|
|
3
|
+
Hive 租户管理员 CLI(`hive-workspace`)—— 以租户 admin/owner 身份登录 hive **workspace** 服务的 HTTP API,管理账号与节点。不直连数据库。
|
|
4
|
+
|
|
5
|
+
平台维护动作(建租户 / 平台管理员 / 岗位 / 技能 / new-api 同步)在另一个包:[`@aiform/cli-platform`](https://www.npmjs.com/package/@aiform/cli-platform)。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g @aiform/cli-workspace # 或 pnpm add -g / npx @aiform/cli-workspace
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 配置
|
|
14
|
+
|
|
15
|
+
| 选项 | 环境变量 | 默认 |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| `--url <url>` | `WORKSPACE_URL` | `http://localhost:3000`(本地 dev) |
|
|
18
|
+
| `--email <email>` | `HIVE_WS_EMAIL` | —(必填) |
|
|
19
|
+
| `--password <pw>` | `HIVE_WS_PASSWORD` | 无则交互输入 |
|
|
20
|
+
|
|
21
|
+
线上 workspace:`https://app.hiv.aiform.com`
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
export WORKSPACE_URL=https://app.hiv.aiform.com
|
|
25
|
+
export HIVE_WS_EMAIL=admin@acme.co
|
|
26
|
+
export HIVE_WS_PASSWORD='...'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 子命令
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# 在当前登录管理员所属租户下新增成员(POST /api/members)
|
|
33
|
+
hive-workspace user add maya@acme.co --slug maya --role member
|
|
34
|
+
hive-workspace user add maya@acme.co --password 'init-pw-123' # 不传 --password 则交互输入
|
|
35
|
+
|
|
36
|
+
# 列出当前租户成员
|
|
37
|
+
hive-workspace user list
|
|
38
|
+
|
|
39
|
+
# 创建工作区(POST /api/workspaces)—— 默认 node;--type app 建出站-only App(需 admin)
|
|
40
|
+
hive-workspace workspace add prod-edge # node:含子域名 + 6 位激活码
|
|
41
|
+
hive-workspace workspace add my-app --type app # app:无子域名/无隧道,仅出站接入
|
|
42
|
+
|
|
43
|
+
# 当前登录账号自己的节点(GET /api/nodes、DELETE /api/nodes/:id)
|
|
44
|
+
hive-workspace nodes list
|
|
45
|
+
hive-workspace nodes revoke <nodeId> # 服务端会重写 chisel-authfile + reload
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
注意:`nodes` 子命令操作的是**当前登录账号自己**的节点(API 是用户级作用域);`user add` 只能加 `admin`/`member`,`owner` 易主走 workspace UI。租户的**首位**成员一般由平台侧创建(`hive-platform tenant user add` 或 platform UI)。
|
|
49
|
+
|
|
50
|
+
`workspace add` 输出 `activationCode`(1h 内有效,仅此一次):node 同时给 `subdomain`/`relayPort`;app 给 API Base URL,由 App 端用激活码换取长期 token(App 不暴露公网、仅出站调 `/api/wb/*`)。`--type app` 需以租户 admin 登录(否则服务端 403)。
|
|
51
|
+
|
|
52
|
+
## 退码
|
|
53
|
+
|
|
54
|
+
`0` 成功;`1` API/网络错误;`2` 参数错误;`130` 交互输入被 Ctrl-C 取消。
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// hive-workspace — 租户管理员 CLI(账号 / 节点)
|
|
3
|
+
//
|
|
4
|
+
// 通过 workspace 的 HTTP API 操作,以租户 admin/owner 身份登录(不直连 DB)。
|
|
5
|
+
// 平台运营动作(建租户 / 平台管理员 / 岗位 / 技能 / new-api 同步)见 `hive-platform`。
|
|
6
|
+
//
|
|
7
|
+
// 全局选项(root 上):
|
|
8
|
+
// --url <url> workspace base URL(默认 WORKSPACE_URL,否则 http://localhost:3000)
|
|
9
|
+
// --email <email> 登录 email(默认 HIVE_WS_EMAIL)
|
|
10
|
+
// --password <pw> 登录密码(默认 HIVE_WS_PASSWORD,否则交互输入)
|
|
11
|
+
//
|
|
12
|
+
// 子命令:
|
|
13
|
+
// user add <email> [--password <pw>] [--slug <slug>] [--role admin|member]
|
|
14
|
+
// user list
|
|
15
|
+
// workspace add <label> [--type node|app] (创建工作区;app 需 admin)
|
|
16
|
+
// nodes list (仅当前登录账号自己的节点)
|
|
17
|
+
// nodes revoke <nodeId>
|
|
18
|
+
//
|
|
19
|
+
// 例:
|
|
20
|
+
// hive-workspace --url https://app.example.com --email admin@acme.co user add maya@acme.co
|
|
21
|
+
// HIVE_WS_EMAIL=admin@acme.co HIVE_WS_PASSWORD=… hive-workspace user list
|
|
22
|
+
|
|
23
|
+
import { Command } from 'commander';
|
|
24
|
+
import { ApiClient, resolveBaseUrl, resolveCreds, runAction, promptHidden, die } from '@aiform/cli-core';
|
|
25
|
+
|
|
26
|
+
const TAG = 'hive-workspace';
|
|
27
|
+
|
|
28
|
+
const program = new Command();
|
|
29
|
+
program
|
|
30
|
+
.name('hive-workspace')
|
|
31
|
+
.description('hive 租户管理员 CLI(账号 / 节点)— 通过 workspace HTTP API 操作')
|
|
32
|
+
.option('--url <url>', 'workspace base URL(默认 WORKSPACE_URL env 或 http://localhost:3000)')
|
|
33
|
+
.option('--email <email>', '登录 email(默认 HIVE_WS_EMAIL env)')
|
|
34
|
+
.option('--password <pw>', '登录密码(默认 HIVE_WS_PASSWORD env,否则交互输入)')
|
|
35
|
+
// 用 --cli-version 避免与子命令选项冲突。
|
|
36
|
+
.version('0.1.0', '-V, --cli-version', '显示 CLI 版本');
|
|
37
|
+
|
|
38
|
+
/** 取一个已登录的 ApiClient(用 root 上的 --url/--email/--password)。 */
|
|
39
|
+
async function client() {
|
|
40
|
+
const o = program.opts();
|
|
41
|
+
const baseUrl = resolveBaseUrl(o.url, 'WORKSPACE_URL', 'http://localhost:3000');
|
|
42
|
+
const creds = await resolveCreds(o, { tag: TAG, emailEnv: 'HIVE_WS_EMAIL', passwordEnv: 'HIVE_WS_PASSWORD' });
|
|
43
|
+
const c = new ApiClient(baseUrl);
|
|
44
|
+
await c.login(creds);
|
|
45
|
+
return c;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const userCmd = program.command('user').description('账号管理');
|
|
49
|
+
|
|
50
|
+
userCmd
|
|
51
|
+
.command('add <email>')
|
|
52
|
+
.description('在当前登录管理员所属租户下新增成员')
|
|
53
|
+
.option('--password <pw>', '新成员初始密码(≥8 位;不传则交互输入)')
|
|
54
|
+
.option('--slug <slug>', 'user_slug(默认按 email 自动生成并去重)')
|
|
55
|
+
.option('--role <role>', '租户内角色:admin|member(默认 member;owner 只能在 UI 转交)', 'member')
|
|
56
|
+
.action(runAction(TAG, async (email, opts) => {
|
|
57
|
+
if (opts.role && !['admin', 'member'].includes(opts.role)) {
|
|
58
|
+
die(TAG, `role 不合法(admin|member): ${opts.role}`, 2);
|
|
59
|
+
}
|
|
60
|
+
let pw = opts.password;
|
|
61
|
+
if (!pw) {
|
|
62
|
+
try {
|
|
63
|
+
pw = await promptHidden('new member password: ');
|
|
64
|
+
} catch {
|
|
65
|
+
die(TAG, 'aborted', 130);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!pw) die(TAG, 'password 不能为空', 2);
|
|
69
|
+
|
|
70
|
+
const c = await client();
|
|
71
|
+
const r = await c.post('/api/members', { email, password: pw, slug: opts.slug, role: opts.role });
|
|
72
|
+
const m = r.member;
|
|
73
|
+
console.log(`[${TAG}] member added id=${m.id} email=${m.email} slug=${m.userSlug} role=${m.role}`);
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
userCmd
|
|
77
|
+
.command('list')
|
|
78
|
+
.description('列出当前租户的成员')
|
|
79
|
+
.action(runAction(TAG, async () => {
|
|
80
|
+
const c = await client();
|
|
81
|
+
const r = await c.get('/api/members');
|
|
82
|
+
console.table(r.members);
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
const wsCmd = program.command('workspace').description('工作区管理(创建 node / app)');
|
|
86
|
+
|
|
87
|
+
wsCmd
|
|
88
|
+
.command('add <label>')
|
|
89
|
+
.description('创建工作区(默认 node;--type app 创建出站-only App,需 admin)')
|
|
90
|
+
.option('--type <type>', '工作区类型:node|app(默认 node)', 'node')
|
|
91
|
+
.action(runAction(TAG, async (label, opts) => {
|
|
92
|
+
if (!['node', 'app'].includes(opts.type)) {
|
|
93
|
+
die(TAG, `type 不合法(node|app): ${opts.type}`, 2);
|
|
94
|
+
}
|
|
95
|
+
const c = await client();
|
|
96
|
+
const r = await c.post('/api/workspaces', { label, type: opts.type });
|
|
97
|
+
console.log(`[${TAG}] workspace created id=${r.workspaceId} label=${r.label} type=${r.type}`);
|
|
98
|
+
console.log(`[${TAG}] activationCode=${r.activationCode}(1h 内有效,仅此一次)`);
|
|
99
|
+
if (r.type === 'app') {
|
|
100
|
+
const baseUrl = r.activationInstructions?.endpoint ?? '—';
|
|
101
|
+
console.log(
|
|
102
|
+
`[${TAG}] App 接入:Base URL=${baseUrl};在 App 端用激活码调用激活接口换取长期 token`
|
|
103
|
+
);
|
|
104
|
+
} else {
|
|
105
|
+
console.log(`[${TAG}] subdomain=${r.subdomain} relayPort=${r.relayPort}`);
|
|
106
|
+
}
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
const nodesCmd = program.command('nodes').description('节点管理(仅当前登录账号自己的节点)');
|
|
110
|
+
|
|
111
|
+
nodesCmd
|
|
112
|
+
.command('list')
|
|
113
|
+
.description('列出当前账号的节点')
|
|
114
|
+
.action(runAction(TAG, async () => {
|
|
115
|
+
const c = await client();
|
|
116
|
+
const r = await c.get('/api/nodes');
|
|
117
|
+
console.table(r.nodes);
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
nodesCmd
|
|
121
|
+
.command('revoke <nodeId>')
|
|
122
|
+
.description('吊销当前账号名下指定节点(服务端会重写 chisel-authfile + reload)')
|
|
123
|
+
.action(runAction(TAG, async (nodeId) => {
|
|
124
|
+
const c = await client();
|
|
125
|
+
await c.del(`/api/nodes/${encodeURIComponent(nodeId)}`);
|
|
126
|
+
console.log(`[${TAG}] revoked ${nodeId}`);
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
program.parseAsync(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aiform/cli-workspace",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "hive 租户管理员 CLI — 通过 workspace HTTP API 管理账号与节点",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hive-workspace": "./bin/hive-workspace.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"commander": "^14.0.3",
|
|
21
|
+
"@aiform/cli-core": "^0.1.0"
|
|
22
|
+
}
|
|
23
|
+
}
|