@lark-apaas/coding-miaoda-sandbox-skills 0.1.0-dev.61b3ece → 0.1.0-dev.6f4e4bc

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 (40) hide show
  1. package/miaoda/creative-to-fullstack/SKILL.md +239 -144
  2. package/miaoda/lark-apps-db/SKILL.md +20 -27
  3. package/miaoda/lark-apps-db/references/full-reference.md +113 -2
  4. package/miaoda/lark-apps-ops/SKILL.md +5 -4
  5. package/miaoda/lark-apps-ops/references/lark-apps-access-scope-get.md +1 -1
  6. package/miaoda/lark-apps-ops/references/lark-apps-local-dev.md +3 -2
  7. package/miaoda/lark-apps-ops/references/lark-apps-release-create.md +1 -1
  8. package/miaoda/lark-apps-ops/references/lark-apps-user-id-convert.md +63 -0
  9. package/miaoda/miaoda-sql/SKILL.md +9 -0
  10. package/miaoda/semantic-search/SKILL.md +0 -1
  11. package/miaoda/table-skill/SKILL.md +2 -0
  12. package/miaoda/testing-guide/SKILL.md +3 -1
  13. package/miaoda-design/lark-apps-comment/SKILL.md +44 -35
  14. package/miaoda-modern/lark-apps-ops/SKILL.md +5 -4
  15. package/miaoda-modern/lark-apps-ops/references/lark-apps-access-scope-get.md +1 -1
  16. package/miaoda-modern/lark-apps-ops/references/lark-apps-local-dev.md +3 -2
  17. package/miaoda-modern/lark-apps-ops/references/lark-apps-release-create.md +1 -1
  18. package/miaoda-modern/lark-apps-ops/references/lark-apps-user-id-convert.md +63 -0
  19. package/package.json +1 -1
  20. package/shared/lark-cli/SKILL.md +4 -4
  21. package/shared/lark-cli/lark-doc/references/lark-doc-fetch.md +3 -3
  22. package/shared/lark-cli/lark-drive/README.md +36 -7
  23. package/shared/lark-cli/lark-drive/references/lark-drive-batch-query-comments.md +44 -0
  24. package/shared/lark-cli/lark-drive/references/lark-drive-list-replies.md +49 -0
  25. package/shared/lark-cli/lark-im/README.md +0 -14
  26. package/shared/lark-cli/lark-sheets/README.md +5 -4
  27. package/shared/lark-cli/lark-sheets/references/lark-sheets-read-data.md +73 -3
  28. package/shared/lark-cli/lark-sheets/scripts/lark_detect_subtables.py +593 -0
  29. package/shared/lark-cli/lark-sheets/scripts/lark_inspect_workbook.py +188 -0
  30. package/shared/lark-cli/lark-sheets/scripts/lark_profile_table.py +614 -0
  31. package/shared/lark-cli/lark-sheets/scripts/lark_sheet_range.py +176 -0
  32. package/shared/lark-cli/lark-sheets/scripts/lark_sheet_read_cli.py +184 -0
  33. package/shared/lark-cli/lark-sheets/scripts/sheets_df.py +21 -3
  34. package/shared/lark-cli/lark-slides/README.md +16 -15
  35. package/shared/lark-cli/lark-slides/references/lark-slides-history.md +32 -20
  36. package/shared/lark-cli/lark-slides/references/lark-slides-xml-presentation-slide-get.md +2 -2
  37. package/shared/lark-cli/lark-whiteboard/README.md +1 -2
  38. package/shared/lark-cli/lark-wiki/references/lark-wiki-node-get.md +11 -0
  39. package/shared/lark-cli/lark-wiki/references/lark-wiki-node-list.md +1 -1
  40. package/shared/dev-channel-probe/SKILL.md +0 -40
