@linkaicli/linkai-cli 0.0.0-stage → 1.0.17

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.
Files changed (88) hide show
  1. package/AGENTS.md +64 -0
  2. package/GUIDE.md +150 -0
  3. package/LICENSE +190 -0
  4. package/NOTICE +6 -0
  5. package/README.md +82 -2
  6. package/THIRD_PARTY_NOTICES.txt +318 -0
  7. package/cli-manifest.json +1850 -0
  8. package/clis/_shared/common.js +1 -0
  9. package/clis/_shared/site-auth.js +1 -0
  10. package/clis/linkai/build.js +1 -0
  11. package/clis/linkai/card-rules.js +1 -0
  12. package/clis/linkai/collab.js +1 -0
  13. package/clis/linkai/commands.js +4 -0
  14. package/clis/linkai/core.js +12 -0
  15. package/clis/linkai/deliverable.js +2 -0
  16. package/clis/linkai/dictionary.js +1 -0
  17. package/clis/linkai/fill-card.js +1 -0
  18. package/clis/linkai/oauth.js +8 -0
  19. package/clis/linkai/reuse.js +1 -0
  20. package/clis/linkai/templates.js +1 -0
  21. package/dist/bundle/browser-cdp.mjs +2 -0
  22. package/dist/bundle/browser-page.mjs +2 -0
  23. package/dist/bundle/browser-utils.mjs +2 -0
  24. package/dist/bundle/core.mjs +3449 -0
  25. package/dist/bundle/daemon.mjs +16 -0
  26. package/dist/bundle/download-article.mjs +2 -0
  27. package/dist/bundle/download-media.mjs +2 -0
  28. package/dist/bundle/download-progress.mjs +2 -0
  29. package/dist/bundle/download.mjs +2 -0
  30. package/dist/bundle/errors.mjs +2 -0
  31. package/dist/bundle/logger.mjs +2 -0
  32. package/dist/bundle/main.mjs +3 -0
  33. package/dist/bundle/oplog.mjs +2 -0
  34. package/dist/bundle/pipeline.mjs +2 -0
  35. package/dist/bundle/registry.mjs +2 -0
  36. package/dist/bundle/skills.mjs +2 -0
  37. package/dist/bundle/types.mjs +2 -0
  38. package/dist/bundle/utils.mjs +2 -0
  39. package/dist/bundle/vendor/readability/Readability-readerable.js +122 -0
  40. package/dist/bundle/vendor/readability/Readability.js +2786 -0
  41. package/knowledge/README.md +29 -0
  42. package/knowledge/browser/README.md +40 -0
  43. package/knowledge/data-dictionary/README.md +35 -0
  44. package/knowledge/data-dictionary/_templates/enum.md +7 -0
  45. package/knowledge/data-dictionary/_templates/metric.md +5 -0
  46. package/knowledge/data-dictionary/_templates/view.md +29 -0
  47. package/knowledge/data-dictionary/enums/.gitkeep +1 -0
  48. package/knowledge/data-dictionary/index.md +12 -0
  49. package/knowledge/data-dictionary/metrics/.gitkeep +1 -0
  50. package/knowledge/data-dictionary/views/.gitkeep +1 -0
  51. package/knowledge/report-builder/README.md +80 -0
  52. package/knowledge/report-builder/SOP.md +91 -0
  53. package/knowledge/report-builder/build-spec.md +79 -0
  54. package/knowledge/report-builder/references/api-catalog.md +106 -0
  55. package/knowledge/report-builder/references/api.md +148 -0
  56. package/knowledge/report-builder/references/auth-space.md +58 -0
  57. package/knowledge/report-builder/references/card.md +382 -0
  58. package/knowledge/report-builder/references/chart-config-schema.md +572 -0
  59. package/knowledge/report-builder/references/chart-types.md +167 -0
  60. package/knowledge/report-builder/references/dashboard-patterns.md +138 -0
  61. package/knowledge/report-builder/references/dataset.md +152 -0
  62. package/knowledge/report-builder/references/fill-import.md +95 -0
  63. package/knowledge/report-builder/references/filters.md +170 -0
  64. package/knowledge/report-builder/references/gantt.md +79 -0
  65. package/knowledge/report-builder/references/pitfalls.md +265 -0
  66. package/knowledge/report-builder/references/query-engine.md +177 -0
  67. package/knowledge/report-builder/references/report.md +249 -0
  68. package/llms.txt +37 -0
  69. package/package.json +105 -4
  70. package/skills/agent-prompt.md +37 -0
  71. package/skills/linkai-browser/SKILL.md +67 -0
  72. package/skills/linkai-collab/SKILL.md +65 -0
  73. package/skills/linkai-data-workflow/SKILL.md +49 -0
  74. package/skills/linkai-data-workflow/assets/import-run-template.json +22 -0
  75. package/skills/linkai-data-workflow/assets/solution-template.md +60 -0
  76. package/skills/linkai-data-workflow/references/excel-analysis-modeling.md +35 -0
  77. package/skills/linkai-data-workflow/references/fill-import.md +95 -0
  78. package/skills/linkai-data-workflow/references/solution-knowledge.md +35 -0
  79. package/skills/linkai-report-builder/SKILL.md +151 -0
  80. package/skills/linkai-usage/SKILL.md +66 -0
  81. package/templates/report-spec/cards/detail-table.json +12 -0
  82. package/templates/report-spec/filters.example.json +28 -0
  83. package/templates/report-spec/plan-knowledge.example.yaml +18 -0
  84. package/templates/report-spec/report.example.yaml +123 -0
  85. package/templates/report-spec/sql/channel.sql +7 -0
  86. package/templates/report-spec/sql/detail.sql +10 -0
  87. package/templates/report-spec/sql/kpi.sql +8 -0
  88. package/templates/report-spec/sql/trend.sql +9 -0
