@xinhehuimeng/cli 0.2.3 → 0.2.5
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 +19 -0
- package/package.json +1 -1
- package/src/commands/member.js +39 -1
- package/src/commands/session.js +59 -6
- package/src/spec/command_surface.json +71 -2
- package/src/spec/embedded_skills.json +2 -2
- package/src/version.js +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,25 @@ npx @xinhehuimeng/cli login --phone 13800000000 --code 123456
|
|
|
13
13
|
npx @xinhehuimeng/cli me --json
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
管理员为普通成员发放 30 分钟临时口令后,可以改用自己的长期口令:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 1) 用临时口令登录
|
|
20
|
+
printf '%s' "$TEMP_PW" | xinhe-oa-cli login --password --account <手机号>
|
|
21
|
+
|
|
22
|
+
# 2) 首次登录必须改密;改密会吊销该账号全部旧会话
|
|
23
|
+
printf '%s\n%s\n%s\n' "$TEMP_PW" "$NEW_PW" "$NEW_PW" | \
|
|
24
|
+
xinhe-oa-cli login --change-password
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
管理员发放临时口令:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
xinhe-oa-cli member --reset-password <USER_ID> --yes --json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
临时口令只显示一次,不要发到工单评论或群聊。
|
|
34
|
+
|
|
16
35
|
装一次就够(`npm i -g @xinhehuimeng/cli` 之后直接用 `xinhe-oa-cli`):
|
|
17
36
|
|
|
18
37
|
```bash
|
package/package.json
CHANGED
package/src/commands/member.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// 成员域:`member --list|--search|--create|--role|--revoke-role|--remove
|
|
1
|
+
// 成员域:`member --list|--search|--create|--role|--revoke-role|--remove|--reset-password`,
|
|
2
2
|
// 含 ORG-001 的部门归属三件套(`--set-dept` / `--unset-dept` / `--set-manager|--clear-manager`)、
|
|
3
3
|
// ORG-002 的授权三动作(`--grant|--revoke-grant|--grants`,issue #82)、
|
|
4
4
|
// ORG-004 的离职 / 移交四动作(issue #81)与 MEMBER-001 的 `--search`(issue #99)。
|
|
@@ -81,6 +81,8 @@ export async function handleMember({ args, runtime }) {
|
|
|
81
81
|
return revokeRole(runtime, args, action.role, action.user);
|
|
82
82
|
case 'remove':
|
|
83
83
|
return removeMember(runtime, action.user, args);
|
|
84
|
+
case 'reset-password':
|
|
85
|
+
return resetPassword(runtime, action.user);
|
|
84
86
|
case 'set-dept':
|
|
85
87
|
return setDept(runtime, args, action.user, action.deptId, action.primary);
|
|
86
88
|
case 'unset-dept':
|
|
@@ -129,6 +131,7 @@ function memberAction(args) {
|
|
|
129
131
|
['--role', given('--role') && !on('--create')],
|
|
130
132
|
['--revoke-role', given('--revoke-role')],
|
|
131
133
|
['--remove', given('--remove')],
|
|
134
|
+
['--reset-password', given('--reset-password')],
|
|
132
135
|
['--set-dept', given('--set-dept')],
|
|
133
136
|
['--unset-dept', given('--unset-dept')],
|
|
134
137
|
['--set-manager', given('--set-manager')],
|
|
@@ -396,6 +399,15 @@ function memberAction(args) {
|
|
|
396
399
|
}
|
|
397
400
|
return { kind: 'transfer-revert', transferId, note: String(note) };
|
|
398
401
|
}
|
|
402
|
+
case '--reset-password':
|
|
403
|
+
return {
|
|
404
|
+
kind: 'reset-password',
|
|
405
|
+
user: requiredId(
|
|
406
|
+
val('--reset-password'),
|
|
407
|
+
'--reset-password',
|
|
408
|
+
'例:xinhe-oa-cli member --reset-password usr_123 --yes --json',
|
|
409
|
+
),
|
|
410
|
+
};
|
|
399
411
|
default:
|
|
400
412
|
return { kind: 'remove', user: String(val('--remove') ?? '') };
|
|
401
413
|
}
|
|
@@ -779,6 +791,32 @@ async function removeMember(runtime, userId, args) {
|
|
|
779
791
|
});
|
|
780
792
|
}
|
|
781
793
|
|
|
794
|
+
/** 管理员发放 30 分钟一次性口令,并吊销该成员全部旧会话。 */
|
|
795
|
+
async function resetPassword(runtime, userId) {
|
|
796
|
+
runtime.requireYes(
|
|
797
|
+
'重置成员口令',
|
|
798
|
+
`xinhe-oa-cli member --reset-password ${userId}`,
|
|
799
|
+
);
|
|
800
|
+
const path = `/admin/users/${userId}/password/reset`;
|
|
801
|
+
const spec = requestSpec();
|
|
802
|
+
if (runtime.dryRun) return runtime.dryRunReport('POST', path, spec);
|
|
803
|
+
const response = await runtime.sendAuthed('POST', path, spec);
|
|
804
|
+
const data = dataOf(response);
|
|
805
|
+
runtime.finishFrom(response, {
|
|
806
|
+
user: data.user ?? null,
|
|
807
|
+
temporary_password: data.temporary_password ?? null,
|
|
808
|
+
expires_at: data.expires_at ?? null,
|
|
809
|
+
sessions_revoked: data.sessions_revoked ?? null,
|
|
810
|
+
must_change_password: data.must_change_password ?? null,
|
|
811
|
+
next_steps: [
|
|
812
|
+
'通过安全渠道把临时口令单独交给本人,不要发群聊或工单评论',
|
|
813
|
+
"本人登录:printf '%s' \"$TEMP_PW\" | xinhe-oa-cli login --password --account <手机号>",
|
|
814
|
+
"本人立即改密:printf '%s\\n%s\\n%s\\n' \"$TEMP_PW\" \"$NEW_PW\" \"$NEW_PW\" | xinhe-oa-cli login --change-password",
|
|
815
|
+
],
|
|
816
|
+
warning: '旧口令与该成员的全部旧会话已失效;临时口令只显示这一次。',
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
782
820
|
// ---------------------------------------------------------------------------
|
|
783
821
|
// 部门归属(ORG-001):set-dept / unset-dept / set-manager
|
|
784
822
|
// ---------------------------------------------------------------------------
|
package/src/commands/session.js
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
import { flagOn, flagValue } from '../core/args.js';
|
|
34
34
|
import {
|
|
35
35
|
adoptSession,
|
|
36
|
+
credentialFromTokens,
|
|
36
37
|
envelopeData,
|
|
37
38
|
loginWithPassword,
|
|
38
39
|
logout as logoutSession,
|
|
@@ -53,8 +54,9 @@ const SCOPE_CAP = 50;
|
|
|
53
54
|
* 顺序不是随手写的:clap 的 `ArgGroup(multiple = false)` 保证四选一,
|
|
54
55
|
* 但"既有 code 又 adopt"这种组合在 Rust 里由 adopt 先赢 —— `meta.command` 要一致。
|
|
55
56
|
*/
|
|
56
|
-
function loginKind({ adopt, password, hasCode }) {
|
|
57
|
+
function loginKind({ adopt, password, changePassword, hasCode }) {
|
|
57
58
|
if (adopt) return '--adopt-session';
|
|
59
|
+
if (changePassword) return '--change-password';
|
|
58
60
|
if (password) return '--password';
|
|
59
61
|
if (hasCode) return '--phone --code';
|
|
60
62
|
return '--phone';
|
|
@@ -64,6 +66,7 @@ function loginKind({ adopt, password, hasCode }) {
|
|
|
64
66
|
export async function handleLogin({ args, runtime }) {
|
|
65
67
|
const adopt = flagOn(args, '--adopt-session');
|
|
66
68
|
const password = flagOn(args, '--password');
|
|
69
|
+
const changePassword = flagOn(args, '--change-password');
|
|
67
70
|
const phone = flagValue(args, '--phone');
|
|
68
71
|
const code = flagValue(args, '--code');
|
|
69
72
|
const account = flagValue(args, '--account');
|
|
@@ -72,15 +75,18 @@ export async function handleLogin({ args, runtime }) {
|
|
|
72
75
|
// (`login --phone` / `login --phone --code` / `login --password` / `login --adopt-session`),
|
|
73
76
|
// 而 Node 的命令表按域名折叠成 `login`。这里在收尾(以及发请求)之前把它校正成
|
|
74
77
|
// Rust 的形态,`--json` 的 `meta.command` 才逐字一致。
|
|
75
|
-
runtime.command = `login ${loginKind({ adopt, password, hasCode: code !== null })}`;
|
|
78
|
+
runtime.command = `login ${loginKind({ adopt, password, changePassword, hasCode: code !== null })}`;
|
|
76
79
|
|
|
77
80
|
// ① 交接会话(install.sh 用):不联网,只把它写进自己的凭据文件。
|
|
78
81
|
if (adopt) return adoptFromStdin(runtime);
|
|
79
82
|
|
|
80
|
-
// ②
|
|
83
|
+
// ② 当前会话改密:先完成临时口令登录,再读取三行口令。
|
|
84
|
+
if (changePassword) return changeCurrentPassword(runtime);
|
|
85
|
+
|
|
86
|
+
// ③ 口令登录(普通成员或引导账号;口令从 stdin 读,**不进 argv**)。
|
|
81
87
|
if (password) return loginByPassword(runtime, account);
|
|
82
88
|
|
|
83
|
-
//
|
|
89
|
+
// ④ 拿验证码换凭据。
|
|
84
90
|
//
|
|
85
91
|
// 判空用 `=== null`(而不是 falsy):`--phone=` 这种显式空串在 Rust 里是
|
|
86
92
|
// `Some("")`,会被原样发给服务端,由服务端回"号码不合法" —— CLI 不替它判。
|
|
@@ -91,12 +97,59 @@ export async function handleLogin({ args, runtime }) {
|
|
|
91
97
|
}
|
|
92
98
|
if (code !== null) return loginByCode(runtime, phone, code);
|
|
93
99
|
|
|
94
|
-
//
|
|
100
|
+
// ⑤ 只给手机号 = 发码。
|
|
95
101
|
return requestOtp(runtime, phone);
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
async function changeCurrentPassword(runtime) {
|
|
105
|
+
const raw = await readStdinText();
|
|
106
|
+
const lines = raw.split(/\r?\n/).map((line) => line.trim());
|
|
107
|
+
if (lines.length < 3 || lines.slice(0, 3).some((line) => line.length === 0)) {
|
|
108
|
+
throw CliError.usage('标准输入里需要当前口令、新口令、确认新口令三行').withHint(
|
|
109
|
+
"用法:printf '%s\\n%s\\n%s\\n' \"$OLD_PW\" \"$NEW_PW\" \"$NEW_PW\" | xinhe-oa-cli login --change-password",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const [currentPassword, newPassword, confirmation] = lines;
|
|
113
|
+
if (newPassword !== confirmation) throw CliError.usage('两次输入的新口令不一致');
|
|
114
|
+
if (newPassword.length < 12) throw CliError.usage('新口令至少需要 12 个字符');
|
|
115
|
+
if (newPassword === currentPassword) throw CliError.usage('新口令不能与当前口令相同');
|
|
116
|
+
|
|
117
|
+
const spec = requestSpec({
|
|
118
|
+
body: { current_password: currentPassword, new_password: newPassword },
|
|
119
|
+
});
|
|
120
|
+
if (runtime.dryRun) {
|
|
121
|
+
return runtime.dryRunReport('POST', '/auth/password/change', spec);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const response = await runtime.sendAuthed('POST', '/auth/password/change', spec);
|
|
125
|
+
const body = envelopeData(response);
|
|
126
|
+
const previous = runtime.credentials.get(runtime.profile);
|
|
127
|
+
const phone = previous?.phone ?? runtime.config.profile(runtime.profile).phone ?? null;
|
|
128
|
+
const deviceId = runtime.ensureDeviceId();
|
|
129
|
+
const credential = credentialFromTokens(body, runtime.baseUrl, deviceId, phone);
|
|
130
|
+
runtime.credentials.put(runtime.profile, credential);
|
|
131
|
+
if (typeof phone === 'string' && phone.length > 0) {
|
|
132
|
+
runtime.config.setPhone(phone);
|
|
133
|
+
runtime.config.save();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return runtime.finishFrom(response, {
|
|
137
|
+
profile: runtime.profile,
|
|
138
|
+
base_url: credential.base_url,
|
|
139
|
+
user: credential.user ?? null,
|
|
140
|
+
auth_method: 'password',
|
|
141
|
+
sessions_revoked: body.sessions_revoked ?? null,
|
|
142
|
+
credentials_file: runtime.paths.credentialsFile,
|
|
143
|
+
access_token: maskToken(credential.tokens?.access_token ?? ''),
|
|
144
|
+
refresh_token: maskToken(credential.tokens?.refresh_token ?? ''),
|
|
145
|
+
access_expires_at: credential.tokens?.access_expires_at ?? null,
|
|
146
|
+
next_step: '口令已更新并保存新会话;现在可以执行 `xinhe-oa-cli me --json` 或每日任务',
|
|
147
|
+
note: '旧口令、全部旧会话和临时口令均已失效;新令牌未进入 argv 与日志。',
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
98
151
|
/**
|
|
99
|
-
* `login --password --account
|
|
152
|
+
* `login --password --account`:普通成员或引导账号,口令从 stdin 读。
|
|
100
153
|
*
|
|
101
154
|
* 三步的顺序是刻意的(与 Rust 一致):**先落盘 device_id,再读口令,最后才碰网络**。
|
|
102
155
|
* 口令读失败(stdin 是空的)时,device_id 已经存好了 —— 用户重跑一次不会换指纹。
|
|
@@ -299,6 +299,43 @@
|
|
|
299
299
|
"--password"
|
|
300
300
|
]
|
|
301
301
|
},
|
|
302
|
+
{
|
|
303
|
+
"name": "login --change-password",
|
|
304
|
+
"flags": {
|
|
305
|
+
"--change-password": {
|
|
306
|
+
"takes_value": false,
|
|
307
|
+
"required": false,
|
|
308
|
+
"optional_value": false
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
"extra_flags": [],
|
|
312
|
+
"allowed_flags": [],
|
|
313
|
+
"params_raw": [
|
|
314
|
+
"--change-password"
|
|
315
|
+
],
|
|
316
|
+
"params": [
|
|
317
|
+
{
|
|
318
|
+
"flag": "--change-password"
|
|
319
|
+
}
|
|
320
|
+
],
|
|
321
|
+
"risk": "write",
|
|
322
|
+
"requires_yes": false,
|
|
323
|
+
"endpoints": [
|
|
324
|
+
"POST /auth/password/change"
|
|
325
|
+
],
|
|
326
|
+
"examples": [
|
|
327
|
+
"printf '%s\\n%s\\n%s\\n' \"$OLD_PW\" \"$NEW_PW\" \"$NEW_PW\" | xinhe-oa-cli login --change-password"
|
|
328
|
+
],
|
|
329
|
+
"output_schema": "{user, auth_method, sessions_revoked, tokens_saved: true}(旧会话与临时口令失效)",
|
|
330
|
+
"synonym_flags": [],
|
|
331
|
+
"rejected_flags": [
|
|
332
|
+
"--phone",
|
|
333
|
+
"--code",
|
|
334
|
+
"--password",
|
|
335
|
+
"--account",
|
|
336
|
+
"--adopt-session"
|
|
337
|
+
]
|
|
338
|
+
},
|
|
302
339
|
{
|
|
303
340
|
"name": "logout",
|
|
304
341
|
"flags": {},
|
|
@@ -22329,7 +22366,7 @@
|
|
|
22329
22366
|
"xinhe-oa-cli report rule --create --key work_daily --name 工作日报 --cadence daily --fillers '{\"roles\":[\"tenant_member\"],\"only_employed\":true}' --recipients '[{\"type\":\"direct_leader\"},{\"type\":\"dept_leader\"}]' --json",
|
|
22330
22367
|
"xinhe-oa-cli report rule --create --key work_weekly --name 工作周报 --cadence weekly --from '{\"recipients\":[{\"type\":\"direct_leader\"}],\"deadline\":{\"anchor\":\"period_end\",\"offset\":\"PT10H\"}}'"
|
|
22331
22368
|
],
|
|
22332
|
-
"output_schema": "{rule: {id, key, name, cadence, version}, period: {period_key, due_at, assignments_created, opened, materialized}}
|
|
22369
|
+
"output_schema": "{rule: {id, key, name, cadence, version}, period: {period_key, due_at, assignments_created, opened, materialized}}(建规则返回当前周期信息;template=[] 表示有界自由格式 content;不预建 period / assignment)",
|
|
22333
22370
|
"synonym_flags": [],
|
|
22334
22371
|
"rejected_flags": [
|
|
22335
22372
|
"--preview"
|
|
@@ -23287,7 +23324,7 @@
|
|
|
23287
23324
|
"xinhe-oa-cli report --submit --rule work_daily --content '{\"done\":\"走访 3 家\"}' --json",
|
|
23288
23325
|
"xinhe-oa-cli report --submit --rule work_daily --period D:2026-10-07 --content @- --cc usr_a,usr_b"
|
|
23289
23326
|
],
|
|
23290
|
-
"output_schema": "{report: {id, period_key, status: submitted|late, submitted_at}}(不传 --period
|
|
23327
|
+
"output_schema": "{report: {id, period_key, status: submitted|late, submitted_at}}(不传 --period 时提交当前周期;历史日期默认允许无限期补交;template=[] 时 content 使用有界自由格式 JSON)",
|
|
23291
23328
|
"synonym_flags": [],
|
|
23292
23329
|
"rejected_flags": [
|
|
23293
23330
|
"--include-sub",
|
|
@@ -26809,6 +26846,38 @@
|
|
|
26809
26846
|
"--primary"
|
|
26810
26847
|
]
|
|
26811
26848
|
},
|
|
26849
|
+
{
|
|
26850
|
+
"name": "member --reset-password",
|
|
26851
|
+
"flags": {
|
|
26852
|
+
"--reset-password": {
|
|
26853
|
+
"takes_value": true,
|
|
26854
|
+
"required": false,
|
|
26855
|
+
"optional_value": false
|
|
26856
|
+
}
|
|
26857
|
+
},
|
|
26858
|
+
"extra_flags": [],
|
|
26859
|
+
"allowed_flags": [],
|
|
26860
|
+
"params_raw": [
|
|
26861
|
+
"--reset-password <USER_ID>"
|
|
26862
|
+
],
|
|
26863
|
+
"params": [
|
|
26864
|
+
{
|
|
26865
|
+
"flag": "--reset-password",
|
|
26866
|
+
"value": "USER_ID"
|
|
26867
|
+
}
|
|
26868
|
+
],
|
|
26869
|
+
"risk": "high-risk-write",
|
|
26870
|
+
"requires_yes": true,
|
|
26871
|
+
"endpoints": [
|
|
26872
|
+
"POST /admin/users/{id}/password/reset"
|
|
26873
|
+
],
|
|
26874
|
+
"examples": [
|
|
26875
|
+
"xinhe-oa-cli member --reset-password usr_123 --yes --json"
|
|
26876
|
+
],
|
|
26877
|
+
"output_schema": "{user, temporary_password, expires_at, sessions_revoked, must_change_password}(临时口令只显示一次,30 分钟有效)",
|
|
26878
|
+
"synonym_flags": [],
|
|
26879
|
+
"rejected_flags": []
|
|
26880
|
+
},
|
|
26812
26881
|
{
|
|
26813
26882
|
"name": "config --list",
|
|
26814
26883
|
"flags": {
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
{
|
|
13
13
|
"name": "xo-core",
|
|
14
14
|
"path": "skills/xo-core/SKILL.md",
|
|
15
|
-
"bytes":
|
|
16
|
-
"content": "# xo-core · xinhe-oa-cli 使用说明(所有角色)\n\n**这个技能解决什么**:告诉你这台机器上唯一的客户端 `xinhe-oa-cli` 怎么用 ——\n怎么登录、怎么看自己的权限、怎么建多维表格、怎么把文件里的数据传上去、怎么装别的技能。\n**读完这一份,日常的活你就能干了。**\n\n> 本技能对**所有已登录用户**可用。平台层面的操作(建企业、任命企业负责人、跨企业查看)\n> 在 `xo-admin` 里,只有 super_admin 拿得到。\n\n---\n\n## 0. 三条铁律(先读这个)\n\n1. **只有命令面**:一切操作都通过 `xinhe-oa-cli` 的子命令。**没有**裸调接口、**没有** SQL 逃生口、\n 没有任意路径 —— 想干的事如果命令面里没有,就是平台不提供,不要试图绕。\n2. **令牌你看不到**:登录后凭据由 CLI 自己维护(`~/.config/xinhe-oa-cli/credentials.json`,0600)。\n 不要试图读它、不要打印它、不要写进任何文件或提示词。\n3. **权限跟着令牌走**:你能看到什么,取决于你这个身份被授了什么。\n 被拒不是 bug —— 退出码 `4` 就是\"去要权限\"的信号(下面 §6 有怎么要)。\n\n---\n\n## 1. 第一次使用\n\n```bash\n# ① 指向后端(唯一必须人工提供的东西)\nxinhe-oa-cli config --set base_url=http://<后端地址>:8100\n\n# ② 登录(两步:发码 → 校验)\nxinhe-oa-cli login --phone <手机号> # 返回里有 dev_code(开发模式回显)\nxinhe-oa-cli login --phone <手机号> --code <验证码> # 成功后凭据落盘,之后不用再登\n\n# ③ 我是谁、能做什么、缺什么\nxinhe-oa-cli me --json\n\n# ④ 装技能(把服务端发布的技能拉到本地,供你读)\nxinhe-oa-cli skills --install --dest ./skills\nxinhe-oa-cli skills --list # 看有哪些;内嵌的那份不需要联网\nxinhe-oa-cli skills --read xo-core # 直接读某一份\n```\n\n**access 令牌只活 10 分钟**,CLI 会自动续期。续不回来时(refresh 也过期)会返回**退出码 3**,\n提示里直接给出重新登录的命令 —— 照着做即可,不要反复重试。\n\n---\n\n## 2. 我是谁、我有什么权限\n\n```bash\nxinhe-oa-cli me --json\n```\n\n返回里要紧的几块:\n\n| 字段 | 含义 |\n|---|---|\n| `user.roles` | 我的角色(`super_admin` / `tenant_owner` / `tenant_member` / `investor`) |\n| `scope.tenants` | 我能看到哪些企业 |\n| `scope.stores` | 我能看到哪些门店 |\n| `capabilities` | 我能做哪些动作(决定命令面里哪些能用) |\n\n**看不到的东西不会告诉你\"存在\"** —— 这是刻意的(避免用报错探测别人的数据)。\n\n---\n\n## 3. 多维表格(主命令面)\n\n企业自建的多维表格承担\"数据库\"的角色。层次是:**库(base) → 表(table) → 字段(field) → 记录(record)**,\n外加**视图(view)** 与**表单(form)** 两种使用界面。\n\n### 3.1 建结构\n\n```bash\nxinhe-oa-cli bitable base --create --name 财务数据 --key finance --json\nxinhe-oa-cli bitable table --create --base <base_id> --name 公账流水 --key bank_flow --json\nxinhe-oa-cli bitable field --create --table <table_id> --name 金额 --type currency --semantic-key amount --json\n```\n\n> `--semantic-key` 是**机器名**(建后不可变),`--name` 是显示名(可改)。\n> 下游脚本与 AI 认语义键,所以**改名不会打断任何东西**。\n\n字段类型(`--type`):`text` `number` `currency` `date` `single_select` `multi_select` `boolean` `person` `attachment_ref` `relation`。\n\n### 3.2 写与读\n\n```bash\n# 写一条\nxinhe-oa-cli bitable record --create --table <table_id> --set 金额=12000 --set 摘要=\"诊疗收入\" --json\n\n# 读:默认只取一页;--all 自动翻完;要自己翻页用 --limit + --offset\n# (返回里有 next_offset / has_more:下一把直接拿 next_offset 当 --offset)\n# 排序不用自己拼\"打破并列\"的键:CLI 会自动补唯一的 id(返回里的 order 就是实际发出去的那份)。\n# --all 翻完会自检:有重复行 / 与 total 对不上 → 退出码 5、cli.paging_unstable\n# (多半是翻页期间有人在写这张表,重跑一次即可;要一致的快照用视图的游标分页)\nxinhe-oa-cli bitable record --list --table <table_id> --all --json\nxinhe-oa-cli bitable record --list --table <table_id> --limit 1000 --offset 1000 --json\n\n# 按字段筛行(例:按日期区间取数,1 万行也一样):过滤在**客户端**做(表取数接口没有过滤参数,\n# --all 先翻完再筛),字段写 **semantic_key**(`bitable field --list` 里的机器名 —— 写显示名会**静默**筛出 0 条);\n# 日期字段的值是 ISO UTC(`2026-01-31T00:00:00+00:00`)、比较按**字典序**,所以区间一律写**半开**:\n# 起点 gte + 次日 lt(写成 lte:2026-01-31 会漏掉 1/31 当天)。\nxinhe-oa-cli bitable record --list --table <table_id> \\\n --all --limit 1000 --workers 4 \\\n --filter 'visit_date:gte:2026-01-01' --filter 'visit_date:lt:2026-02-01' --json\n\n# 改 / 删\nxinhe-oa-cli bitable record --update <record_id> --set 摘要=\"已核对\" --json\nxinhe-oa-cli bitable record --delete <record_id> --json # 高风险:要 --yes\n```\n\n### 3.3 视图与表单\n\n```bash\nxinhe-oa-cli bitable view --create --table <table_id> --name 收入视图 --json\nxinhe-oa-cli bitable view --query --view <view_id> --all --json\n# 按字段筛行走**服务端**(只回命中行、游标分页不重不漏;字段用 semantic_key)\nxinhe-oa-cli bitable view --query --view <view_id> \\\n --filter 'visit_date:gte:2026-01-01' --filter 'visit_date:lt:2026-02-01' \\\n --page-size 1000 --all --json\nxinhe-oa-cli bitable view --aggregate --view <view_id> --measure 金额:sum --group-by 科室 --json\nxinhe-oa-cli bitable form --submit --form <form_id> --set 摘要=\"报销\" --json\n```\n\n> **视图取数是\"服务端过滤 + 游标分页\"的快路径**(0.1.5 起):过滤下推到服务端(写法与 §3.2 相同,\n> 字段同样是 semantic_key),只要命中行;`--all` 撞上 200 页上限会**告警**并给\n> `truncated: true` + `next_cursor`,拿它 `--cursor` 接着取(不会假装翻完了)。\n> ⚠️ **0.1.4 及更早有缺陷**:CLI 读 `data.records`、服务端回 `data.items`,`view --query`\n> 与 `record --list --view` 恒返回空行且 `ok:true` —— 那个版本请改用 §3.2 的 `record --list`。\n\n### 3.4 表默认是**私有**的:看不见 ≠ 不存在\n\n> **新建的库与表默认 `private`** —— **只有建它的人看得到**。别人(含同部门同事)\n> 既读不到、列表里也不会出现,只以 `hidden_count` 计数。\n> **别把\"查不到\"当成\"表不存在\"**:先 `--visibility --show` 看档位与分享。\n\n```bash\n# 看档位(回的是**声明档位** + **夹紧后的有效档位**)\nxinhe-oa-cli bitable table --visibility <table_id> --show --json\n# → {visibility, effective_visibility, clamped_by, owner_user_id, readable_by_me, ...}\n\n# 改档位(高风险:要 --yes):private | dept | internal\nxinhe-oa-cli bitable table --visibility <table_id> --set internal --yes --json\nxinhe-oa-cli bitable table --visibility <table_id> --set dept --dept <ou_id> --yes --json\nxinhe-oa-cli bitable table --move <table_id> --to-dept <ou_id> --yes --json # 换归属=重算继承\n\n# 分享:**默认只给读**;要别人能改,必须显式 --acl-action write(默认 90 天到期)\nxinhe-oa-cli bitable table --share <table_id> --to-user <uid> --json\nxinhe-oa-cli bitable table --share <table_id> --to-user <uid> --acl-action write --json\nxinhe-oa-cli bitable table --share <table_id> --to-dept <ou_id> --json\nxinhe-oa-cli bitable table --acl-list <table_id> --json\nxinhe-oa-cli bitable table --acl-revoke <table_id> --from-user <uid> --acl-action read --json\n\n# 库侧同一组命令(`bitable base --visibility|--share|--acl-list|--move …`)\n# 投资人逐表放行(TABLE-002;高风险)\nxinhe-oa-cli bitable table --investor-visibility <table_id> --investor-set open --yes --json\n```\n\n> **分享表之前先分享库**:库的档位是表的**上限** —— 库没给出去,表分享了也读不到\n> (`--visibility --show` 的 `clamped_by` 会告诉你是被哪个库夹住的)。\n\n| 口径 | 说明 |\n|---|---|\n| **默认私有** | 新库 / 新表都是 `private`;存量表由迁移标成 `internal`(升级前的等效语义) |\n| **库是表的上限** | 库 `private` ⇒ 库里 `internal` 的表**实际按 private 判**(`effective_visibility` 会告诉你被谁夹住) |\n| **分享默认读** | 不给 `--write` 就只能看;**只被分享了 read 的人改不动记录**(403,不是 404) |\n| **写分享有到期** | `--write` 默认 90 天,**不给永久**(与文件域同口径) |\n| **`public` 不可写** | 本期没有对外分享入口,写入直接 400 |\n| **企业负责人看得到全部** | 但每次**纯治理读**都留一条 `object.admin_read` 审计 |\n| **投资人默认看不到** | 企业要逐表 `--investor-visibility --set open` 才可见(TABLE-002) |\n\n> **别用大范围档位图省事**:`--set internal` 等于\"全企业可读可写(模板自带 write)\"。\n> 只让某几个人看,用 `--share`(先 `--share` 库、再 `--share` 表 —— 库是上限)。\n\n---\n\n## 4. 把文件里的数据传上去(最常用的一招)\n\n**解析在客户端完成**,平台只存结果 —— 所以你不需要先把 Excel 转成别的格式。\n\n```bash\nxinhe-oa-cli bitable table --import \\\n --file ./公账流水.csv \\\n --base <base_id> \\\n --name 公账流水 --json\n```\n\n它会:读表头 → 推断每列类型(金额带千分位、日期、百分比都能认)→ 建表 → 建字段 → **分批写记录**(默认每批 200 条)。\n`.csv` 与 `.xlsx` 都支持(xlsx 默认读第一张 sheet,可用 `--sheet <名字>` 指定)。\n\n**大文件(几千行以上)加三个参数**(2026-09-29 起支持:实测 5,000 行从 5 分钟降到 33 秒):\n\n```bash\nxinhe-oa-cli bitable table --import --file ./大表.csv --base <base_id> --name 大表 \\\n --batch-size 500 --workers 4 --json\n```\n\n- `--batch-size 500`:每批最多 500 行(服务端 `row_ops_batch_max` 也是 500;宽表会按请求体\n 大小自动再切小)。批越大,每批那点固定开销摊得越薄;\n- `--workers 4`:并发发批(默认 3,上限 16)。服务端喊\"慢一点\"(429/5xx)时它会自己降并发;\n- **中途断了直接重跑同一条命令**:进度落在 `<配置目录>/checkpoints/`,只补剩余批次,\n 不会重复写入(`--fresh` 才是从头来)。断了的那次会明确告诉你\"已写入 N 行、怎么续\"。\n\n导入过程每批往 stderr 打一行进度(`batch 12/29 · 2400/5611 行 · elapsed 132s · 18.2 行/秒`),\n不想看加全局 `--quiet`(注意:告警不会被 `--quiet` 吞掉)。\n\n返回里给你 `table_id` / `records_created` / `rows_in_file` —— **对一下这两个数**,\n不一致就说明有行没读进来(空行会被跳过,`skipped_empty_rows` 会告诉你跳了多少)。\n\n想手工指定某列类型,用 `--type 列名=类型`(可重复)。想把原始文件也留档,加 `--keep-source`。\n\n---\n\n## 5. 附件\n\n附件是**独立资源**:上传拿到 `attachment_id`,再显式挂到某条记录的附件字段上。\n\n```bash\nxinhe-oa-cli attachment --upload --file ./单据.pdf --tenant <企业id> --name 单据.pdf --json\nxinhe-oa-cli attachment --link --id <attachment_id> --resource-type record --resource-id <record_id> --field <field_id> --json\nxinhe-oa-cli attachment --download <attachment_id> --out ./下载.pdf --json\n```\n\n---\n\n## 6. 被拒绝怎么办(退出码 4)\n\nCLI 会把服务端的原话透出来,`error.details.missing_scopes` 里就是**你缺的那条权限**:\n\n```json\n{\"code\": \"authz.missing_scope\",\n \"details\": {\"missing_scopes\": [\"data:kashu:*:*:write\"]},\n \"hint\": \"响应 details.missing_scopes 里是缺的 scope,拿它去找 super_admin 走 POST /admin/grants 授权\"}\n```\n\n**正确做法**:拿着这条 scope 去找**企业负责人**(本企业的事)或 **super_admin**(平台的事)要权限,\n而不是换个命令反复试、更不是试图绕。反复越权探测本身会被审计记录。\n\n---\n\n## 7. 企业负责人:管理本企业的人\n\n> 只有 `tenant_owner` 能做。其余角色做这些会得到退出码 `4`。\n\n```bash\n# 给本企业加人并赋角(一步完成;不写 --tenant 就是你自己那家企业)\nxinhe-oa-cli member --create --phone 13900000003 --name 小唐 --role tenant_member --json\n\n# 看本企业有哪些人、各自什么角色\nxinhe-oa-cli member --list --json\nxinhe-oa-cli member --role <user_id> --json\n\n# 撤角(撤权会同时吊销那个人的会话链)\nxinhe-oa-cli member --revoke-role <user_id> --reason \"离职\" --json\n```\n\n**角色怎么选**:\n\n| 角色 | 能干什么 | 用在哪 |\n|---|---|---|\n| `tenant_member` | 本企业**读 / 写 / 建表**(传数据的人) | 业务员、财务 |\n| `tenant_operator` | 只能读写**被点名的那几张表**(可限门店) | 只看某块数据的人 |\n| `tenant_owner` | 本企业**全量** + 管人(建人/赋角) | 企业负责人 |\n\n### 让投资人能看本企业的数据\n\n投资人的可见性是**两道门**:平台声明(super_admin 赋 `investor` 角色)+ **企业背书**(你点头)。\n缺任何一道,他都看不到 —— 这是刻意的:**能不能看你的数,由你说了算**。\n\n```bash\nxinhe-oa-cli investor --list --json # 看谁在等你确认\nxinhe-oa-cli investor --endorse --user <user_id> --reason \"投资人可查看全部数据\" --json\nxinhe-oa-cli investor --revoke --user <user_id> --reason \"合作结束\" --json # 随时收回\n```\n\n---\n\n## 8. 日报 / 周报(汇报)\n\n> 规则是**配置面**(谁写给谁看),`report_instance` 是**日报主数据**。\n> 当前周期按日期实时计算:员工直接提交即可,**不依赖服务端定时任务开窗**。\n> 没有 active 规则时 `report --due` 才会为空。\n> 日报默认允许无限期补交:历史日期显式传 `--period` 即可;未来周期仍禁止提交。\n\n```bash\n# ① 建规则(企业负责人;--fillers 是**谁要交**,不给就谁都不用交)\nxinhe-oa-cli report rule --create --key work_daily --name 工作日报 --cadence daily \\\n --template '[{\"key\":\"done\",\"label\":\"今天做了什么\",\"type\":\"text\",\"required\":true},\n {\"key\":\"plan\",\"label\":\"明天计划\",\"type\":\"text\",\"required\":true}]' \\\n --fillers '{\"roles\":[\"tenant_member\",\"tenant_operator\"],\"only_employed\":true}' \\\n --recipients '[{\"type\":\"direct_leader\"},{\"type\":\"dept_leader\"}]' --json\n\n# 返回里的 period.period_key(如 D:2026-10-28)可用于显式指定当前周期;\n# 日常提交不需要它,服务端会按规则和当前日期自动计算。\n\n# ② 预演(不落库):谁要填 / 谁收到 / 谁没解析到 —— 配错了在这里就看得见\nxinhe-oa-cli report rule --preview --rule work_daily --json\n\n# ③ 员工:看该填什么 → 直接存草稿 / 提交(不再要求 --period)\nxinhe-oa-cli report --due --json\nxinhe-oa-cli report --save --rule work_daily --content '{\"done\":\"走访 3 家\"}' --json\nxinhe-oa-cli report --submit --rule work_daily --content '{\"done\":\"走访 3 家\",\"plan\":\"回访\"}' --json\n\n# 历史补交/查询仍可显式指定周期\nxinhe-oa-cli report --submit --rule work_daily --period D:2026-10-27 --content '{\"done\":\"补交\"}' --json\n\n# ④ 上级 / 企业负责人:看汇总与未交\nxinhe-oa-cli report --list --team --period D:2026-10-28 --json # 我的汇报子树(含间接下属)\nxinhe-oa-cli report --stats --rule work_daily --include-sub --json\nxinhe-oa-cli report --confirm <report_id> --yes --json # 也可用 approval --approve(同一底座)\n```\n\n**四条必须知道的口径**:\n\n| 口径 | 说明 |\n|---|---|\n| **谁要交** | `--fillers` 支持 `depts`(按部门,**只认主部门**)/ `roles` / `users`。**空 `fillers` = 谁都不用交**,这是最常见的\"日报坏了\" |\n| **汇报给谁** | `--recipients` 是**解析器**(`direct_leader` / `dept_leader` / `all_leaders` / `named_user` / …),不写死人名 —— 调岗后自动跟着变;都没配到就落到**企业负责人** |\n| **谁能看** | 默认 `participants`(作者 + 汇报对象 + 抄送),**同事互相看不到**;上级与**企业负责人**默认看得到(负责人每次读都留审计)。要全公司可见必须显式改 `visibility` |\n| **提交后不可撤回** | 要改只能 `report --reopen <id> --note <原因> --yes`(管理员操作,留痕) |\n\n> 应填成员和未交状态按规则实时计算;`report_tick` 只负责可选催办,故障不会阻断提交。\n> 补交默认不设时间上限,提交状态会记为 `late`;审计保留实际提交时间。\n> 建规则时若返回 `period.assignments_created: 0`,说明 `fillers` 一个人都没展开出来 —— 先回去改它。\n\n---\n\n## 9. 组织架构与汇报关系(`org` 域)\n\n> **汇报线只存一处**:组织主数据上的两个字段(直属上级、部门负责人)。\n> 报告本身**不存**\"我上级是谁\" —— 改一处,全局实时生效。\n> 读只要登录;**写要 `admin:member`(企业负责人)且必须自然人**。\n\n```bash\n# ① 看部门树与成员归属\nxinhe-oa-cli org --tree --json # 树(含 depth / path / 人数)\nxinhe-oa-cli org --show <dept_id> --json # 单个部门详情\nxinhe-oa-cli org --explain-scope --user <uid> --json # 他为什么看得到/看不到(声明+锚点+子树规模)\n\n# ② 建部门 / 改名 / 移动 / 归档(写,要 --yes 的是移动与归档)\nxinhe-oa-cli org --create --name 销售部 --key sales --json\nxinhe-oa-cli org --create --name 华东区 --parent <dept_id> --json\nxinhe-oa-cli org --rename <dept_id> --name 销售中心 --json\nxinhe-oa-cli org --move <dept_id> --parent <new_parent_id> --yes --json\nxinhe-oa-cli org --leader-set <dept_id> --user <uid> --json # 部门负责人\n\n# ③ 把人挂进部门 / 设直属上级(汇报关系的两个字段就在这里)\nxinhe-oa-cli member --set-dept <dept_id> --user <uid> --primary --json # 首个部门自动为主部门\nxinhe-oa-cli member --set-manager <上级uid> --user <uid> --json\nxinhe-oa-cli member --clear-manager --user <uid> --json\nxinhe-oa-cli member --search --dept <dept_id> --json # 按部门看人(精确,不含下级;分页)\n```\n\n> **找人只用 `member --search`**(MEMBER-001 / issue #99):按 `--name`(子串,2..=32 字)/\n> `--phone`(**精确 11 位**,不做前缀 / 号段 / 模糊)/ `--dept` 三选一或组合,\n> 范围永远是\"我能管的企业\"。同名多人会**全部返回**,靠每行的 `phone_masked`(脱敏号)+\n> `departments` 区分;`duplicate_name=true` 时用 `--dept` 或 `--phone` 收窄。\n> 跨企业的号码与\"不存在的号码\"**响应完全一样**(问不出\"这个号在不在平台上\")。\n> **别用 `member --list` 拉全量再本地筛**:服务端上限 500 条(`truncated: true` = 可能被截断),\n> 且 `--list` 上带 `--name` / `--phone` / `--dept` 现在是**用法错误(退出码 2,不发请求)**。\n\n**六条必须知道的口径**:\n\n| 口径 | 说明 |\n|---|---|\n| **一人多部门只认主部门** | 上级链、按部门展开的填写范围、行级部门归属**都锚定主部门**;兼任部门不上链、不参与数据范围 |\n| **`--set-manager` 落在主部门行** | 换主部门后要重新确认上级;服务端校验同企业、不放成环 |\n| **负责人 ≠ 权限** | 部门负责人可以是外部人员,**不因此获得任何数据面权限**(职位与权限解耦);他读得到下级的日报,但要读下级的**表**仍需单独授权 |\n| **写要自然人** | `org` 的写操作与 `/admin/*` 同口径:AI 代跑令牌一律拒 |\n| **移动 = 重算继承** | 整棵子树换父,权限**不保留**(继承只能更严);归档要求子树为空且无在职成员 |\n| **`--key` 企业内唯一且不可改** | `--name` 才是显示名 |\n\n> **汇报关系不需要在报告里配**:建规则时 `--recipients` 用解析器\n> (`direct_leader` / `dept_leader` / `all_leaders` / `dept_leader_chain`),\n> 解析发生在开窗与提交时 —— 谁调岗、谁换上级,下个周期自动跟着变。\n\n---\n\n## 10. 退出码速查\n\n| 码 | 含义 | 你该做什么 |\n|---|---|---|\n| `0` | 成功 | — |\n| `2` | 用法错误(参数拼错/缺必填) | 看 `--help`;本地就拦下了,没发请求 |\n| `3` | 未登录 / 凭据失效 | 按提示 `login --phone <号码>` |\n| `4` | 权限不足 | 看 `details.missing_scopes`,去找人要权限(§6) |\n| `5` | 冲突 / 幂等 / 乐观锁 | 数据被改过或重复提交,重新读一次再改 |\n| `6` | 服务端错误 | 稍后重试;持续失败找运维 |\n| `7` | 网络不可达 | `doctor --json` 看网络段;内网直连时 `config --set proxy=none` |\n| `8` | CLI 版本过旧 | 自更新:`xinhe-oa-cli doctor --upgrade`(见 §9.1),完了重开终端再重试刚才那条命令 |\n\n---\n\n## 11. 出问题时先跑这个\n\n```bash\nxinhe-oa-cli doctor --json\n```\n\n它会检查:版本、配置目录与权限、凭据是否存在/是否过期、设备指纹、**连通性与代理判定**、\n磁盘、内嵌技能数量,并给出 `next_steps`。**报错前先跑它**,多数问题它能直接说清。\n\n### 11.1 版本旧了怎么升(自更新,别去手工下二进制)\n\n```bash\nxinhe-oa-cli doctor --upgrade --dry-run --json # 预演:只问一次 /cli/latest,不下载、不替换、不写任何文件\nxinhe-oa-cli doctor --upgrade --json # 问服务端发布了什么 → 下载 → 校验 → 原地替换自己\nxinhe-oa-cli doctor --upgrade --out /新位置/xinhe-oa-cli # 只要产物,不动当前这个文件\n```\n\n- **`--dry-run` 是预演,不是\"轻量地升一次\"**:它止步于只读的 `/cli/latest`(报告里\n `data.upgrade.dry_run=true`),**一个字节都不写** —— 别拿它当\"先试试\",\n 真要升就去掉它。看到 `dry_run=true` 就说明本次**没有**发生任何替换。\n- **`--out <路径>` 与\"本机是不是最新\"无关**:它要的是\"服务端当前发布的那一份产物\"\n (装到别的机器 / 排障 / 核对 sha256),所以本机恰好是最新时也照样下载落盘;\n `data.upgrade.target=\"out\"`、`current_binary_touched=false` 表示**本体没被动过**。\n- **用你已有的登录凭据下载**(`/cli/download/<os>/<arch>` 要令牌):所以**没登录升不了**,\n 会回 `auth.missing_token` —— 先按 §1 登录。\n- **先校验 sha256 再落盘**:对不上就一个字节都不换,当前版本照常可用。\n- ⚠️ **退出码 0 不代表升级成功**:`doctor` 是自检,升级失败只写在报告里。\n 要判\"升没升\"看 `data.upgrade.replaced` / `data.upgrade.to` / `data.upgrade.current_binary_touched`,或直接 `xinhe-oa-cli --version`。\n- 升完**重开终端**(或重新执行命令)用的才是新版本;旧文件留在同名 `.old`,下次启动自动清理。\n- 服务端没发你这个平台的产物 → `cli.artifact_missing`(`error.details` 里列了有哪些平台)。\n 这种重试没用,报给运维(例:macOS 产物本期未发布)。\n- **旧到读不到版本清单时**:如果 `doctor --upgrade` 自己就回了 `cli.too_old`(版本门禁只在\n `/cli/latest` 上),说明这个版本已经旧到没法和新服务端协商 —— 此时按\n `error.details.next_step` 用安装脚本重装一遍,之后日常升级再用 `doctor --upgrade`。\n- 什么时候该升:退出码 `8`、`doctor` 提示版本旧、或服务端 `notes` 里有你要用的新能力。\n\n---\n\n## 12. 红线(会被审计)\n\n- 不要试图绕过命令面(没有裸调、没有 SQL);\n- 不要把令牌写进任何文件、日志、提示词或命令行参数;\n- 不要用\"反复试\"来探测别人的数据 —— 越权探测会被记录在审计里;\n- 高风险写操作(`record --delete`、`application --copy`、`workflow --run`、`event --replay`)\n 必须显式加 `--yes`,那是给人留的确认点,**不要自动加**。\n\n> 需要平台级操作(建企业、任命企业负责人、跨企业查看、审计检索)→ 见 `xo-admin`(仅 super_admin)。\n"
|
|
15
|
+
"bytes": 24851,
|
|
16
|
+
"content": "# xo-core · xinhe-oa-cli 使用说明(所有角色)\n\n**这个技能解决什么**:告诉你这台机器上唯一的客户端 `xinhe-oa-cli` 怎么用 ——\n怎么登录、怎么看自己的权限、怎么建多维表格、怎么把文件里的数据传上去、怎么装别的技能。\n**读完这一份,日常的活你就能干了。**\n\n> 本技能对**所有已登录用户**可用。平台层面的操作(建企业、任命企业负责人、跨企业查看)\n> 在 `xo-admin` 里,只有 super_admin 拿得到。\n\n---\n\n## 0. 三条铁律(先读这个)\n\n1. **只有命令面**:一切操作都通过 `xinhe-oa-cli` 的子命令。**没有**裸调接口、**没有** SQL 逃生口、\n 没有任意路径 —— 想干的事如果命令面里没有,就是平台不提供,不要试图绕。\n2. **令牌你看不到**:登录后凭据由 CLI 自己维护(`~/.config/xinhe-oa-cli/credentials.json`,0600)。\n 不要试图读它、不要打印它、不要写进任何文件或提示词。\n3. **权限跟着令牌走**:你能看到什么,取决于你这个身份被授了什么。\n 被拒不是 bug —— 退出码 `4` 就是\"去要权限\"的信号(下面 §6 有怎么要)。\n\n---\n\n## 1. 第一次使用\n\n```bash\n# ① 指向后端(唯一必须人工提供的东西)\nxinhe-oa-cli config --set base_url=http://<后端地址>:8100\n\n# ② 登录(两步:发码 → 校验)\nxinhe-oa-cli login --phone <手机号> # 返回里有 dev_code(开发模式回显)\nxinhe-oa-cli login --phone <手机号> --code <验证码> # 成功后凭据落盘,之后不用再登\n\n# ③ 我是谁、能做什么、缺什么\nxinhe-oa-cli me --json\n\n# ④ 装技能(把服务端发布的技能拉到本地,供你读)\nxinhe-oa-cli skills --install --dest ./skills\nxinhe-oa-cli skills --list # 看有哪些;内嵌的那份不需要联网\nxinhe-oa-cli skills --read xo-core # 直接读某一份\n```\n\n**access 令牌只活 10 分钟**,CLI 会自动续期。续不回来时(refresh 也过期)会返回**退出码 3**,\n提示里直接给出重新登录的命令 —— 照着做即可,不要反复重试。\n\n---\n\n## 2. 我是谁、我有什么权限\n\n```bash\nxinhe-oa-cli me --json\n```\n\n返回里要紧的几块:\n\n| 字段 | 含义 |\n|---|---|\n| `user.roles` | 我的角色(`super_admin` / `tenant_owner` / `tenant_member` / `investor`) |\n| `scope.tenants` | 我能看到哪些企业 |\n| `scope.stores` | 我能看到哪些门店 |\n| `capabilities` | 我能做哪些动作(决定命令面里哪些能用) |\n\n**看不到的东西不会告诉你\"存在\"** —— 这是刻意的(避免用报错探测别人的数据)。\n\n---\n\n## 3. 多维表格(主命令面)\n\n企业自建的多维表格承担\"数据库\"的角色。层次是:**库(base) → 表(table) → 字段(field) → 记录(record)**,\n外加**视图(view)** 与**表单(form)** 两种使用界面。\n\n### 3.1 建结构\n\n```bash\nxinhe-oa-cli bitable base --create --name 财务数据 --key finance --json\nxinhe-oa-cli bitable table --create --base <base_id> --name 公账流水 --key bank_flow --json\nxinhe-oa-cli bitable field --create --table <table_id> --name 金额 --type currency --semantic-key amount --json\n```\n\n> `--semantic-key` 是**机器名**(建后不可变),`--name` 是显示名(可改)。\n> 下游脚本与 AI 认语义键,所以**改名不会打断任何东西**。\n\n字段类型(`--type`):`text` `number` `currency` `date` `single_select` `multi_select` `boolean` `person` `attachment_ref` `relation`。\n\n### 3.2 写与读\n\n```bash\n# 写一条\nxinhe-oa-cli bitable record --create --table <table_id> --set 金额=12000 --set 摘要=\"诊疗收入\" --json\n\n# 读:默认只取一页;--all 自动翻完;要自己翻页用 --limit + --offset\n# (返回里有 next_offset / has_more:下一把直接拿 next_offset 当 --offset)\n# 排序不用自己拼\"打破并列\"的键:CLI 会自动补唯一的 id(返回里的 order 就是实际发出去的那份)。\n# --all 翻完会自检:有重复行 / 与 total 对不上 → 退出码 5、cli.paging_unstable\n# (多半是翻页期间有人在写这张表,重跑一次即可;要一致的快照用视图的游标分页)\nxinhe-oa-cli bitable record --list --table <table_id> --all --json\nxinhe-oa-cli bitable record --list --table <table_id> --limit 1000 --offset 1000 --json\n\n# 按字段筛行(例:按日期区间取数,1 万行也一样):过滤在**客户端**做(表取数接口没有过滤参数,\n# --all 先翻完再筛),字段写 **semantic_key**(`bitable field --list` 里的机器名 —— 写显示名会**静默**筛出 0 条);\n# 日期字段的值是 ISO UTC(`2026-01-31T00:00:00+00:00`)、比较按**字典序**,所以区间一律写**半开**:\n# 起点 gte + 次日 lt(写成 lte:2026-01-31 会漏掉 1/31 当天)。\nxinhe-oa-cli bitable record --list --table <table_id> \\\n --all --limit 1000 --workers 4 \\\n --filter 'visit_date:gte:2026-01-01' --filter 'visit_date:lt:2026-02-01' --json\n\n# 改 / 删\nxinhe-oa-cli bitable record --update <record_id> --set 摘要=\"已核对\" --json\nxinhe-oa-cli bitable record --delete <record_id> --json # 高风险:要 --yes\n```\n\n### 3.3 视图与表单\n\n```bash\nxinhe-oa-cli bitable view --create --table <table_id> --name 收入视图 --json\nxinhe-oa-cli bitable view --query --view <view_id> --all --json\n# 按字段筛行走**服务端**(只回命中行、游标分页不重不漏;字段用 semantic_key)\nxinhe-oa-cli bitable view --query --view <view_id> \\\n --filter 'visit_date:gte:2026-01-01' --filter 'visit_date:lt:2026-02-01' \\\n --page-size 1000 --all --json\nxinhe-oa-cli bitable view --aggregate --view <view_id> --measure 金额:sum --group-by 科室 --json\nxinhe-oa-cli bitable form --submit --form <form_id> --set 摘要=\"报销\" --json\n```\n\n> **视图取数是\"服务端过滤 + 游标分页\"的快路径**(0.1.5 起):过滤下推到服务端(写法与 §3.2 相同,\n> 字段同样是 semantic_key),只要命中行;`--all` 撞上 200 页上限会**告警**并给\n> `truncated: true` + `next_cursor`,拿它 `--cursor` 接着取(不会假装翻完了)。\n> ⚠️ **0.1.4 及更早有缺陷**:CLI 读 `data.records`、服务端回 `data.items`,`view --query`\n> 与 `record --list --view` 恒返回空行且 `ok:true` —— 那个版本请改用 §3.2 的 `record --list`。\n\n### 3.4 表默认是**私有**的:看不见 ≠ 不存在\n\n> **新建的库与表默认 `private`** —— **只有建它的人看得到**。别人(含同部门同事)\n> 既读不到、列表里也不会出现,只以 `hidden_count` 计数。\n> **别把\"查不到\"当成\"表不存在\"**:先 `--visibility --show` 看档位与分享。\n\n```bash\n# 看档位(回的是**声明档位** + **夹紧后的有效档位**)\nxinhe-oa-cli bitable table --visibility <table_id> --show --json\n# → {visibility, effective_visibility, clamped_by, owner_user_id, readable_by_me, ...}\n\n# 改档位(高风险:要 --yes):private | dept | internal\nxinhe-oa-cli bitable table --visibility <table_id> --set internal --yes --json\nxinhe-oa-cli bitable table --visibility <table_id> --set dept --dept <ou_id> --yes --json\nxinhe-oa-cli bitable table --move <table_id> --to-dept <ou_id> --yes --json # 换归属=重算继承\n\n# 分享:**默认只给读**;要别人能改,必须显式 --acl-action write(默认 90 天到期)\nxinhe-oa-cli bitable table --share <table_id> --to-user <uid> --json\nxinhe-oa-cli bitable table --share <table_id> --to-user <uid> --acl-action write --json\nxinhe-oa-cli bitable table --share <table_id> --to-dept <ou_id> --json\nxinhe-oa-cli bitable table --acl-list <table_id> --json\nxinhe-oa-cli bitable table --acl-revoke <table_id> --from-user <uid> --acl-action read --json\n\n# 库侧同一组命令(`bitable base --visibility|--share|--acl-list|--move …`)\n# 投资人逐表放行(TABLE-002;高风险)\nxinhe-oa-cli bitable table --investor-visibility <table_id> --investor-set open --yes --json\n```\n\n> **分享表之前先分享库**:库的档位是表的**上限** —— 库没给出去,表分享了也读不到\n> (`--visibility --show` 的 `clamped_by` 会告诉你是被哪个库夹住的)。\n\n| 口径 | 说明 |\n|---|---|\n| **默认私有** | 新库 / 新表都是 `private`;存量表由迁移标成 `internal`(升级前的等效语义) |\n| **库是表的上限** | 库 `private` ⇒ 库里 `internal` 的表**实际按 private 判**(`effective_visibility` 会告诉你被谁夹住) |\n| **分享默认读** | 不给 `--write` 就只能看;**只被分享了 read 的人改不动记录**(403,不是 404) |\n| **写分享有到期** | `--write` 默认 90 天,**不给永久**(与文件域同口径) |\n| **`public` 不可写** | 本期没有对外分享入口,写入直接 400 |\n| **企业负责人看得到全部** | 但每次**纯治理读**都留一条 `object.admin_read` 审计 |\n| **投资人默认看不到** | 企业要逐表 `--investor-visibility --set open` 才可见(TABLE-002) |\n\n> **别用大范围档位图省事**:`--set internal` 等于\"全企业可读可写(模板自带 write)\"。\n> 只让某几个人看,用 `--share`(先 `--share` 库、再 `--share` 表 —— 库是上限)。\n\n---\n\n## 4. 把文件里的数据传上去(最常用的一招)\n\n**解析在客户端完成**,平台只存结果 —— 所以你不需要先把 Excel 转成别的格式。\n\n```bash\nxinhe-oa-cli bitable table --import \\\n --file ./公账流水.csv \\\n --base <base_id> \\\n --name 公账流水 --json\n```\n\n它会:读表头 → 推断每列类型(金额带千分位、日期、百分比都能认)→ 建表 → 建字段 → **分批写记录**(默认每批 200 条)。\n`.csv` 与 `.xlsx` 都支持(xlsx 默认读第一张 sheet,可用 `--sheet <名字>` 指定)。\n\n**大文件(几千行以上)加三个参数**(2026-09-29 起支持:实测 5,000 行从 5 分钟降到 33 秒):\n\n```bash\nxinhe-oa-cli bitable table --import --file ./大表.csv --base <base_id> --name 大表 \\\n --batch-size 500 --workers 4 --json\n```\n\n- `--batch-size 500`:每批最多 500 行(服务端 `row_ops_batch_max` 也是 500;宽表会按请求体\n 大小自动再切小)。批越大,每批那点固定开销摊得越薄;\n- `--workers 4`:并发发批(默认 3,上限 16)。服务端喊\"慢一点\"(429/5xx)时它会自己降并发;\n- **中途断了直接重跑同一条命令**:进度落在 `<配置目录>/checkpoints/`,只补剩余批次,\n 不会重复写入(`--fresh` 才是从头来)。断了的那次会明确告诉你\"已写入 N 行、怎么续\"。\n\n导入过程每批往 stderr 打一行进度(`batch 12/29 · 2400/5611 行 · elapsed 132s · 18.2 行/秒`),\n不想看加全局 `--quiet`(注意:告警不会被 `--quiet` 吞掉)。\n\n返回里给你 `table_id` / `records_created` / `rows_in_file` —— **对一下这两个数**,\n不一致就说明有行没读进来(空行会被跳过,`skipped_empty_rows` 会告诉你跳了多少)。\n\n想手工指定某列类型,用 `--type 列名=类型`(可重复)。想把原始文件也留档,加 `--keep-source`。\n\n---\n\n## 5. 附件\n\n附件是**独立资源**:上传拿到 `attachment_id`,再显式挂到某条记录的附件字段上。\n\n```bash\nxinhe-oa-cli attachment --upload --file ./单据.pdf --tenant <企业id> --name 单据.pdf --json\nxinhe-oa-cli attachment --link --id <attachment_id> --resource-type record --resource-id <record_id> --field <field_id> --json\nxinhe-oa-cli attachment --download <attachment_id> --out ./下载.pdf --json\n```\n\n---\n\n## 6. 被拒绝怎么办(退出码 4)\n\nCLI 会把服务端的原话透出来,`error.details.missing_scopes` 里就是**你缺的那条权限**:\n\n```json\n{\"code\": \"authz.missing_scope\",\n \"details\": {\"missing_scopes\": [\"data:kashu:*:*:write\"]},\n \"hint\": \"响应 details.missing_scopes 里是缺的 scope,拿它去找 super_admin 走 POST /admin/grants 授权\"}\n```\n\n**正确做法**:拿着这条 scope 去找**企业负责人**(本企业的事)或 **super_admin**(平台的事)要权限,\n而不是换个命令反复试、更不是试图绕。反复越权探测本身会被审计记录。\n\n---\n\n## 7. 企业负责人:管理本企业的人\n\n> 只有 `tenant_owner` 能做。其余角色做这些会得到退出码 `4`。\n\n```bash\n# 给本企业加人并赋角(一步完成;不写 --tenant 就是你自己那家企业)\nxinhe-oa-cli member --create --phone 13900000003 --name 小唐 --role tenant_member --json\n\n# 看本企业有哪些人、各自什么角色\nxinhe-oa-cli member --list --json\nxinhe-oa-cli member --role <user_id> --json\n\n# 撤角(撤权会同时吊销那个人的会话链)\nxinhe-oa-cli member --revoke-role <user_id> --reason \"离职\" --json\n```\n\n**角色怎么选**:\n\n| 角色 | 能干什么 | 用在哪 |\n|---|---|---|\n| `tenant_member` | 本企业**读 / 写 / 建表**(传数据的人) | 业务员、财务 |\n| `tenant_operator` | 只能读写**被点名的那几张表**(可限门店) | 只看某块数据的人 |\n| `tenant_owner` | 本企业**全量** + 管人(建人/赋角) | 企业负责人 |\n\n### 让投资人能看本企业的数据\n\n投资人的可见性是**两道门**:平台声明(super_admin 赋 `investor` 角色)+ **企业背书**(你点头)。\n缺任何一道,他都看不到 —— 这是刻意的:**能不能看你的数,由你说了算**。\n\n```bash\nxinhe-oa-cli investor --list --json # 看谁在等你确认\nxinhe-oa-cli investor --endorse --user <user_id> --reason \"投资人可查看全部数据\" --json\nxinhe-oa-cli investor --revoke --user <user_id> --reason \"合作结束\" --json # 随时收回\n```\n\n---\n\n## 8. 日报 / 周报(汇报)\n\n> 规则是**配置面**(谁写给谁看),`report_instance` 是**日报主数据**。\n> 当前周期按日期实时计算:员工直接提交即可,**不依赖服务端定时任务开窗**。\n> 没有 active 规则时 `report --due` 才会为空。\n> 日报默认允许无限期补交:历史日期显式传 `--period` 即可;未来周期仍禁止提交。\n> `template=[]` 表示**自由格式**:content 可以是任意有界 JSON 对象,不需要企业负责人先补模板。\n\n```bash\n# ① 建规则(企业负责人;--fillers 是**谁要交**,不给就谁都不用交)\nxinhe-oa-cli report rule --create --key work_daily --name 工作日报 --cadence daily \\\n --template '[{\"key\":\"done\",\"label\":\"今天做了什么\",\"type\":\"text\",\"required\":true},\n {\"key\":\"plan\",\"label\":\"明天计划\",\"type\":\"text\",\"required\":true}]' \\\n --fillers '{\"roles\":[\"tenant_member\",\"tenant_operator\"],\"only_employed\":true}' \\\n --recipients '[{\"type\":\"direct_leader\"},{\"type\":\"dept_leader\"}]' --json\n\n# 返回里的 period.period_key(如 D:2026-10-28)可用于显式指定当前周期;\n# 日常提交不需要它,服务端会按规则和当前日期自动计算。\n\n# ② 预演(不落库):谁要填 / 谁收到 / 谁没解析到 —— 配错了在这里就看得见\nxinhe-oa-cli report rule --preview --rule work_daily --json\n\n# ③ 员工:看该填什么 → 直接存草稿 / 提交(不再要求 --period)\nxinhe-oa-cli report --due --json\nxinhe-oa-cli report --save --rule work_daily --content '{\"done\":\"走访 3 家\"}' --json\nxinhe-oa-cli report --submit --rule work_daily --content '{\"done\":\"走访 3 家\",\"plan\":\"回访\"}' --json\n\n# 历史补交/查询仍可显式指定周期\nxinhe-oa-cli report --submit --rule work_daily --period D:2026-10-27 --content '{\"done\":\"补交\"}' --json\n\n# ④ 上级 / 企业负责人:看汇总与未交\nxinhe-oa-cli report --list --team --period D:2026-10-28 --json # 我的汇报子树(含间接下属)\nxinhe-oa-cli report --stats --rule work_daily --include-sub --json\nxinhe-oa-cli report --confirm <report_id> --yes --json # 也可用 approval --approve(同一底座)\n```\n\n**四条必须知道的口径**:\n\n| 口径 | 说明 |\n|---|---|\n| **谁要交** | `--fillers` 支持 `depts`(按部门,**只认主部门**)/ `roles` / `users`。**空 `fillers` = 谁都不用交**,这是最常见的\"日报坏了\" |\n| **汇报给谁** | `--recipients` 是**解析器**(`direct_leader` / `dept_leader` / `all_leaders` / `named_user` / …),不写死人名 —— 调岗后自动跟着变;都没配到就落到**企业负责人** |\n| **谁能看** | 默认 `participants`(作者 + 汇报对象 + 抄送),**同事互相看不到**;上级与**企业负责人**默认看得到(负责人每次读都留审计)。要全公司可见必须显式改 `visibility` |\n| **提交后不可撤回** | 要改只能 `report --reopen <id> --note <原因> --yes`(管理员操作,留痕) |\n\n> 应填成员和未交状态按规则实时计算;`report_tick` 只负责可选催办,故障不会阻断提交。\n> 补交默认不设时间上限,提交状态会记为 `late`;审计保留实际提交时间。\n> 自由格式仍限制顶层字段数、键长、内容大小和 JSON 嵌套深度。\n> 建规则时若返回 `period.assignments_created: 0`,说明 `fillers` 一个人都没展开出来 —— 先回去改它。\n\n---\n\n## 9. 组织架构与汇报关系(`org` 域)\n\n> **汇报线只存一处**:组织主数据上的两个字段(直属上级、部门负责人)。\n> 报告本身**不存**\"我上级是谁\" —— 改一处,全局实时生效。\n> 读只要登录;**写要 `admin:member`(企业负责人)且必须自然人**。\n\n```bash\n# ① 看部门树与成员归属\nxinhe-oa-cli org --tree --json # 树(含 depth / path / 人数)\nxinhe-oa-cli org --show <dept_id> --json # 单个部门详情\nxinhe-oa-cli org --explain-scope --user <uid> --json # 他为什么看得到/看不到(声明+锚点+子树规模)\n\n# ② 建部门 / 改名 / 移动 / 归档(写,要 --yes 的是移动与归档)\nxinhe-oa-cli org --create --name 销售部 --key sales --json\nxinhe-oa-cli org --create --name 华东区 --parent <dept_id> --json\nxinhe-oa-cli org --rename <dept_id> --name 销售中心 --json\nxinhe-oa-cli org --move <dept_id> --parent <new_parent_id> --yes --json\nxinhe-oa-cli org --leader-set <dept_id> --user <uid> --json # 部门负责人\n\n# ③ 把人挂进部门 / 设直属上级(汇报关系的两个字段就在这里)\nxinhe-oa-cli member --set-dept <dept_id> --user <uid> --primary --json # 首个部门自动为主部门\nxinhe-oa-cli member --set-manager <上级uid> --user <uid> --json\nxinhe-oa-cli member --clear-manager --user <uid> --json\nxinhe-oa-cli member --search --dept <dept_id> --json # 按部门看人(精确,不含下级;分页)\n```\n\n> **找人只用 `member --search`**(MEMBER-001 / issue #99):按 `--name`(子串,2..=32 字)/\n> `--phone`(**精确 11 位**,不做前缀 / 号段 / 模糊)/ `--dept` 三选一或组合,\n> 范围永远是\"我能管的企业\"。同名多人会**全部返回**,靠每行的 `phone_masked`(脱敏号)+\n> `departments` 区分;`duplicate_name=true` 时用 `--dept` 或 `--phone` 收窄。\n> 跨企业的号码与\"不存在的号码\"**响应完全一样**(问不出\"这个号在不在平台上\")。\n> **别用 `member --list` 拉全量再本地筛**:服务端上限 500 条(`truncated: true` = 可能被截断),\n> 且 `--list` 上带 `--name` / `--phone` / `--dept` 现在是**用法错误(退出码 2,不发请求)**。\n\n**六条必须知道的口径**:\n\n| 口径 | 说明 |\n|---|---|\n| **一人多部门只认主部门** | 上级链、按部门展开的填写范围、行级部门归属**都锚定主部门**;兼任部门不上链、不参与数据范围 |\n| **`--set-manager` 落在主部门行** | 换主部门后要重新确认上级;服务端校验同企业、不放成环 |\n| **负责人 ≠ 权限** | 部门负责人可以是外部人员,**不因此获得任何数据面权限**(职位与权限解耦);他读得到下级的日报,但要读下级的**表**仍需单独授权 |\n| **写要自然人** | `org` 的写操作与 `/admin/*` 同口径:AI 代跑令牌一律拒 |\n| **移动 = 重算继承** | 整棵子树换父,权限**不保留**(继承只能更严);归档要求子树为空且无在职成员 |\n| **`--key` 企业内唯一且不可改** | `--name` 才是显示名 |\n\n> **汇报关系不需要在报告里配**:建规则时 `--recipients` 用解析器\n> (`direct_leader` / `dept_leader` / `all_leaders` / `dept_leader_chain`),\n> 解析发生在开窗与提交时 —— 谁调岗、谁换上级,下个周期自动跟着变。\n\n---\n\n## 10. 退出码速查\n\n| 码 | 含义 | 你该做什么 |\n|---|---|---|\n| `0` | 成功 | — |\n| `2` | 用法错误(参数拼错/缺必填) | 看 `--help`;本地就拦下了,没发请求 |\n| `3` | 未登录 / 凭据失效 | 按提示 `login --phone <号码>` |\n| `4` | 权限不足 | 看 `details.missing_scopes`,去找人要权限(§6) |\n| `5` | 冲突 / 幂等 / 乐观锁 | 数据被改过或重复提交,重新读一次再改 |\n| `6` | 服务端错误 | 稍后重试;持续失败找运维 |\n| `7` | 网络不可达 | `doctor --json` 看网络段;内网直连时 `config --set proxy=none` |\n| `8` | CLI 版本过旧 | 自更新:`xinhe-oa-cli doctor --upgrade`(见 §9.1),完了重开终端再重试刚才那条命令 |\n\n---\n\n## 11. 出问题时先跑这个\n\n```bash\nxinhe-oa-cli doctor --json\n```\n\n它会检查:版本、配置目录与权限、凭据是否存在/是否过期、设备指纹、**连通性与代理判定**、\n磁盘、内嵌技能数量,并给出 `next_steps`。**报错前先跑它**,多数问题它能直接说清。\n\n### 11.1 版本旧了怎么升(自更新,别去手工下二进制)\n\n```bash\nxinhe-oa-cli doctor --upgrade --dry-run --json # 预演:只问一次 /cli/latest,不下载、不替换、不写任何文件\nxinhe-oa-cli doctor --upgrade --json # 问服务端发布了什么 → 下载 → 校验 → 原地替换自己\nxinhe-oa-cli doctor --upgrade --out /新位置/xinhe-oa-cli # 只要产物,不动当前这个文件\n```\n\n- **`--dry-run` 是预演,不是\"轻量地升一次\"**:它止步于只读的 `/cli/latest`(报告里\n `data.upgrade.dry_run=true`),**一个字节都不写** —— 别拿它当\"先试试\",\n 真要升就去掉它。看到 `dry_run=true` 就说明本次**没有**发生任何替换。\n- **`--out <路径>` 与\"本机是不是最新\"无关**:它要的是\"服务端当前发布的那一份产物\"\n (装到别的机器 / 排障 / 核对 sha256),所以本机恰好是最新时也照样下载落盘;\n `data.upgrade.target=\"out\"`、`current_binary_touched=false` 表示**本体没被动过**。\n- **用你已有的登录凭据下载**(`/cli/download/<os>/<arch>` 要令牌):所以**没登录升不了**,\n 会回 `auth.missing_token` —— 先按 §1 登录。\n- **先校验 sha256 再落盘**:对不上就一个字节都不换,当前版本照常可用。\n- ⚠️ **退出码 0 不代表升级成功**:`doctor` 是自检,升级失败只写在报告里。\n 要判\"升没升\"看 `data.upgrade.replaced` / `data.upgrade.to` / `data.upgrade.current_binary_touched`,或直接 `xinhe-oa-cli --version`。\n- 升完**重开终端**(或重新执行命令)用的才是新版本;旧文件留在同名 `.old`,下次启动自动清理。\n- 服务端没发你这个平台的产物 → `cli.artifact_missing`(`error.details` 里列了有哪些平台)。\n 这种重试没用,报给运维(例:macOS 产物本期未发布)。\n- **旧到读不到版本清单时**:如果 `doctor --upgrade` 自己就回了 `cli.too_old`(版本门禁只在\n `/cli/latest` 上),说明这个版本已经旧到没法和新服务端协商 —— 此时按\n `error.details.next_step` 用安装脚本重装一遍,之后日常升级再用 `doctor --upgrade`。\n- 什么时候该升:退出码 `8`、`doctor` 提示版本旧、或服务端 `notes` 里有你要用的新能力。\n\n---\n\n## 12. 红线(会被审计)\n\n- 不要试图绕过命令面(没有裸调、没有 SQL);\n- 不要把令牌写进任何文件、日志、提示词或命令行参数;\n- 不要用\"反复试\"来探测别人的数据 —— 越权探测会被记录在审计里;\n- 高风险写操作(`record --delete`、`application --copy`、`workflow --run`、`event --replay`)\n 必须显式加 `--yes`,那是给人留的确认点,**不要自动加**。\n\n> 需要平台级操作(建企业、任命企业负责人、跨企业查看、审计检索)→ 见 `xo-admin`(仅 super_admin)。\n"
|
|
17
17
|
}
|
|
18
18
|
]
|
|
19
19
|
}
|
package/src/version.js
CHANGED