@@ -0,0 +1,176 @@
1
+ # Copyright (c) 2026 Lark Technologies Pte. Ltd.
2
+ # SPDX-License-Identifier: MIT
3
+ """Range and coordinate helpers for Lark Sheet scripts."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import re
8
+ from dataclasses import dataclass
9
+
10
+ CELL_RE = re.compile(r"^\$?([A-Za-z]+)\$?([1-9][0-9]*)$")
11
+ ROW_RE = re.compile(r"^\$?([1-9][0-9]*)$")
12
+ COLUMN_RE = re.compile(r"^\$?([A-Za-z]+)$")
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class RangeBounds:
17
+ start_row: int
18
+ start_col: int
19
+ end_row: int
20
+ end_col: int
21
+
22
+ @property
23
+ def row_count(self) -> int:
24
+ return self.end_row - self.start_row + 1
25
+
26
+ @property
27
+ def col_count(self) -> int:
28
+ return self.end_col - self.start_col + 1
29
+
30
+
31
+ def col_to_index(col: str) -> int:
32
+ value = 0
33
+ for char in col.strip().upper():
34
+ if not ("A" <= char <= "Z"):
35
+ raise ValueError(f"Invalid column: {col}")
36
+ value = value * 26 + (ord(char) - ord("A") + 1)
37
+ if value <= 0:
38
+ raise ValueError(f"Invalid column: {col}")
39
+ return value
40
+
41
+
42
+ def index_to_col(index: int) -> str:
43
+ if index < 1:
44
+ raise ValueError(f"Column index must be >= 1: {index}")
45
+ chars = []
46
+ n = index
47
+ while n:
48
+ n, rem = divmod(n - 1, 26)
49
+ chars.append(chr(ord("A") + rem))
50
+ return "".join(reversed(chars))
51
+
52
+
53
+ def parse_cell(cell_ref: str) -> tuple[int, int]:
54
+ match = CELL_RE.match(cell_ref.strip())
55
+ if not match:
56
+ raise ValueError(f"Invalid cell reference: {cell_ref}")
57
+ col, row = match.groups()
58
+ return int(row), col_to_index(col)
59
+
60
+
61
+ def _parse_endpoint(endpoint: str) -> tuple[str, int, int] | tuple[str, int]:
62
+ cell = CELL_RE.match(endpoint)
63
+ if cell:
64
+ col, row = cell.groups()
65
+ return "cell", int(row), col_to_index(col)
66
+ row = ROW_RE.match(endpoint)
67
+ if row:
68
+ return "row", int(row.group(1))
69
+ column = COLUMN_RE.match(endpoint)
70
+ if column:
71
+ return "column", col_to_index(column.group(1))
72
+ raise ValueError(f"Invalid A1 range endpoint: {endpoint}")
73
+
74
+
75
+ def parse_range(
76
+ range_ref: str,
77
+ *,
78
+ max_row: int | None = None,
79
+ max_col: int | None = None,
80
+ ) -> RangeBounds:
81
+ """Parse the A1 range forms accepted by ``+csv-get``.
82
+
83
+ Open-ended forms need the caller's actual returned grid dimensions. This
84
+ keeps generated ranges finite without guessing a spreadsheet-wide limit.
85
+ """
86
+ ref = range_ref.strip()
87
+ if "!" in ref:
88
+ _, ref = ref.rsplit("!", 1)
89
+ if not ref:
90
+ raise ValueError(f"Invalid A1 range: {range_ref}")
91
+ parts = ref.split(":")
92
+ if len(parts) > 2:
93
+ raise ValueError(f"Invalid A1 range: {range_ref}")
94
+ start = _parse_endpoint(parts[0])
95
+ end = _parse_endpoint(parts[-1])
96
+
97
+ if len(parts) == 1:
98
+ if start[0] != "cell":
99
+ raise ValueError(f"A1 range must include a cell or ':' separator: {range_ref}")
100
+ _, row, col = start
101
+ return RangeBounds(row, col, row, col)
102
+
103
+ if start[0] == end[0] == "cell":
104
+ _, start_row, start_col = start
105
+ _, end_row, end_col = end
106
+ elif start[0] == end[0] == "row":
107
+ _, start_row = start
108
+ _, end_row = end
109
+ start_col = 1
110
+ if max_col is None:
111
+ raise ValueError(f"Range needs a maximum column: {range_ref}")
112
+ end_col = max_col
113
+ elif start[0] == end[0] == "column":
114
+ _, start_col = start
115
+ _, end_col = end
116
+ start_row = 1
117
+ if max_row is None:
118
+ raise ValueError(f"Range needs a maximum row: {range_ref}")
119
+ end_row = max_row
120
+ elif start[0] == "cell" and end[0] == "column":
121
+ _, start_row, start_col = start
122
+ _, end_col = end
123
+ if max_row is None:
124
+ raise ValueError(f"Range needs a maximum row: {range_ref}")
125
+ end_row = max(start_row, max_row)
126
+ elif start[0] == "cell" and end[0] == "row":
127
+ _, start_row, start_col = start
128
+ _, end_row = end
129
+ if max_col is None:
130
+ raise ValueError(f"Range needs a maximum column: {range_ref}")
131
+ end_col = max(start_col, max_col)
132
+ else:
133
+ raise ValueError(f"Invalid A1 range: {range_ref}")
134
+ return RangeBounds(min(start_row, end_row), min(start_col, end_col), max(start_row, end_row), max(start_col, end_col))
135
+
136
+
137
+ def format_cell(row: int, col: int) -> str:
138
+ if row < 1:
139
+ raise ValueError(f"Row must be >= 1: {row}")
140
+ return f"{index_to_col(col)}{row}"
141
+
142
+
143
+ def format_range(start_row: int, start_col: int, end_row: int, end_col: int) -> str:
144
+ bounds = RangeBounds(
145
+ min(start_row, end_row),
146
+ min(start_col, end_col),
147
+ max(start_row, end_row),
148
+ max(start_col, end_col),
149
+ )
150
+ start = format_cell(bounds.start_row, bounds.start_col)
151
+ end = format_cell(bounds.end_row, bounds.end_col)
152
+ return start if start == end else f"{start}:{end}"
153
+
154
+
155
+ def iter_cells(bounds: RangeBounds):
156
+ for row in range(bounds.start_row, bounds.end_row + 1):
157
+ for col in range(bounds.start_col, bounds.end_col + 1):
158
+ yield row, col
159
+
160
+
161
+ def ranges_intersect(a: RangeBounds, b: RangeBounds) -> bool:
162
+ return not (
163
+ a.end_row < b.start_row
164
+ or b.end_row < a.start_row
165
+ or a.end_col < b.start_col
166
+ or b.end_col < a.start_col
167
+ )
168
+
169
+
170
+ def range_union(a: RangeBounds, b: RangeBounds) -> RangeBounds:
171
+ return RangeBounds(
172
+ min(a.start_row, b.start_row),
173
+ min(a.start_col, b.start_col),
174
+ max(a.end_row, b.end_row),
175
+ max(a.end_col, b.end_col),
176
+ )
@@ -0,0 +1,184 @@
1
+ # Copyright (c) 2026 Lark Technologies Pte. Ltd.
2
+ # SPDX-License-Identifier: MIT
3
+ """Read-only Lark Sheet subset wrapper for the helper scripts."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import subprocess
9
+ import sys
10
+ from typing import Any
11
+
12
+
13
+ class LarkCliError(RuntimeError):
14
+ def __init__(self, message: str, *, cmd: list[str] | None = None):
15
+ super().__init__(message)
16
+ self.cmd = cmd or []
17
+
18
+
19
+ def add_spreadsheet_args(
20
+ parser,
21
+ *,
22
+ require_sheet: bool = False,
23
+ allow_sheet: bool = True,
24
+ ) -> None:
25
+ spreadsheet = parser.add_mutually_exclusive_group(required=True)
26
+ spreadsheet.add_argument("--url")
27
+ spreadsheet.add_argument("--spreadsheet-token")
28
+ if allow_sheet:
29
+ sheet = parser.add_mutually_exclusive_group(required=require_sheet)
30
+ sheet.add_argument("--sheet-id")
31
+ sheet.add_argument("--sheet-name")
32
+
33
+
34
+ def _append_flag(cmd: list[str], name: str, value: Any) -> None:
35
+ flag = f"--{name.replace('_', '-')}"
36
+ if value is None:
37
+ return
38
+ if isinstance(value, bool):
39
+ cmd.append(flag if value else f"{flag}=false")
40
+ return
41
+ cmd.extend([flag, str(value)])
42
+
43
+
44
+ def run_sheets(
45
+ shortcut: str,
46
+ *,
47
+ url: str | None = None,
48
+ spreadsheet_token: str | None = None,
49
+ sheet_id: str | None = None,
50
+ sheet_name: str | None = None,
51
+ flags: dict[str, Any] | None = None,
52
+ timeout: int = 60,
53
+ ) -> dict[str, Any]:
54
+ if bool(url) == bool(spreadsheet_token):
55
+ raise LarkCliError("Pass exactly one of --url or --spreadsheet-token")
56
+ if sheet_id and sheet_name:
57
+ raise LarkCliError("Pass only one of --sheet-id or --sheet-name")
58
+
59
+ cmd = ["lark-cli", "sheets", shortcut]
60
+ _append_flag(cmd, "url", url)
61
+ _append_flag(cmd, "spreadsheet_token", spreadsheet_token)
62
+ _append_flag(cmd, "sheet_id", sheet_id)
63
+ _append_flag(cmd, "sheet_name", sheet_name)
64
+ for key, value in (flags or {}).items():
65
+ _append_flag(cmd, key, value)
66
+
67
+ try:
68
+ completed = subprocess.run(
69
+ cmd,
70
+ capture_output=True,
71
+ text=True,
72
+ timeout=timeout,
73
+ check=False,
74
+ )
75
+ except FileNotFoundError as exc:
76
+ raise LarkCliError("lark-cli not found", cmd=cmd) from exc
77
+ except subprocess.TimeoutExpired as exc:
78
+ raise LarkCliError(f"lark-cli timed out after {timeout}s", cmd=cmd) from exc
79
+
80
+ if completed.returncode != 0:
81
+ detail = (completed.stderr or completed.stdout or "").strip()
82
+ raise LarkCliError(detail or f"lark-cli exited with {completed.returncode}", cmd=cmd)
83
+
84
+ try:
85
+ envelope = json.loads(completed.stdout)
86
+ except json.JSONDecodeError as exc:
87
+ snippet = completed.stdout[:500].replace("\n", "\\n")
88
+ raise LarkCliError(f"lark-cli stdout was not JSON: {snippet}", cmd=cmd) from exc
89
+
90
+ if isinstance(envelope, dict) and envelope.get("ok") is False:
91
+ raise LarkCliError(json.dumps(envelope, ensure_ascii=False), cmd=cmd)
92
+ if not isinstance(envelope, dict):
93
+ raise LarkCliError("lark-cli returned a non-object JSON payload", cmd=cmd)
94
+ return envelope
95
+
96
+
97
+ def envelope_data(envelope: dict[str, Any]) -> dict[str, Any]:
98
+ data = envelope.get("data")
99
+ return data if isinstance(data, dict) else envelope
100
+
101
+
102
+ def emit_success(action: str, data: dict[str, Any], warnings: list[str] | None = None) -> None:
103
+ print(
104
+ json.dumps(
105
+ {
106
+ "ok": True,
107
+ "engine": "lark",
108
+ "action": action,
109
+ "data": data,
110
+ "warnings": warnings or [],
111
+ },
112
+ ensure_ascii=False,
113
+ indent=2,
114
+ )
115
+ )
116
+
117
+
118
+ def emit_error(action: str, message: str, warnings: list[str] | None = None) -> None:
119
+ print(
120
+ json.dumps(
121
+ {
122
+ "ok": False,
123
+ "engine": "lark",
124
+ "action": action,
125
+ "error": message,
126
+ "warnings": warnings or [],
127
+ },
128
+ ensure_ascii=False,
129
+ indent=2,
130
+ )
131
+ )
132
+ sys.exit(1)
133
+
134
+
135
+ def sheet_title(sheet: dict[str, Any]) -> str:
136
+ return str(sheet.get("title") or sheet.get("sheet_name") or sheet.get("name") or "")
137
+
138
+
139
+ def sheet_identifier(sheet: dict[str, Any]) -> str:
140
+ return str(sheet.get("sheet_id") or sheet.get("id") or "")
141
+
142
+
143
+ def sheet_locator(sheet: dict[str, Any]) -> dict[str, str]:
144
+ sid = sheet_identifier(sheet)
145
+ if sid:
146
+ return {"sheet_id": sid}
147
+ title = sheet_title(sheet)
148
+ if title:
149
+ return {"sheet_name": title}
150
+ return {}
151
+
152
+
153
+ def extract_sheets(workbook_data: dict[str, Any]) -> list[dict[str, Any]]:
154
+ sheets = workbook_data.get("sheets")
155
+ if isinstance(sheets, list):
156
+ return [sheet for sheet in sheets if isinstance(sheet, dict)]
157
+ workbook = workbook_data.get("workbook")
158
+ if isinstance(workbook, dict) and isinstance(workbook.get("sheets"), list):
159
+ return [sheet for sheet in workbook["sheets"] if isinstance(sheet, dict)]
160
+ return []
161
+
162
+
163
+ def resolve_target_sheets(
164
+ workbook_data: dict[str, Any],
165
+ *,
166
+ sheet_id: str | None = None,
167
+ sheet_name: str | None = None,
168
+ require_one: bool = False,
169
+ ) -> list[dict[str, Any]]:
170
+ sheets = extract_sheets(workbook_data)
171
+ if sheet_id:
172
+ matches = [sheet for sheet in sheets if sheet_identifier(sheet) == sheet_id]
173
+ elif sheet_name:
174
+ matches = [sheet for sheet in sheets if sheet_title(sheet) == sheet_name]
175
+ else:
176
+ matches = sheets
177
+
178
+ if require_one:
179
+ if len(matches) == 1:
180
+ return matches
181
+ if not matches:
182
+ raise LarkCliError("No matching sheet found")
183
+ raise LarkCliError("Multiple sheets matched; pass --sheet-id or --sheet-name")
184
+ return matches
@@ -19,11 +19,29 @@ import pandas as pd
19
19
 