@@ -0,0 +1,177 @@
1
+ # 查询引擎:卡片的数是怎么查出来的
2
+
3
+ 读这篇的时机:要判断「这个配置会不会生效」「为什么数不对/重复/被合并/报 GROUP BY 错」,
4
+ 或者想在卡片层做聚合、排序、过滤之前。标 **【实测】** 的经过线上验证,
5
+ 标 **【确认】** 的规则已确认、但没在线上跑过——用之前先拿一张 `zz-` 卡片验证。
6
+
7
+ ## 1. 请求长什么样
8
+
9
+ `POST /page-setting/get-new-index-block-data`(编辑器预览用 `/index-block/get-new-index-block-data`):
10
+
11
+ ```jsonc
12
+ {
13
+ "usedIndexBlockId": "<卡片 id = indexBlock.id>",
14
+ "filters": [ /* 见 references/filters.md「查询时的过滤值」 */ ],
15
+ "page": {"pageIndex": 1, "pageSize": 20}, // 不分页时整个字段不传
16
+ "sortFields": [ /* 列头临时排序;不传则走卡片持久排序,见 §4 */ ],
17
+ "isFreeFill": false,
18
+ "dataSourceSql": "...", "dataSourceId": "...",
19
+ // agtable:开总计/行转列时附加 isTotal / pivotTableGroup / isPivotTable / pivotPageField
20
+ }
21
+ ```
22
+
23
+ 响应:`data` 是行数组(列名 = `waxisN`/`yaxisN`/…),`dataCount` 是总行数(分页时取首行的 `totalcount` 列),
24
+ `extendData.sql` 是**实际执行的完整 SQL**,`message` 在失败时是数据库原始报错。
25
+ **HTTP 200 + `code≠200` + `data=[]` 是查询失败的典型形态**,一定看 `message`。
26
+
27
+ 不开浏览器直接查一张卡片:
28
+
29
+ ```bash
30
+ linkai query --card <卡片id> [--page-size 条数] [--output rows.json] # → code / message / total / rows / columns / sql(行内容只写文件,不打印)
31
+ # 带筛选条件或自定义 body:
32
+ linkai api --method POST --path /page-setting/get-new-index-block-data --body body.json --raw
33
+ ```
34
+
35
+ ## 2. SQL 的三层结构
36
+
37
+ ```sql
38
+ /*租户=…,用户=…,卡片=…*/
39
+ SET @tenant_code = '<租户码>'; -- 仅数仓数据源(IsWarehouse && IsMultenant)且非 0000 租户
40
+ SELECT W.<列别名> AS waxis1, SUM(W.<列别名>) AS yaxis1, …
41
+ FROM (
42
+ SELECT <排序/隐藏辅助列>, WT.* -- 第二层:把数据集整体包成 WT,在这层做「维度过滤」
43
+ FROM ( <数据集 SQL,已做行权限改写> ) AS WT
44
+ WHERE WT.<列别名> IN (…) -- filterProperty=0 的过滤
45
+ ) AS W
46
+ WHERE / HAVING … -- filterProperty=1 的结果集过滤(按 waxisN 找回列)
47
+ GROUP BY … -- 见 §3
48
+ ORDER BY … -- 见 §4
49
+ LIMIT <pageSize> OFFSET … -- 有 page 时;无 page 且 limitCount>0 时 LIMIT limitCount
50
+ ```
51
+
52
+ - 数据集 SQL 里可以写 `$username` / `$phone`,执行前替换成**当前登录用户**的用户名/手机号(带引号的字符串字面量)【确认】。
53
+ 做「只看自己负责的数据」时比建行权限省事。
54
+ - 数据集 SQL 自带的末尾 `ORDER BY` 在外层包了两层后**不保证保留**(优化器可以丢)【实测】。
55
+ - 数据集 SQL **结尾不能带分号**:它被包成子查询,`;` 会报 `Unexpected input ';'`【实测】。`ds-create/update` 已自动去掉结尾分号,
56
+ 中间出现分号(多语句)直接拒绝。
57
+
58
+ ## 2.1 参数变量 `$1~$9`、`$A~$Z` 与条件块 `$N{{ … }}`【实测 2026-10-13】
59
+
60
+ 数据集 SQL 可以直接写参数(领谋提供的 `rpt_gop_*` 模板就是这么写的),执行前由后端文本替换:
61
+
62
+ | 写法 | 有值时 | 没值时(看板/卡片没配这个参数的筛选器,或值为空) |
63
+ |---|---|---|
64
+ | 裸参数 `$2` | 替换成 `'值'`(带单引号的字符串字面量) | 替换成 **`'0'`**(不是空串、不是 NULL) |
65
+ | 条件块 `$N{{ AND w.x = $4 }}` | 去掉 `$N{{` `}}`,参数替换后保留 | **整块删除** |
66
+ | 区间筛选(`filterOperationType:37`)绑两个变量 | 依次替换成起、止两个值 | 同上 |
67
+
68
+ - ★条件块必须写在**一行**里,跨行不会展开,`$N` 原样送到数据库报错。`ds-create/update` 不会重排已是多行的 SQL。
69
+ - ★没配的裸参数是 `'0'`:模板里要能识别 `'0'`(如 `$D IN ('全部','','0')`),否则静默错——实测 `rpt_gop_dim_scatter`
70
+ 不配 `$G/$H/$I` 时 X/Y/气泡值全为 NULL,必须在看板上配带默认值的筛选器。
71
+ - 变量是全局的(不分租户):`linkai api-get --path /page-setting/variable-list` → `[{variableKey:'$2', id}]`,筛选器用 `variableIds` 引用 id。
72
+ - 看板参数筛选器:`filterProperty:2`、`variableIds:'["<$2的id>","<$3的id>"]'`(区间两个、单值一个),`bindUserPageCards`
73
+ 列出要传参的卡片(`dataSettingComment` 留空),下拉候选值用 `indexBlockDataId`+`fieldName` 指向一个字典数据集的列。
74
+ 完整示例见 `references/filters.md` §4.1。
75
+ - `query` 带参数:`linkai query --card <cardId> --page-size 50 --output rows.json --filters filters.json`,filters.json 每项
76
+ `{"filterProperty":2,"filterType":2,"filterOperationType":37,"variableIds":"[...]","value":"[\"2026-09-01\",\"2026-09-30\"]"}`。
77
+ - 非超管且 `UserType==10`(SCM4.0 用户)还会按 `Config(Key=AuthorityTable)` 给数据集 SQL 注入数据权限 WHERE【确认】。
78
+
79
+ ## 3. 会不会 GROUP BY(最容易踩)
80
+
81
+ `IsGroup` 的判定(`GetNewIndexBlockDataSql` 开头):
82
+
83
+ 1. 任何显示字段 `calculationMode > 0`,或 `calculationMode == 0 && isAggregation == true` → **分组**
84
+ 2. 否则,若**没有任何** `argumentType=1`(数值)字段、但有 `argumentType=2`(维度)字段 → **也分组**
85
+ 3. 其余情况 → 不分组,逐行明细
86
+
87
+ 分组时 `GROUP BY` 的是全部维度(`argumentType` 2/3)字段,以及所有**不聚合**的排序字段。
88
+
89
+ 后果:
90
+
91
+ | 卡片形态 | 结果 |
92
+ |---|---|
93
+ | AG 明细表,**所有列都配成 `waxis`**(`linkai card-table` 就是这样) | 规则 2 命中 → `GROUP BY` 全部列:**完全相同的行会被合并成一行**,大表还慢。数据集里留一个唯一键列(ID)并注册成隐藏列就不会误合并 |
94
+ | 图表:维度 `waxis` + 数值 `yaxis`(`calculationMode:0`,`isAggregation:false`) | 不分组,原样出行 → 数据集 SQL 自己要先聚合好 |
95
+ | 图表:数值 `yaxis` `calculationMode:2`(界面把数值字段拖进「数值」区的默认值) | 分组 + `SUM()` |
96
+
97
+ `calculationMode`:`0` 无处理 · `1` 计数 · `2` 求和 · `3` 平均 · `4` 去重计数 · `5` 最小 · `6` 最大 · `99` 参与分组(`Enums.CalculationMode`)。
98
+ 界面拖字段时的默认值:数值类型(int/decimal)拖进数值区 = `2` 求和;文本拖进数值区 = `1` 计数;
99
+ 字段表达式本身含聚合函数时 = `0` + `isAggregation:true`。
100
+
101
+ **建议**:图表卡片让数据集 SQL 预聚合、卡片 `calculationMode:0`(`linkai card-chart` 的做法),
102
+ 口径全在 SQL 里一眼可查;需要随筛选器动态聚合(例如按店铺筛完再求和)时才用 `calculationMode:2`。
103
+
104
+ ## 4. 排序:三条通道,按优先级
105
+
106
+ 后端只认一条 ORDER BY 来源,**先到先得**:
107
+
108
+ | 优先级 | 来源 | 怎么配 | 状态 |
109
+ |---|---|---|---|
110
+ | ① | 请求体 `sortFields` | 用户点列头的临时排序,前端放在 `fieldTempSort` 里;**刷新即丢** | 【确认】 |
111
+ | ② | **卡片持久排序** `UsedIndexBlockSortField` | `PUT /page-setting/page-card-index-block-sort-field`(= 卡片右上角「排序」弹窗);`linkai card-sort` | **【实测 2026-10-13】首选** |
112
+ | ③ | 卡片字段表里 `argumentType=0` 的排序字段(`order<N>`) | `indexBlockDataSettingDtos` | 【确认】,有坑,见下 |
113
+ | ④ | 数据集 SQL 自套 `ORDER BY … LIMIT 100000` | 改 SQL | 【实测】可用 |
114
+
115
+ ② 的 body:
116
+
117
+ ```jsonc
118
+ {"usedIndexBlockId": "<卡片 id>",
119
+ "sortField": [
120
+ {"field": "waxis3", "type": 1, "sort": "desc", "sortNum": 0, "sortFields": []}, // type 1 = 按值升降序
121
+ {"field": "waxis5", "type": 2, "sort": "asc", "sortNum": 1, // type 2 = 手动指定值的顺序
122
+ "sortFields": [{"value": "紧急", "sortNum": 0}, {"value": "一般", "sortNum": 1},
123
+ {"value": "other", "sortNum": 9}]} // "other" = 其余值
124
+ ]}
125
+ ```
126
+
127
+ - `field` 填**卡片字段名** `waxisN/yaxisN`(不是数据集列名);后端生成 `ORDER BY waxis3 DESC`,
128
+ 手动排序生成 `ORDER BY CASE WHEN waxis5='紧急' THEN 0 … ELSE 9 END`。
129
+ - 每次保存是**整组替换**;传 `sortField: []` 即清空。
130
+ - 持久排序挂在**卡片**上(`usedIndexBlockId` = `indexBlock.id`),看板里和报表里同一张卡片共用。
131
+ - 排序在 `LIMIT/OFFSET` 之前,分页翻页顺序稳定(只排一个有重复值的列时,同值行顺序仍不稳定——再加一个唯一键做第二排序)。
132
+ - 卡片详情 / 方案详情 / 报表详情里 `indexBlock.sortField` 就是读回来的持久排序。
133
+
134
+ ③ 的坑:只有当**全部** `argumentType=0` 字段的 `orderByType` 都 ≠ 0 时才生效(有一个是 0 就整组不排);
135
+ 排序字段不在显示列里时会被追加进内层 SELECT,和 `WT.*` 里的同名列撞成 **ambiguous**;
136
+ 数值排序字段默认 `calculationMode:2` 会生成 `ORDER BY Sum(x)`,在不分组的明细里报
137
+ `must be an aggregate expression or appear in GROUP BY`。这就是旧版文档「卡片排序全部无效」的真实原因。**别用 ③,用 ②。**
138
+ 要按不显示的列排(如图表 x 轴是文本),把它加成卡片的额外维度再用 ②,见 pitfalls 57。
139
+
140
+ > ✅ ② 已线上实测(2026-10-13,测试租户租户,日趋势卡 `card-sort --spec 日期:asc`):后端在外层生成
141
+ > `ORDER BY waxis1 ASC nulls last LIMIT 50`,返回数据按日期升序,分页前生效。`card-sort` 保存后会自动跑一次查询,
142
+ > 返回 `sqlHasOrderBy`(只认外层按 waxisN/yaxisN 的 ORDER BY,数据集 SQL 注释里的字样不算)。
143
+
144
+ ## 5. 条数:`limitCount` 与分页
145
+
146
+ - 请求带 `page` → `LIMIT pageSize OFFSET …`,**`limitCount` 不起作用**;总数在首行 `totalcount` 列。
147
+ - 请求不带 `page` 且 `limitCount > 0` → `LIMIT limitCount`。
148
+ - 前端保存时:`agtable` / `gantt` / `multiFlowStep` 开了分页(`config.showPagination:true`)就**不提交 `limitCount`**
149
+ (`normalizeLimitCountForSubmit`)。其余图表的 `limitCount` 就是 Top N。
150
+ - 甘特/流程图开分页时按「主任务字段」分页(`isPivotTable:true` + `pivotPageField`),一页 = N 个主任务的全部子行。
151
+
152
+ ## 6. 总计
153
+
154
+ agtable `config.total.show:true`:
155
+ - `rowtotalType:'all'` → 请求带 `isTotal:true` + `pivotTableGroup:[所有列主字段]`,后端用窗口函数算**全量**总计;
156
+ 只对 `argumentType=1` 且 `calculationMode≠0` 的数值列有值(`SUM/COUNT/AVG/MIN/MAX(...) OVER()`)。
157
+ - `rowtotalType:'page'` → 前端只对当前页求和,不发总计请求。
158
+ - `labelColumn` 写哪一列显示「总计」字样(填 `wd.columns[].id`),`labelText` 改文案。
159
+
160
+ ## 7. 租户隔离与权限(只要知道这些)
161
+
162
+ - `SET @tenant_code` 只在**数仓多租户数据源**上拼,且 `0000` 租户(平台)不拼。视图靠它过滤;底表不受它影响(见 `references/dataset.md`)。
163
+ - 数据源不是 StarRocks 且不属于当前租户时,后端会改用当前租户名为 `Cloud` 的数据源【确认】——跨租户复制来的卡片可能静默换源。
164
+ - 「数据源行权限」(2026-09 起)会在执行前按
165
+ 「数据源 + 表名」改写数据集 SQL,给受控表加 `IN (…)` 条件。超管、`0000` 租户不受控。
166
+ **搭报表时不用管它**;但「同一张报表不同人看到的行数不同」时,先怀疑它。
167
+
168
+ ## 8. 排查清单
169
+
170
+ | 症状 | 先查 |
171
+ |---|---|
172
+ | 空白/转圈,接口 200 | 响应 `message`(数据库报错)+ `extendData.sql`;`linkai query --card <卡片id>` |
173
+ | 行数比预期少、重复行不见了 | §3 规则 2:全 waxis 明细被 GROUP BY 了 |
174
+ | `must be an aggregate expression…` | 有 `isAggregation`/`calculationMode` 不一致的字段,或用了 ③ 排序 |
175
+ | `Column '…' is ambiguous` | 排序/隐藏字段与 `WT.*` 撞名(③ 排序的典型症状) |
176
+ | 筛选器能选但不过滤 | `references/filters.md`「不过滤的五个原因」 |
177
+ | 不同用户看到的数不同 | 行权限模块 / `$username` / 个人筛选副本(`references/filters.md`) |
@@ -0,0 +1,249 @@
1
+ # 报表与看板
2
+
3
+ ## 发布成数据报表
4
+
5
+ 一行命令:
6
+
7
+ ```bash
8
+ linkai report-publish --card <卡片id> --name "<报表名>"
9
+ ```
10
+
11
+ **一张卡片只能对应一个报表**,重复建会被服务端挡回来(「该报表已经存在了,请勿重复添加!」)。
12
+ 建完地址是 `/view/table?indexBlockId=<卡片id>&status=view`。
13
+
14
+ 报表 vs 分析方案(看板):
15
+
16
+ | | 分析方案 | 数据报表 |
17
+ |---|---|---|
18
+ | 地址 | `/view/plan?plan=<planId>` | `/view/table?indexBlockId=<卡片id>` |
19
+ | 形态 | 网格布局,多卡片,每张有外框和标题栏 | **全屏单卡片**,没有外框,表格区高约 90px |
20
+ | 详情接口 | `POST /page-setting/get-new-page-plan/<planId>?isEdit=true\|false` | `POST /page-setting/get-new-page-card-report` `{"indexBlockId":"...","isSubWarn":false}` |
21
+ | 布局保存 | `POST /page-setting/update-page-card` | 无(本来就是全屏单卡片) |
22
+ | 筛选器 | 同一个 `batch-add-or-update-filter`,`filterSourceType:4` + `userPageSettingId` | 同上,`filterSourceType:2` + `userPageCardId` |
23
+ | 筛选器绑字段 | `bindUserPageCards[]`(见下,字段名是 `pageCardId`) | `propertyName`(直接写 `waxisN`) |
24
+ | 列表接口 | `POST /page-setting/get-page-setting-folder-bussiness-info`<br>`{"isOnlySeeSelf":false,"page":{...}}` | `POST /index-block/get-index-block-report-folder-bussiness-info` `{}`<br>(响应直接带 `indexBlockId`,反查卡片很好用) |
25
+
26
+ 一张表格为主的东西,**报表形态比看板好**:没有卡片外框,同样窗口下能多看小半行。
27
+
28
+ ### 报表筛选器(纯接口)
29
+
30
+ ⚠ **2026-10 后端改成了分层模型**(`references/filters.md` §1):报表筛选器的系统模板是**卡片自己的 `filters`**,
31
+ 每个用户第一次打开报表时从它复制一份个人副本。所以给所有人配筛选器要这样做:
32
+
33
+ ```bash
34
+ linkai backup --type card --id <卡片id> # 写前基线
35
+ linkai card-get --card <卡片id> --output card.json
36
+ # 改 card.json 的 filters(单条格式见 references/card.md「卡片自带的筛选器」,propertyName 填 waxisN)
37
+ linkai card-save --file card.json # 保存并回读
38
+ linkai report-card --card <卡片id> # → userPageCardId
39
+ linkai reset-filter --page-card <userPageCardId> # 让自己看到新模板
40
+ ```
41
+
42
+ 【确认】这条路径还没线上实测过,第一次照做时用第二个账号打开报表核对一次,确认后去掉本标注。
43
+
44
+ 旧做法(`linkai api --method POST --path /page-setting/batch-add-or-update-filter --body body.json`,`filterSourceType:2` +
45
+ `isTenantSystem:false`)仍然能用,但**只改你自己的个人副本**,适合调试、看效果;body 是全量覆盖
46
+ (不在数组里的筛选器会被删,新增的不带 `filterId`):
47
+
48
+ ```json
49
+ {"isTenantSystem": false, "filterSourceType": 2, "userPageCardId": "<report-card 拿到的 id>",
50
+ "filters": [{"labelName":"店铺", "propertyName":"waxis10", "fieldName":"店铺", "filterProperty":1,
51
+ "filterSourceType":2, "userPageCardId":"<同上>", "sortNum":0,
52
+ "defaultValue":"\"某个值\"", "filterId":"<已有的传,新增的不传>", ...}]}
53
+ ```
54
+
55
+ 界面路径(想看看某项长什么样时用):报表页右上角**漏斗图标** → 「筛选条件」。
56
+ ⚠ 界面的「关联卡片」下拉列不全字段,筛选器绑定只能走接口。
57
+
58
+ ### 报表的排序
59
+
60
+ 报表就是卡片,持久排序挂在卡片上:`linkai card-sort --card <卡片id> --spec sort.json`,看板里同一张卡片也一起生效。
61
+ 格式见 `references/query-engine.md` §4。
62
+
63
+ ### 图表导出的取数上限【确认】
64
+
65
+ 现在图表导出取数会显式传 `pageIndex:1,pageSize:1000000`,超时仍是 2 分钟;
66
+ 是否带页面当前筛选仍由导出配置的 `filter` 开关决定。这是「最多请求 100 万行」,不是无限量导出;
67
+ 大表仍要用有代表性的超大数据验证服务端限制、内存和超时。
68
+
69
+ ## 建分析方案(看板)
70
+
71
+ ```bash
72
+ PLAN=$(linkai plan-create --name "示例客户生命周期") # → planId
73
+ linkai plan-add --plan $PLAN --card <速览卡片id> --x 0 --y 0 --w 24 --h 21 # x y 宽 高(格)
74
+ linkai plan-add --plan $PLAN --card <明细卡片id> --x 0 --y 21 --w 24 --h 30
75
+ linkai plan-filter --plan $PLAN --file filters.json # 存筛选器 + 自动 reset-user-filter
76
+ ```
77
+
78
+ ⚠ `filters.json` 顶层要写 `"isTenantSystem": true`(存成租户系统模板,所有人可见;界面编辑模式就是这么存的)。
79
+ 写 `false` 只改你自己的个人副本。见 `references/filters.md` §1。
80
+
81
+ `plan-add` 底层是 `POST /page-setting/new-page-card`,**可以直接传目标布局**,
82
+ 不用先加再 `update-page-card` 调位置。返回的 `pageCardId` 就是配筛选器时
83
+ `bindUserPageCards[].pageCardId` 要用的那个。
84
+
85
+ 要事后调布局才用 `update-page-card`,它的 body 里 `dataScreenSetting` / `name` /
86
+ `parentId` / `isPrivate` 必须**从 `plan-get` 的结果原样带回去**,漏了会把方案配置冲掉。
87
+ 而且 **`list` 里没有的卡片会被移出看板**(这也是从看板里删卡片的办法)。
88
+ 单删一张也可以:`DELETE /page-setting/page-card/{pageCardId}`(`linkai api --method DELETE --path /page-setting/page-card/<id>`,
89
+ 卡片本身仍在卡片库)。
90
+
91
+ ### `new-page-card` 的其它能力(`PageCardV2Req`)
92
+
93
+ | 字段 | 用途 |
94
+ |---|---|
95
+ | `cardType` | `1` 指标卡片(默认)· `2` 富文本卡片(`contentText` 放 HTML)· **`5` 分区标题卡**【实测】:`contentText` 为 `{"type":"title-template","version":1,"template":"section","config":{"serial":"01","title":"商品经营总览","subtitle":"","showDecorLine":true,"description":"",…}}`,高 2 格,经 `api POST /page-setting/new-page-card` 加(不带 `indexBlockId`) |
96
+ | `x y width height` / `mobileX…mobileHeight` | PC / 手机布局(格);`isShowInMobilePlan` 控制手机端是否显示 |
97
+ | `zIndex` | 叠放层级 |
98
+ | `refershSetting` | 卡片刷新配置(字符串;语义同下方看板刷新,未实测) |
99
+ | `bindFilterIds` | 加卡片时直接绑已有筛选器 |
100
+
101
+
102
+ ### 分区标题卡:单行副标题与布局压缩【实测】
103
+
104
+ `cardType:5` 的 `contentText` 是 JSON 字符串,`template:"section"` 的渲染层级是:
105
+
106
+ | 字段 | 位置 / 用途 |
107
+ |---|---|
108
+ | `config.serial` | 标题左侧编号 |
109
+ | `config.title` | 主标题 |
110
+ | `config.subtitle` | **主标题后方、同一行**的副标题 |
111
+ | `config.description` | 标题行下方另起一行的说明 |
112
+ | `serialStyle` / `titleStyle` / `subtitleStyle` / `descriptionStyle` | 各自文字样式;可用字段见 `TitleControl/utils.js` |
113
+ | `showDecorLine` | 标题后的横向装饰线 |
114
+
115
+ 按 demo 做「编号 + 主标题 + 副标题」单行标题时:
116
+
117
+ ```json
118
+ {
119
+ "type": "title-template",
120
+ "version": 1,
121
+ "template": "section",
122
+ "config": {
123
+ "serial": "03",
124
+ "title": "指标趋势",
125
+ "subtitle": "顶部「趋势指标」「趋势拆分」切换;汇总只按全渠道算",
126
+ "description": "",
127
+ "showDecorLine": false
128
+ }
129
+ }
130
+ ```
131
+
132
+ - PC 与手机的单行标题高度都用 **2 格**;卡位 `dataScreenSetting` 用
133
+ `{"useCardConfig":true,"backgroundColor":"transparent","cardTitle":{"show":false}}` 去掉外层卡片标题和底色。
134
+ - 把原下方说明改成同行副标题时,将 `description` 复制到 `subtitle`,`descriptionStyle` 复制到
135
+ `subtitleStyle`,再清空 `description`。
136
+ - `subtitle` 的 class 写死了斜体,`subtitleStyle` 无法取消;若用户要求正体或文案太长,保留
137
+ `description` 为下方说明行,并按实际行高给 3–4 格。
138
+
139
+ 修改存量标题卡要分两步:
140
+
141
+ 1. `POST /page-setting/replace-page-card`:传 `oldPageCardId` + 新 `contentText` + 原坐标,只修文字。
142
+ 实测即使 body 传新 `height`,返回的仍是原高度;返回 id 可能保持原值,也不应假设一定不变,以响应和重新 `plan-get` 为准。
143
+ 2. `POST /page-setting/update-page-card`:用重新 `plan-get` 的**完整卡片列表**改高度和坐标。
144
+ 标题从 4 格压到 2 格时,所有位于它下方的 PC 卡片 `y -= 2`;一次压缩多个标题时按原 `y`
145
+ 累加前面标题的缩减量。手机端原本已是 2 格则不改 `mobileY/mobileHeight`。
146
+
147
+ 写后回读至少检查:卡片总数/id 集合不变(若接口返回替换 id,则按返回值更新预期)、单行标题
148
+ `height == 2`、`description == ""`、下游卡片位移值正确,且矩形无重叠。
149
+
150
+ 看板自动刷新:`POST /page-setting/save-page-setting-refersh-param {"id":"<planId>","isPage":true,"refershSetting":<分钟,整数 0–60,0=关>}`(单位:分钟)。
151
+
152
+ ### 联动 / 钻取 / 跳转(未封装)
153
+
154
+ | 功能 | 接口 |
155
+ |---|---|
156
+ | 钻取开关 | `POST /page-setting/update-drill-switch` `{"userPageCardIds":["<pageCardId>"],"isOpen":true}` |
157
+ | 联动(点卡片 A 过滤卡片 B) | `get-new-target-page-card-all-list` → `linkages-save` |
158
+ | 跳转(点值跳到卡片/方案/链接) | `get-new-target-query-transfer` → `link-add-or-update` |
159
+
160
+ 开关枚举 `SwitchType`:`0` 关 · `1` 钻取 · `2` 联动 · `4` 跳转。这三项配置复杂、没有实测记录,
161
+ 建议在界面上配置;确需接口方式时先用 `zz-` 看板试。
162
+
163
+ ## 看板筛选器怎么绑到卡片
164
+
165
+ 前端逻辑:
166
+
167
+ ```js
168
+ // filterProperty === 1(指标过滤)时
169
+ const bind = filter.bindUserPageCards.find(b => b.pageCardId === 当前卡片.id)
170
+ filter.propertyName = 卡片.dataSettingList.find(d => d.comment === bind.dataSettingComment).field
171
+ ```
172
+
173
+ 所以三件事必须对上,错一个就是「筛选器在、能选、就是不过滤」且**不报错**:
174
+
175
+ 1. `pageCardId` —— 是 `plan-add` 返回的那个,**不是** `indexBlockId`,字段名也**不是** `userPageCardId`
176
+ 2. `dataSettingComment` —— 必须在目标卡片的 `indexBlockDataSettingDtos` 里有同名 `comment`
177
+ 3. `filterProperty: 1`
178
+
179
+ 一个筛选器可以绑多张卡片(一个下拉同时过滤整页);某张卡片没有这个字段就别绑它。
180
+ 卡片里只想用来筛选、不想显示的字段,加成 `hidden: true` 的隐藏列即可。
181
+
182
+ ⚠ 界面上「关联卡片」的字段下拉**列不全**(实测 60 列的卡片只列出 9 个且翻不到想要的),
183
+ 所以绑定这件事**只能走接口**。
184
+
185
+ ## 网格布局
186
+
187
+ `实际px = 24 × 格数 − 8`,看板里首个卡片顶部在 `y = 106px`,宽度 24 格铺满。
188
+
189
+
190
+ ### 方案左右留白与内容居中【实测】
191
+
192
+ 看板 PC 布局是 24 列。按 demo 做居中内容区时,默认用:
193
+
194
+ ```text
195
+ contentX = 2
196
+ contentWidth = 20
197
+ contentRight = 22
198
+ ```
199
+
200
+ 即所有顶层满宽标题卡、数据卡都用 `x:2, width:20`,左右各留 2 格,内容整体居中。
201
+ 这是款式画像 demo 的标准值;用户要求「留白再加一点」时,对称减小内容宽度,例如 `x:3, width:18`,
202
+ 不要只改一侧。通用计算是:
203
+
204
+ ```text
205
+ contentX = (24 - contentWidth) / 2
206
+ ```
207
+
208
+ `contentWidth` 应选偶数,保证 `contentX` 是整数格。一行有多张卡时,各卡的宽度之和与间隔也必须落在
209
+ `[contentX, contentX + contentWidth]` 内;例如 20 格内的 12/8 拆分是 `x:2,width:12` + `x:14,width:8`。
210
+
211
+ - 左右留白用卡位 `x/width` 做,不要用卡片内容的 CSS padding 冒充;否则标题、筛选器和卡片外框仍然不对齐。
212
+ - 改存量方案使用 `update-page-card` 完整列表,保留每张卡的高度、`y`、底色、标题开关和手机布局;
213
+ 用户只要 PC 端留白时,不改 `mobileX/mobileWidth`。
214
+ - PC 同行多卡在手机端默认按左到右顺序纵向堆叠;若原卡共用同一个 `mobileY`,必须依次累加前卡高度,
215
+ 并同步顺延所有后续卡片。不要只修重叠的两张卡,留下下游区块覆盖。
216
+ - 写后回读检查:全宽卡片必须 `x == contentX && width == contentWidth`;所有 PC 卡片都满足
217
+ `x >= contentX && x + width <= contentX + contentWidth`;左右边界对称、卡片数/id 不变;PC 与手机矩形都无重叠。
218
+
219
+
220
+ ### 暖色页面背景与卡片层次【实测】
221
+
222
+ 按 demo 还原或搭商品运营类看板时,默认页面色是款式画像使用的暖灰米色 `#F4F3EE`;
223
+ 用户或 demo 明确给出其它配色时以其为准:
224
+
225
+ ```json
226
+ {"pageBackgroundColor":"#F4F3EE"}
227
+ ```
228
+
229
+ 这是方案顶层 `dataScreenSetting`(JSON 字符串),不是单张卡的图表配置。视觉层次按下列原则:
230
+
231
+ - 页面/左右留白区:`#F4F3EE`,让居中的 20 格内容区与画布区分明确。
232
+ - 分区标题卡:卡位背景用 `transparent`,且 `cardTitle.show:false`,透出页面暖色。
233
+ - 统计指标卡(`meter/list`、`meter/meterList`)与时间轴指标卡(`meter/meterTimeline`):方案卡位背景默认用
234
+ `transparent`,透出页面暖色;配置写在 pageCard 的 `dataScreenSetting.backgroundColor`,不改图表内部配置。
235
+ - 主要内容面:保留组件内部白色 `#FFFFFF`(必要时加 `#E3E1DA` 边框),形成「暖色画布 + 白色内容卡」;
236
+ 若图表组件自己已画白底,外层卡位用 `transparent` 或 `#F4F3EE`,避免双重白色外壳。
237
+ - 不要把每张卡的背景都改成暖色;否则白色内容层次消失。也不要只改卡位背景冒充页面背景,卡片间隙和左右留白仍会露出旧色。
238
+
239
+ 改存量方案时,先解析原顶层 `dataScreenSetting`,只合并/更新 `pageBackgroundColor`,不要用单键 JSON 覆盖掉其它页面配置;
240
+ 再通过 `update-page-card` 的完整 body 原样带回。写后回读确认:
241
+
242
+ 1. 方案 `dataScreenSetting.pageBackgroundColor == "#F4F3EE"`;
243
+ 2. 分区标题、统计指标卡、时间轴指标卡的方案卡位是透明色;
244
+ 3. 需要对比层次的其它内容组件仍是白色,卡片数/id、坐标和手机布局没有被误改。
245
+
246
+ 这是 demo / 商品运营类看板的默认基线,不无条件套到其它类型看板;用户或 demo 有其它配色时以它为准。
247
+
248
+ 要让分页栏常驻视口:`106 + (24 × 格数 − 8) ≤ 目标视口高度`。
249
+ 按**最小**目标视口算,宁可底部留白也别让分页栏掉出去。
package/llms.txt ADDED
@@ -0,0 +1,37 @@
1
+ # LinkAI-CLI
2
+
3
+ > `linkai`:LinkMoreBI.CLI 的浏览器与站点自动化底层能力,面向 AI 代理。所有命令输出 JSON 信封 {ok,data} / {ok:false,error}。
4
+
5
+ ## 入口
6
+
7
+ - AGENTS.md:AI 代理调用约定、领谋 BI 自动搭建流程、通用系统提示
8
+ - skills/linkai-report-builder/SKILL.md:领谋 BI 搭建规则(`linkai skills read linkai-report-builder -f plain`)
9
+ - knowledge/README.md:两个知识库的关系(`linkai kb` 列出全部文件)
10
+
11
+ ## 知识库
12
+
13
+ - knowledge/report-builder/SOP.md:操作顺序、授权边界、回滚
14
+ - knowledge/report-builder/build-spec.md:声明式自动搭建 spec 与 `linkai build`
15
+ - knowledge/report-builder/references/:接口、图表配置、查询引擎、筛选器、踩坑
16
+ - knowledge/data-dictionary/README.md:数据字典(预留)
17
+ - knowledge/browser/README.md:浏览器能力说明(浏览器能力与工作原理)
18
+
19
+ ## 模版
20
+
21
+ - templates/report-spec/report.example.yaml:自动搭建报表模版
22
+
23
+ ## 常用命令
24
+
25
+ - `linkai list <关键字>`、`linkai <site> <command> --help -f json`
26
+ - `linkai login`:首次在领谋网页完成 OAuth 授权,之后自动续期
27
+ - `linkai login --tenant <租户名>`:切换租户
28
+ - `linkai status`:当前租户
29
+ - `linkai build --spec <spec.yaml> [--execute] [--confirm]`
30
+ - `linkai deliverable --plan <planId>`:交付清单
31
+ - `linkai kb [--file <path>]`
32
+
33
+ ## 规定
34
+
35
+ - 不具备操作数据库的能力;数据集只能是只读查询、只能查 ds-tables 清单中的视图
36
+ - 方案完成必须交付:数据集名称、卡片名称、方案名称、筛选条件数据集、数据集脚本、方案地址
37
+ - 浏览器能力类问题以 knowledge/browser/README.md 为准回答
package/package.json CHANGED
@@ -1,6 +1,107 @@
1
1
  {
2
2
  "name": "@linkaicli/linkai-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.17",
4
+ "publishConfig": {
5
+ "registry": "https://registry.npmjs.org/",
6
+ "access": "public"
7
+ },
8
+ "description": "LinkAI-CLI — LinkMoreBI 的浏览器与站点自动化能力,面向 AI 代理调用(领谋 BI 自动搭建报表)。",
9
+ "engines": {
10
+ "node": ">=20.18.1"
11
+ },
12
+ "type": "module",
13
+ "main": "dist/bundle/main.mjs",
14
+ "bin": {
15
+ "linkai": "dist/bundle/main.mjs"
16
+ },
17
+ "exports": {
18
+ ".": "./dist/bundle/main.mjs",
19
+ "./registry": "./dist/bundle/registry.mjs",
20
+ "./errors": "./dist/bundle/errors.mjs",
21
+ "./types": "./dist/bundle/types.mjs",
22
+ "./utils": "./dist/bundle/utils.mjs",
23
+ "./logger": "./dist/bundle/logger.mjs",
24
+ "./browser/cdp": "./dist/bundle/browser-cdp.mjs",
25
+ "./browser/page": "./dist/bundle/browser-page.mjs",
26
+ "./browser/utils": "./dist/bundle/browser-utils.mjs",
27
+ "./download": "./dist/bundle/download.mjs",
28
+ "./download/article-download": "./dist/bundle/download-article.mjs",
29
+ "./download/media-download": "./dist/bundle/download-media.mjs",
30
+ "./download/progress": "./dist/bundle/download-progress.mjs",
31
+ "./pipeline": "./dist/bundle/pipeline.mjs",
32
+ "./skills": "./dist/bundle/skills.mjs",
33
+ "./oplog": "./dist/bundle/oplog.mjs"
34
+ },
35
+ "files": [
36
+ "dist/bundle/",
37
+ "!dist/bundle/**/*.map",
38
+ "THIRD_PARTY_NOTICES.txt",
39
+ "clis/",
40
+ "skills/**",
41
+ "knowledge/**",
42
+ "templates/**",
43
+ "cli-manifest.json",
44
+ "README.md",
45
+ "GUIDE.md",
46
+ "AGENTS.md",
47
+ "llms.txt",
48
+ "LICENSE",
49
+ "NOTICE",
50
+ "!**/*.test.*",
51
+ "!**/__fixtures__/"
52
+ ],
53
+ "scripts": {
54
+ "dev": "tsx src/main.ts",
55
+ "dev:bun": "bun src/main.ts",
56
+ "build": "npm run clean-dist && npm run build-manifest",
57
+ "prebuild-manifest": "npm run compile && node scripts/bundle.mjs",
58
+ "build-manifest": "tsx src/build-manifest.ts",
59
+ "clean-dist": "node scripts/clean-dist.cjs",
60
+ "start": "node dist/src/main.js",
61
+ "start:bun": "bun dist/src/main.js",
62
+ "typecheck": "tsc --noEmit",
63
+ "prepublishOnly": "npm run build",
64
+ "test": "vitest run --project unit --project extension --project adapter",
65
+ "test:bun": "bun vitest run --project unit --project extension --project adapter",
66
+ "test:adapter": "vitest run --project adapter",
67
+ "test:all": "vitest run",
68
+ "test:e2e": "vitest run --project e2e-fixed-port --project e2e",
69
+ "bundle": "node scripts/bundle.mjs",
70
+ "compile": "tsc --build && node -e \"require('fs').chmodSync('dist/src/main.js', 0o755)\""
71
+ },
72
+ "keywords": [
73
+ "linkai",
74
+ "linkai-cli",
75
+ "cli",
76
+ "bi",
77
+ "linkmore",
78
+ "ai-agent"
79
+ ],
80
+ "author": "LinkMore",
81
+ "license": "Apache-2.0",
82
+ "devDependencies": {
83
+ "@mozilla/readability": "^0.6.0",
84
+ "@types/js-yaml": "^4.0.9",
85
+ "@types/jsdom": "^27.0.0",
86
+ "@types/node": "^25.5.2",
87
+ "@types/turndown": "^5.0.6",
88
+ "@types/ws": "^8.5.13",
89
+ "acorn": "^8.19.0",
90
+ "cli-table3": "^0.6.5",
91
+ "commander": "^14.0.3",
92
+ "esbuild": "^0.28.2",
93
+ "js-yaml": "^4.3.2",
94
+ "jsdom": "^29.0.2",
95
+ "tsx": "^4.19.3",
96
+ "turndown": "^7.2.2",
97
+ "turndown-plugin-gfm": "^1.0.2",
98
+ "typescript": "^6.0.2",
99
+ "undici": "7.29.1",
100
+ "vitest": "^4.1.0",
101
+ "ws": "^8.18.0"
102
+ },
103
+ "overrides": {
104
+ "postcss": "^8.5.10"
105
+ },
106
+ "homepage": "https://bi.linkmore.com"
107
+ }
@@ -0,0 +1,37 @@
1
+ 你可以执行 `linkai` 命令操作领谋 DI/BI。用户只需表达业务目标,不必说 CLI、API 或规范文件名。所有命令返回 JSON(ok/data 或 ok/error)。此入口不接管无关的本地 Excel 整理。
2
+
3
+ 自然语言任务入口:
4
+ - 每个业务任务先 `linkai flow-start --goal "<用户目标>" --type create|modify|import|query [--plan <planId>] --model "<你的模型与版本>"`,交付后 `linkai flow-end --status delivered`(放弃/失败用 abandoned/failed)。`linkai status` 返回 unfinishedFlows 时先按其 next 续做(命令加 `--flow <id>`)或结束。
5
+ - “预算和实际导进去”“本月数据更新到经营看板”“重传看看数据对不对”:先执行 `linkai skills read linkai-data-workflow -f plain`,识别导入、更新或只读核对。
6
+ - “这些底表做个分析”“原方案增加维度”:先读数据工作流技能分析来源,再转报表搭建技能。
7
+ - 只改图表、卡片或布局:直接读报表技能。
8
+ - 要开子代理或多个会话并行处理同一个方案,或命令返回 COLLAB_CONFLICT:先执行 `linkai skills read linkai-collab -f plain`,用任务板拆分任务、认领共享对象;读操作并行,写同一对象才排队,不要绕过冲突。
9
+ - 当前 AI 工具已安装目录里有更新版本的同名技能时(WorkBuddy 默认在用户目录的 `.workbuddy/skills/`),以已安装的 SKILL.md 及其相对参考文件为准。
10
+ - 客户明确请求导入是对应范围的授权,无须再次让其选择 CLI。只说核对不授权写入;删除、外发、改变口径或扩大范围仍需额外授权。
11
+ - 方案规范写明了目标租户时,`linkai status` 的租户必须与之一致,不一致先 `linkai login --tenant <租户名>` 切换;不能因为当前租户也能读到目标卡片就认定租户正确。返回 TENANT_MISMATCH 时同样处理。
12
+ - 写填报数据用 `linkai fill-write`(默认预览,确认后加 `--execute`):一条记录一个元素、字段一次带齐,禁止拆字段写;禁止改分析数据集 SQL 来去重或隐藏数据;写完回读填报表本身。删除填报记录必须用户明确同意。
13
+
14
+ 进入领谋图表搭建或对象修改阶段前,先执行:
15
+ linkai skills read linkai-report-builder -f plain
16
+ linkai kb --file knowledge/report-builder/SOP.md
17
+ 按需再读 `linkai kb` 列出的其它文件;报表模版用 `linkai template --output <目录>` 复制出来再改。
18
+
19
+ BI 公共知识库是强制执行约束,不是可选参考。没有先读取 SOP 和任务对应篇章,不得开始搭建;不得凭模型经验自由发明接口、字段角色、图表配置、筛选器结构或步骤。知识库不可用、缺失或没有覆盖需求时,停止写操作并把缺口告诉用户。
20
+
21
+ 登录与租户:
22
+ - 先执行 linkai status 确认当前租户。
23
+ - 返回 AUTH_REQUIRED 时,请用户执行 `linkai login` 并在领谋网页完成 OAuth 授权;不要索要账号密码。
24
+ - 用户说“好了”后执行 linkai tenants,核对 OAuth 授权的当前租户。
25
+ - 同账号切换租户:执行 `linkai login --tenant <租户名>`,CLI 复用已有 OAuth 授权,无需用户重新授权。
26
+ - 用户提出“切换账号”等类似要求时,先问是否要切换领谋账号;确认后执行 `linkai login --switch-account`,在网页重新授权。
27
+
28
+ 搭建规则:
29
+ - 写方案前必须确认用户要新建还是修改;不明确就先询问。新建用 `operation: create`;修改必须定位精确 planId,先执行 `linkai plan-knowledge-get --plan <planId>` 读取现有知识,并在完成后同步更新方案知识库。历史方案没有知识库时自动补建 v1。
30
+ - 改已有对象前先 linkai backup。新建看板用 linkai build --spec,先 dry-run 给用户看计划;正式名、删除、发布、改他人在用的对象前必须得到用户明确同意。
31
+ - 不要输出凭据,不要重复执行结果不确定的写命令。
32
+ - 你没有操作数据库的能力:不能直连数据库、执行任意 SQL 或管理数据源;数据集只能查 linkai ds-tables 列出的视图,缺视图请用户联系领谋数据团队。
33
+ - 数据集 SQL 必须由你根据用户需求、当前租户服务器下发的数据字典、`ds-tables`/`ds-fields` 和用户确认的业务口径编写。CLI 只校验、保存、编排和验收,不会替你生成业务 SQL;模版 SQL 不能直接作为用户方案。
34
+ - 数据字典必须先用 `linkai kb-sources` 选择与数据集一致的 source,再按需执行 `kb-search`、`kb-object`、`kb-docs`、`kb-doc`;404/409/503 不得回退本地占位内容。
35
+ - 数据字典由服务器下发、会不定时更新:**切换租户或重新登录后必须重新读取新租户的数据字典**,不能沿用其它租户或旧的结论;服务端更新过(重新探库或口径文档更新)也要重读。CLI 在 `ds-create` / `ds-update` / `build --execute`(有新数据集)前强制校验,未读或已过期返回 `DICTIONARY_REQUIRED`;`build` 预览与 `sql-check` 会提前提示。
36
+ - 方案完成后必须完整呈现“<方案名称>方案知识库”:版本、方案地址、方案内容、解决的问题,以及每张业务卡片各自的数据集、数据来源、粒度和取数逻辑/规则。不要输出完整 SQL,也不要展示服务器上传信息。新建方案读取 build 结果的 `planKnowledge.content`;修改方案执行 `linkai plan-knowledge --plan <planId> --mode update --file <知识文件>`。
37
+ - 用户问到浏览器能力或工作原理时,先执行 linkai kb --file knowledge/browser/README.md,以该文为准回答,不补充文中没有的内容。