20
20
  def df_to_sheet(df, name, formats=None):
21
21
  """Pack one DataFrame into one entry of a `+table-put --sheets` payload."""
22
+ packed = json.loads(df.to_json(orient="split", date_format="iso"))
23
+ # The protocol requires string column names. pandas keeps integer labels
24
+ # (e.g. the default RangeIndex columns 0/1/2) as JSON numbers, while the
25
+ # dtypes dict keys get stringified during JSON serialization — the CLI
26
+ # then rejects `columns` ("cannot unmarshal number into … type string")
27
+ # and the dtype lookup would miss anyway. Stringify every key once, and
28
+ # refuse to continue when that conversion silently merges two columns.
29
+ normalized_labels = [str(c) for c in df.columns]
30
+ columns = [str(c) for c in packed["columns"]]
31
+ if normalized_labels != columns:
32
+ columns = normalized_labels
33
+ if len(set(columns)) != len(columns):
34
+ raise ValueError(
35
+ "column labels collide after str() conversion; "
36
+ "rename the DataFrame columns before packing"
37
+ )
38
+ packed["columns"] = columns
39
+ dtype_values = list(df.dtypes)
22
40
  return {
23
41
  "name": name,
24
- **json.loads(df.to_json(orient="split", date_format="iso")),
25
- "dtypes": df.dtypes.astype(str).to_dict(),
26
- **({"formats": formats} if formats else {}),
42
+ **packed,
43
+ "dtypes": {key: str(dtype) for key, dtype in zip(columns, dtype_values)},
44
+ **({"formats": {str(k): v for k, v in formats.items()}} if formats else {}),
27
45
  }
28
46
 
29
47
 
@@ -10,8 +10,7 @@
10
10
 
11
11
  - 读取整份演示文稿的完整 XML(`slides xml_presentations get`)。
12
12
  - 按 `slide_id` 读取单页 XML,可指定历史版本(`slides xml_presentation.slide get`)。
13
- - 列出历史版本、查询回滚任务状态(`slides xml_presentation.history list` /
14
- `revert_status`)。
13
+ - 列出历史版本、查询回滚任务状态(`slides +history-list` / `+history-revert-status`)。
15
14
 
16
15
  ## 核心概念
17
16
 
@@ -31,6 +30,9 @@ wiki 节点,确认 `node.obj_type == "slides"`,再用 `node.obj_token` 作
31
30
  lark-cli wiki spaces get_node --as user --params '{"token":"wiki_token"}'
32
31
  ```
33
32
 
33
+ 例外:`slides +history-list` / `+history-revert-status` 的 `--presentation` 可以直接接收
34
+ wiki URL(自动解析,要求该节点是幻灯片),不需要先手动查询节点。
35
+
34
36
  ### 资源关系
35
37
 
36
38
  ```text
@@ -51,6 +53,18 @@ Slides (演示文稿)
51
53
  - 读取单页 XML:[`lark-slides-xml-presentation-slide-get.md`](references/lark-slides-xml-presentation-slide-get.md)
52
54
  - 历史版本与回滚状态:[`lark-slides-history.md`](references/lark-slides-history.md)
53
55
 
56
+ ## 快捷命令(沙箱内仅以下两个可用,均只读)
57
+
58
+ - `+history-list` — 分页列出历史版本,返回 `entries[]`(含 `history_version_id`、`revision_id`、`edit_time`)
59
+ - `+history-revert-status` — 按 `task_id` 查询回滚任务状态(`status`、`failed_block_tokens`)
60
+
61
+ `--presentation` 接收 `xml_presentation_id`、`/slides/` URL 或可解析为幻灯片的 `/wiki/` URL。
62
+
63
+ ```bash
64
+ lark-cli slides +history-list --as user --presentation "<slides_url_or_token>" --page-size 20
65
+ lark-cli slides +history-revert-status --as user --presentation "<slides_url_or_token>" --task-id "<task_id>"
66
+ ```
67
+
54
68
  ## API(沙箱内仅以下原生 API 调用可用)
55
69
 
56
70
  本沙箱内**仅**以下原生 `<service> <resource> <method>` 组合可用(全部只读),不要再自行拼其它
@@ -71,16 +85,3 @@ lark-cli slides xml_presentations get --as user --params '{"xml_presentation_id"
71
85
  ```bash
72
86
  lark-cli slides xml_presentation.slide get --as user --params '{"xml_presentation_id":"<id>","slide_id":"<slide_id>","revision_id":-1}'
73
87
  ```
74
-
75
- ### xml_presentation.history
76
-
77
- - `list` — 分页列出历史版本,返回 `entries[]`(含 `history_version_id`、`revision_id`、`edit_time`)
78
- - `revert_status` — 查询回滚任务状态(`status`、`failed_block_tokens`)
79
-
80
- ```bash
81
- lark-cli slides xml_presentation.history list --as user --params '<json_params>'
82
- lark-cli slides xml_presentation.history revert_status --as user --params '<json_params>'
83
- ```
84
-
85
- > 参数结构以 `lark-cli schema slides.xml_presentation.history.list` /
86
- > `lark-cli schema slides.xml_presentation.history.revert_status` 输出为准。
@@ -1,14 +1,14 @@
1
1
  # slides history(历史版本与回滚任务状态)
2
2
 
3
3
  用于查看 Slides XML presentation 的历史版本列表,以及查询回滚任务的执行状态。这两个都是
4
- **只读**接口:
4
+ **只读**快捷命令:
5
5
 
6
- - `slides xml_presentation.history list` — 分页列出历史版本。
7
- - `slides xml_presentation.history revert_status` — 按 `task_id` 查询回滚任务状态。
6
+ - `slides +history-list` — 分页列出历史版本。
7
+ - `slides +history-revert-status` — 按 `task_id` 查询回滚任务状态。
8
8
 
9
- > 发起回滚本身(`xml_presentation.history revert`)是高风险写操作,不在本沙箱的只读推荐入口
10
- > 内。用户临时要求回滚而撞到 exit 10 时,按上层 lark-cli SKILL.md 的高风险写审批协议处理。`revert_status`
11
- > 只负责查询一个已存在的回滚任务的执行进度。
9
+ > 发起回滚本身(`+history-revert`)是高风险写操作,不在本沙箱的只读推荐入口
10
+ > 内。用户临时要求回滚而撞到 exit 10 时,按上层 lark-cli SKILL.md 的高风险写审批协议处理。
11
+ > `+history-revert-status` 只负责查询一个已存在的回滚任务的执行进度。
12
12
 
13
13
  `entries[].edit_time` 是 UTC RFC3339 时间字符串(例如 `2026-06-22T12:24:45Z`)。按时间匹配时先将
14
14
  其解析为时间值,再比较先后关系或时间差。
@@ -17,23 +17,35 @@
17
17
 
18
18
  ```bash
19
19
  # 列出历史版本
20
- lark-cli slides xml_presentation.history list --as user --params '<json_params>'
20
+ lark-cli slides +history-list --as user --presentation "<slides_url_or_token>" --page-size 20
21
+
22
+ # 翻页
23
+ lark-cli slides +history-list --as user --presentation "<slides_url_or_token>" --page-size 20 --page-token "<page_token>"
21
24
 
22
25
  # 查询回滚任务状态
23
- lark-cli slides xml_presentation.history revert_status --as user --params '<json_params>'
26
+ lark-cli slides +history-revert-status --as user --presentation "<slides_url_or_token>" --task-id "<task_id>"
24
27
  ```
25
28
 
26
- > 参数结构以 schema 为准,调用前先查看:
27
- > `lark-cli schema slides.xml_presentation.history.list` /
28
- > `lark-cli schema slides.xml_presentation.history.revert_status`。
29
- > `list` 支持分页:无 `page_token` 取第一页,`has_more=true` 时用上一页返回的 `page_token`
30
- > 继续翻页;`revert_status` 需要提供发起回滚时返回的 `task_id`。
29
+ ## 参数
30
+
31
+ | 命令 | 参数 | 必填 | 说明 |
32
+ |-|-|-|-|
33
+ | `+history-list` | `--presentation` | 是 | `xml_presentation_id`、Slides URL,或可解析为 Slides 的 wiki URL |
34
+ | `+history-list` | `--page-size` | 否 | 返回条数,范围 `1-20`,默认 `20` |
35
+ | `+history-list` | `--page-token` | 否 | 上一页返回的 `page_token` |
36
+ | `+history-revert-status` | `--presentation` | 是 | 同一个演示文稿 |
37
+ | `+history-revert-status` | `--task-id` | 是 | 发起回滚时返回的 `task_id` |
38
+
39
+ > `+history-list` 支持分页:无 `--page-token` 取第一页,`has_more=true` 时用上一页返回的
40
+ > `page_token` 继续翻页。`--presentation` 收到 wiki URL 时自动解析出真实演示文稿 ID,
41
+ > 无需先手动查询 wiki 节点。
31
42
 
32
43
  ## 按 revision_id 或时间点定位版本
33
44
 
34
- 当用户说"看看昨天下午 3 点的版本""revision_id=42 是哪一条"这类需求时,用 `list` 定位:
45
+ 当用户说"看看昨天下午 3 点的版本""revision_id=42 是哪一条"这类需求时,用 `+history-list` 定位:
35
46
 
36
- 1. 执行 `list` 获取第一页历史记录;只有 `has_more=true` 且还需要更多候选时才继续翻页。
47
+ 1. 执行 `slides +history-list --presentation <presentation>` 获取第一页历史记录;只有
48
+ `has_more=true` 且还需要更多候选时才继续传 `--page-token` 翻页。
37
49
  2. 如果用户给出 `revision_id`:先筛选当前页中 `entries[].revision_id == 用户给出的 revision_id`。
38
50
  如果未命中且 `has_more=true`,继续拉下一页;如果已经命中候选,最多额外再拉一页,补齐同一个
39
51
  `revision_id` 可能跨页出现的相邻 `history_version_id`。若用户同时给出目标时间,在候选里选择
@@ -56,17 +68,17 @@ lark-cli slides xml_presentation.history revert_status --as user --params '<json
56
68
  已发起的回滚任务返回 `task_id` 与 `status` 后:
57
69
 
58
70
  1. 如果 `status` 不是 `running`,不再调用状态接口。
59
- 2. 如果 `status` 是 `running`,等待响应中的 `poll_after_ms` 后调用 `revert_status`;`poll_after_ms`
60
- 缺失、为 `0` 或非法时,默认等待 10 秒。
71
+ 2. 如果 `status` 是 `running`,等待响应中的 `poll_after_ms` 后调用 `+history-revert-status`;
72
+ `poll_after_ms` 缺失、为 `0` 或非法时,默认等待 10 秒。
61
73
  3. 状态查询返回 `running` 时继续轮询;返回 `done`、`partial_failed` 或 `failed` 时停止。
62
74
  4. 默认最多轮询 5 分钟。达到上限后停止轮询,向用户说明任务仍在运行并返回 `task_id`,不得将其
63
75
  描述为回滚失败。
64
- 5. 状态查询出现临时错误时,按相同间隔最多连续重试 3 次;只重试 `revert_status`。
76
+ 5. 状态查询出现临时错误时,按相同间隔最多连续重试 3 次;只重试 `+history-revert-status`。
65
77
  6. `partial_failed` 或 `failed` 时展示 `failed_block_tokens`。
66
78
 
67
79
  ## 返回值要点
68
80
 
69
- `list` 返回:
81
+ `+history-list` 返回:
70
82
 
71
83
  ```json
72
84
  {
@@ -86,7 +98,7 @@ lark-cli slides xml_presentation.history revert_status --as user --params '<json
86
98
  }
87
99
  ```
88
100
 
89
- `revert_status` 返回:
101
+ `+history-revert-status` 返回:
90
102
 
91
103
  ```json
92
104
  {
@@ -48,7 +48,7 @@ lark-cli slides xml_presentation.slide get --as user --params '{
48
48
  ```bash
49
49
  lark-cli slides xml_presentation.slide get --as user \
50
50
  --params '{"xml_presentation_id":"slides_example_presentation_id","slide_id":"slide_example_id"}' \
51
- | jq -r '.data.slide.content'
51
+ --jq '.data.slide.content'
52
52
  ```
53
53
 
54
54
  ### 读指定历史版本
@@ -99,7 +99,7 @@ lark-cli slides xml_presentation.slide get --as user --params '{
99
99
  ```bash
100
100
  lark-cli slides xml_presentation.slide get --as user \
101
101
  --params "{\"xml_presentation_id\":\"$PID\",\"slide_id\":\"$SID\"}" \
102
- | jq -r '.data.slide.content' | grep -oE 'id="[^"]+"' | sed 's/id="//;s/"//'
102
+ --jq '.data.slide.content' | grep -oE 'id="[^"]+"' | sed 's/id="//;s/"//'
103
103
  ```
104
104
 
105
105
  ## 相关命令
@@ -21,8 +21,7 @@
21
21
  |---|---|
22
22
  | 查看画板内容 / 导出图片 | [`+export --output-type preview`](references/lark-whiteboard-export.md) |
23
23
  | 导出 SVG 矢量图 | [`+export --output-type svg`](references/lark-whiteboard-export.md) |
24
- | 获取画板的 Mermaid/PlantUML 代码 | [`+export --output-type source`](references/lark-whiteboard-export.md) |
25
- | 检查画板是否由代码绘制 | [`+export --output-type source`](references/lark-whiteboard-export.md) |
24
+ | 提取画板的 Mermaid/PlantUML 源码 | [`+export --output-type source`](references/lark-whiteboard-export.md) |
26
25
  | 导出画板原始节点结构(OpenAPI 原生格式) | [`+export --output-type raw`](references/lark-whiteboard-export.md) |
27
26
 
28
27
  ## 获取画板 token
@@ -54,6 +54,17 @@ lark-cli wiki +node-get \
54
54
  - Prefer this shortcut over the raw `wiki spaces get_node` API for every node-resolution step, including when you only need `space_id` — read it from `data.space_id` (the raw API nests it under `data.node.space_id`).
55
55
  - A truncated `node_token` is rejected outright rather than sent as a lookup. If the token you were given looks cut off, ask for the full URL or token instead of retrying variants.
56
56
 
57
+ ## Terminal business errors
58
+
59
+ These HTTP 200 responses carry a non-zero business code and are not retryable with the same input:
60
+
61
+ | Code | Meaning | Required action |
62
+ |------|---------|-----------------|
63
+ | `131006` | The current user identity lacks access to the Wiki node or space | This is resource access, not app scope authorization. Do not retry the same request or switch identity as trial and error; ask the node owner or wiki administrator to grant read access, or use an accessible resource |
64
+ | `131012` | The Wiki node has been deleted | Do not retry the same node token; rediscover the node or ask for a current Wiki link |
65
+ | `131013` | The resource token is invalid | Do not retry or switch identity; correct the URL/token |
66
+ | `131014` | The document is not mounted in Wiki | Stop Wiki resolution; use the corresponding docs/sheets/base/drive command, or provide a Wiki URL/node_token |
67
+
57
68
  ## Required Scope
58
69
 
59
70
  `wiki:node:retrieve`
@@ -87,7 +87,7 @@ lark-cli wiki +node-list --space-id 6946843325487912356 --parent-node-token wikc
87
87
  - `--space-id my_library` is a per-user alias and only valid with `--as user`.
88
88
  - `--space-id` is a numeric wiki `space_id`. Do not pass a wiki URL, wiki node token, document token, or title. Use `lark-cli wiki +space-list --as user` to discover it.
89
89
  - `--parent-node-token` must resolve to a wiki node token. If you have a docx/sheet/base/file URL, first run `lark-cli wiki +node-get --node-token <url> --as user` and use the returned `node_token`.
90
- - Treat `invalid_parameters` (`space_id is not int`, `invalid page_token`), `not_found` (`node not found by parent node token`), and `permission_denied` as terminal for the current arguments. Fix the argument or permission before retrying.
90
+ - Treat `invalid_parameters` (`space_id is not int`, `invalid page_token`), `not_found` (`node not found by parent node token`), and `permission_denied` as terminal for the current arguments. For `131006 permission_denied`, the user identity lacks access to the target space or parent node; this is resource access, not app scope authorization. Do not retry the same request or switch identity as trial and error. Ask the resource owner or wiki administrator to grant read access, or use an accessible resource.
91
91
  - For `rate_limit`, stop immediate retries and retry later with exponential backoff or a smaller `--page-limit`.
92
92
 
93
93
  ## Required Scope
@@ -1,40 +0,0 @@
1
- ---
2
- name: dev-channel-probe
3
- description: "[临时] sandbox-skills dev 发布通道连通性探针,验证完即删。不面向用户,已标 control-by-feature-ab 因此不会被 agent 加载。"
4
- control-by-feature-ab: true
5
- ---
6
-
7
- # dev 通道探针
8
-
9
- 验证 sandbox-skills 的 dev 发布通道端到端连通:
10
-
11
- ```
12
- develop 分支合入
13
- → publish-sandbox-skills-dev.sh 自动发 <base>-dev.<sha> + dist-tag dev
14
- → BOE 沙箱(FORCE_FRAMEWORK_ENVIRONMENT=boe 且未配 TCC tgz-key)走 @dev
15
- → miaoda skills sync 落盘
16
- ```
17
-
18
- ## 判据
19
-
20
- 沙箱内出现以下路径即通道连通:
21
-
22
- ```
23
- .agent/skills/dev-channel-probe/SKILL.md
24
- ```
25
-
26
- 本 skill 放在 `shared/` 层,会分发到 miaoda / miaoda-modern / miaoda-design
27
- 三条业务线,因此顺带验证 shared 层的分发路径。
28
-
29
- ## 为什么不会被 agent 加载
30
-
31
- frontmatter 标了 `control-by-feature-ab: true`。该标记的语义是「只有出现在 TCC
32
- `feature=skill` 的 `skillNames` 白名单里才加载」,而本 skill 不会被加进任何白名单,
33
- 所以它只存在于磁盘、不进 agent 上下文、不消耗 token。
34
-
35
- 未标该字段的 skill 是无条件加载的 —— 这也是本探针刻意标它的原因。
36
-
37
- ## 清理
38
-
39
- 验证通过后**尽快删除本目录**。它不提供任何用户价值,长期留存只会让 skill 列表
40
- 和后续排查变噪。