chore: harden feishu export delivery

This commit is contained in:
whzy 2026-06-15 22:03:52 +08:00
parent d8cb4aa9d2
commit 1af54e015a
12 changed files with 531 additions and 98 deletions

View File

@ -0,0 +1,68 @@
# Feishu Bitable Dry-Run Schema
`gitlink-cli feishu` currently generates Bitable schema and records locally.
It does not call Feishu Bitable OpenAPI.
## Commands
```bash
gitlink-cli feishu +bitable-schema --format markdown
gitlink-cli feishu +bitable-records --from-workflow-json report.json --format json
```
## Tables
Default tables:
```text
issues
prs
contributors
reports
```
## Record Semantics
Records are summary records derived from `workflow +repo-report` JSON.
They are not one row per GitLink issue or one row per GitLink pull request.
Current behavior:
```text
reports: one summary row per repo report
issues: summary buckets by issue type and priority
prs: summary buckets by change type and risk
contributors: reserved schema; records are empty unless workflow JSON later includes contributor details
```
## Real Write Boundary
Not implemented:
```text
Bitable OpenAPI create
Bitable OpenAPI batch create
Bitable update
Bitable upsert
Base creation
table creation
view creation
field creation
person/open_id mapping
```
Real Bitable writes require a separate design for:
```text
app authentication
table IDs
record unique keys
search-before-update
pagination
partial failure handling
rate limits
permission diagnostics
```

View File

@ -1,27 +1,39 @@
# Feishu Integration
`gitlink-cli feishu` exports local GitLink workflow JSON to Feishu collaboration surfaces.
`gitlink-cli feishu` exports local GitLink workflow JSON to Feishu.
The commands are intentionally one-way:
The stable command path is intentionally narrow:
```text
workflow JSON -> local preview
workflow JSON -> Feishu bot card
workflow JSON -> Feishu DocX / Wiki report
workflow JSON -> Bitable-ready dry-run records
workflow JSON -> Feishu custom bot card
workflow JSON -> weekly report
workflow JSON -> Bitable schema / records dry-run
```
## Stable Commands
```text
gitlink-cli feishu +bot-test
gitlink-cli feishu +notify
gitlink-cli feishu +weekly-report
gitlink-cli feishu +bitable-schema
gitlink-cli feishu +bitable-records
```
`feishu +doc-export` exists as an experimental command. It uses Feishu self-built app OpenAPI and is not part of the clean first-path workflow.
## Safety Model
Default behavior is local preview.
- Default behavior is local preview.
- Real Feishu bot delivery requires `--send`.
- `--send` and `--dry-run` cannot be used together.
- Webhook URLs are redacted in command output.
- Secrets and tokens are never intentionally printed.
- The stable commands do not write to GitLink resources.
- Bitable commands are dry-run only and do not call Bitable OpenAPI.
Network operations require `--send`.
`--send` and `--dry-run` cannot be used together.
The implementation does not write to GitLink resources, does not close issues, does not comment on PRs, and does not merge code.
## Custom Bot
## Custom Bot Setup
Use a Feishu custom group bot for notification cards.
@ -32,26 +44,41 @@ $env:FEISHU_WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/..."
$env:FEISHU_WEBHOOK_SECRET="optional signing secret"
```
Preview:
Preview a test card:
```bash
gitlink-cli feishu +bot-test --format json
```
Send:
Send a test card:
```bash
gitlink-cli feishu +bot-test --send --format table
```
Send a workflow report card:
## Workflow Report Card
Generate workflow JSON:
```bash
gitlink-cli workflow +repo-report --owner Gitlink --repo gitlink-cli --format json > report.json
```
On Windows PowerShell, redirected files may be written with UTF-16 encoding. `feishu` workflow JSON readers accept UTF-8 and UTF-16 BOM files so the redirected output above can be consumed directly.
Preview a card:
```bash
gitlink-cli feishu +notify --from-workflow-json report.json --format json
```
Send a card:
```bash
gitlink-cli feishu +notify --from-workflow-json report.json --send --format table
```
Send a card with a DocX or Wiki link:
Send a card with an existing Feishu document or Wiki link:
```bash
gitlink-cli feishu +notify \
@ -61,9 +88,39 @@ gitlink-cli feishu +notify \
--format table
```
## DocX / Wiki Export
## Weekly Report
Use a Feishu self-built app for document export.
Render markdown:
```bash
gitlink-cli feishu +weekly-report --from-workflow-json report.json --format markdown
```
Send a weekly summary card:
```bash
gitlink-cli feishu +weekly-report --from-workflow-json report.json --send --format table
```
## Bitable Dry Run
Generate recommended table schemas:
```bash
gitlink-cli feishu +bitable-schema --format markdown
```
Generate Bitable-ready records:
```bash
gitlink-cli feishu +bitable-records --from-workflow-json report.json --format json
```
These records are summary records derived from workflow repo-report JSON. They are not a per-issue or per-PR synchronization.
## Experimental DocX / Wiki Export
`feishu +doc-export` is experimental because it uses Feishu self-built app credentials and writes to DocX / Wiki through OpenAPI.
Environment:
@ -81,7 +138,7 @@ gitlink-cli feishu +doc-export \
--format markdown
```
Append report blocks to a Wiki-backed DocX:
Write to DocX / Wiki:
```bash
gitlink-cli feishu +doc-export \
@ -91,40 +148,10 @@ gitlink-cli feishu +doc-export \
--format table
```
Create a new DocX in a folder:
```bash
gitlink-cli feishu +doc-export \
--from-workflow-json report.json \
--folder-token "<folder_token>" \
--title "GitLink workflow report" \
--send \
--format table
```
Required Feishu setup:
```text
1. The self-built app must have DocX / Drive application scopes.
2. The app permission version must be published and approved.
3. The target Wiki / DocX / folder must grant the app write permission.
1. The self-built app must have approved DocX / Drive scopes.
2. The target Wiki / DocX / folder must grant the app write permission.
3. If Feishu returns 1770032: forBidden, credentials are valid but the app cannot write the target document.
```
If Feishu returns `1770032: forBidden`, the token is valid but the app cannot write to the target document. Grant the app access to that Wiki/DocX page or use a folder where the app can create documents.
## Bitable Dry Run
Generate recommended table schemas:
```bash
gitlink-cli feishu +bitable-schema --format markdown
```
Generate Bitable-ready records:
```bash
gitlink-cli feishu +bitable-records --from-workflow-json report.json --format json
```
These commands do not call Bitable OpenAPI and do not create, update, or upsert records.

75
docs/feishu-security.md Normal file
View File

@ -0,0 +1,75 @@
# Feishu Security Notes
## Default Behavior
All Feishu shortcut commands default to local preview.
Real network writes require `--send`.
`--send` and `--dry-run` cannot be used together.
## Secrets
Supported environment variables:
```text
FEISHU_WEBHOOK_URL
FEISHU_WEBHOOK_SECRET
FEISHU_APP_ID experimental doc-export only
FEISHU_APP_SECRET experimental doc-export only
```
Do not commit real webhook URLs, app secrets, access tokens, Base app tokens, table IDs, or document tokens.
Command output redacts webhook URLs. Tests use fake webhook IDs.
## Stable Surface
The stable surface uses Feishu custom bot webhooks:
```text
feishu +bot-test
feishu +notify
feishu +weekly-report
```
These commands can send Feishu cards, but they do not read or write Feishu documents, tables, users, or groups.
## Dry-Run Surface
The Bitable commands are local only:
```text
feishu +bitable-schema
feishu +bitable-records
```
They do not call Feishu OpenAPI and cannot create, update, or upsert Bitable records.
## Experimental Surface
`feishu +doc-export` is experimental. It uses:
```text
app_id
app_secret
tenant_access_token
Wiki OpenAPI
DocX OpenAPI
```
It should not be treated as part of the stable clean export path. If used, grant the self-built app only the minimum required document permissions.
## Non-Goals
```text
BotBuilder integration
Feishu Robot Assistant workflows
automatic Feishu permission changes
GitLink remote writes
GitLink comments
Issue closure
merge actions
real Bitable writes
```

View File

@ -1,24 +1,48 @@
# Feishu Export Examples
## 1. Generate Workflow JSON
## Stable Workflow
### 1. Generate Workflow JSON
```bash
gitlink-cli workflow +repo-report --owner Gitlink --repo gitlink-cli --format json > report.json
```
## 2. Preview Notification Card
### 2. Preview Notification Card
```bash
gitlink-cli feishu +notify --from-workflow-json report.json --format json
```
## 3. Send Notification Card
### 3. Send Notification Card
```bash
gitlink-cli feishu +notify --from-workflow-json report.json --send --format table
```
## 4. Preview Wiki Export
### 4. Render Weekly Report
```bash
gitlink-cli feishu +weekly-report --from-workflow-json report.json --format markdown
```
### 5. Generate Bitable Schema
```bash
gitlink-cli feishu +bitable-schema --format markdown
```
### 6. Generate Bitable Records
```bash
gitlink-cli feishu +bitable-records --from-workflow-json report.json --format json
```
## Experimental DocX / Wiki Export
`+doc-export` is available for Feishu self-built app experiments. It is not part of the stable clean export path.
Preview:
```bash
gitlink-cli feishu +doc-export \
@ -27,7 +51,7 @@ gitlink-cli feishu +doc-export \
--format markdown
```
## 5. Export To Wiki
Write:
```bash
gitlink-cli feishu +doc-export \
@ -37,9 +61,3 @@ gitlink-cli feishu +doc-export \
--format table
```
## 6. Generate Bitable Records
```bash
gitlink-cli feishu +bitable-records --from-workflow-json report.json --format json
```

View File

@ -266,7 +266,8 @@ But it is incomplete for a "Feishu collaboration export" feature because it does
Required design adjustment:
```text
Add doc-export with DocX/Wiki support as the first Open Platform custom-app integration.
Keep real Bitable writes after doc-export.
Keep custom bot as the low-friction smoke test path.
Keep custom bot as the low-friction stable smoke test path.
Keep Bitable as dry-run in the stable path.
Treat doc-export with DocX/Wiki support as experimental because it uses self-built app OpenAPI and document write permissions.
Keep real Bitable writes out of scope.
```

View File

@ -266,7 +266,7 @@ type BitableRecord struct {
Official Feishu Open Platform docs show that cloud document integration belongs to the self-built app flow, not the custom bot flow.
Add this after the custom bot MVP:
Experimental command:
```text
feishu +doc-export
@ -310,13 +310,19 @@ Implementation notes:
- Mock all HTTP tests.
- Do not implement document sharing or permission changes in the first doc export pass.
Recommended product flow:
Stable product flow:
```text
workflow +repo-report -> feishu +weekly-report -> feishu +notify --doc-url -> feishu +bitable-records
```
Experimental product flow:
```text
workflow +repo-report -> feishu +doc-export --wiki-url -> feishu +notify --doc-url -> feishu +bitable-records
```
This should land before any real Bitable write because DocX export has clearer value and simpler consistency semantics than Bitable upsert.
DocX export should remain clearly marked as experimental until tenant permissions, scopes, and document-write behavior are stable.
## Tests To Add

View File

@ -30,7 +30,7 @@ gitlink-cli feishu +bitable-records
- Added project activity card generation from workflow JSON.
- Added weekly report rendering from workflow JSON.
- Added `--doc-url` support for notification cards.
- Added `feishu +doc-export` for Feishu DocX / Wiki export.
- Added experimental `feishu +doc-export` for Feishu DocX / Wiki export.
- Added self-built app tenant token acquisition.
- Added Wiki node resolution.
- Added DocX block creation client.
@ -109,6 +109,20 @@ Design now treats Feishu Knowledge Base / Wiki pages as a project showcase and r
workflow JSON -> DocX/Wiki report -> bot card with doc URL -> Bitable dry-run records
```
After scope review, DocX / Wiki export is explicitly experimental and not part of the stable clean workflow.
Stable path:
```text
workflow JSON -> bot card / weekly report / Bitable dry-run records
```
Experimental path:
```text
workflow JSON -> DocX/Wiki export through self-built app OpenAPI
```
## Tests
Commands run:
@ -147,9 +161,13 @@ document permission modification
DocX content write
```
Note: experimental `doc-export` can attempt DocX block writes when explicitly invoked with `--send`, but it remains outside the stable clean export path.
## Next Engineering Step
Complete the Feishu document permission setup and rerun:
For stable delivery, continue validating custom bot delivery, weekly reports, and Bitable dry-run output.
For experimental DocX/Wiki export, complete the Feishu document permission setup and rerun:
```text
gitlink-cli feishu +doc-export --from-workflow-json report.json --wiki-url <wiki_url> --send --format table

View File

@ -0,0 +1,116 @@
# Feishu Test Enterprise Smoke Report
Date: 2026-06-15
Branch:
```text
feat/feishu-export-clean
```
## Scope
This smoke run validates the stable Feishu export path in a real Feishu test enterprise.
Stable path:
```text
bot-test
notify
weekly-report
bitable-schema
bitable-records
```
Experimental path:
```text
doc-export
```
Secrets, full webhook URLs, app secrets, and real Wiki tokens are not recorded in this report.
## Local Verification
Commands:
```bash
gofmt -w shortcuts/feishu
git diff --check
go test ./shortcuts/feishu ./shortcuts ./...
```
Result:
```text
passed
```
## Windows JSON Encoding Check
The smoke run generated workflow JSON through Windows PowerShell redirection.
Initial result before fix:
```text
parse workflow JSON: invalid character 'ÿ' looking for beginning of value
```
Fix:
```text
workflow JSON reader now accepts UTF-8 BOM, UTF-16LE BOM, and UTF-16BE BOM.
```
Post-fix result:
```text
workflow JSON generated by PowerShell redirection is accepted.
```
## Real Feishu Bot Smoke
| Step | Result | Notes |
| --- | --- | --- |
| bot-test preview | pass | local preview |
| bot-test send | pass | HTTP 200 / Feishu code 0 |
| notify preview | pass | local preview |
| notify send | pass | HTTP 200 / Feishu code 0 |
| notify send with doc URL | pass | HTTP 200 / Feishu code 0 |
| weekly-report markdown | pass | markdown rendered |
| weekly-report send | pass | HTTP 200 / Feishu code 0 |
## Bitable Dry-Run Smoke
| Step | Result | Notes |
| --- | --- | --- |
| bitable-schema json | pass | JSON schema generated |
| bitable-schema markdown | pass | Markdown schema generated |
| bitable-records json | pass | dry-run records generated |
| bitable-records table | pass | reports/issues/prs records summarized; contributors reserved |
## Experimental Doc Export Smoke
| Step | Result | Notes |
| --- | --- | --- |
| doc-export preview | pass | target resolved as Wiki append preview |
| doc-export send | expected failure | Feishu returned HTTP 403 / code 1770032 / forBidden |
Interpretation:
```text
The self-built app credentials can get tenant_access_token and resolve the Wiki node, but the app cannot write blocks to the target Wiki-backed DocX page yet.
```
Required Feishu-side action:
```text
Grant the self-built app approved DocX/Drive scopes and write access to the target Wiki/DocX page or use a writable folder token.
```
## Conclusion
Stable Feishu export path is working in the test enterprise.
`doc-export` remains experimental and permission-blocked for real writes.

View File

@ -76,7 +76,7 @@ func newWeeklyReportShortcut() *common.Shortcut {
func newDocExportShortcut() *common.Shortcut {
return &common.Shortcut{
Name: "doc-export",
Description: "Preview or export a workflow report to Feishu DocX or Wiki",
Description: "Experimental: preview or export a workflow report to Feishu DocX or Wiki",
Flags: []common.Flag{
{Name: "from-workflow-json", Usage: "Read workflow repo report JSON from a file", Required: true},
{Name: "title", Usage: "Document title"},

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
@ -120,6 +121,31 @@ func TestReadWorkflowReportSupportsInputFixture(t *testing.T) {
}
}
func TestReadWorkflowReportSupportsPowerShellUTF16Redirect(t *testing.T) {
raw, err := os.ReadFile(filepath.Join("..", "workflow", "testdata", "repo_report.json"))
if err != nil {
t.Fatalf("read fixture: %v", err)
}
utf16Data := []byte{0xFF, 0xFE}
for _, r := range string(raw) {
if r > 0xFFFF {
t.Fatalf("fixture contains non-BMP rune %q", r)
}
utf16Data = append(utf16Data, byte(r), byte(r>>8))
}
path := filepath.Join(t.TempDir(), "report.json")
if err := os.WriteFile(path, utf16Data, 0600); err != nil {
t.Fatalf("write UTF-16 fixture: %v", err)
}
report, err := readWorkflowReport(path, "en")
if err != nil {
t.Fatalf("readWorkflowReport returned error: %v", err)
}
if report.Repository != "Gitlink/gitlink-cli" {
t.Fatalf("Repository = %q", report.Repository)
}
}
func TestBitableSchemaAndRecords(t *testing.T) {
report, err := readWorkflowReport(filepath.Join("..", "workflow", "testdata", "repo_report.json"), "en")
if err != nil {

View File

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"strings"
"unicode/utf16"
"github.com/gitlink-org/gitlink-cli/shortcuts/workflow"
)
@ -14,6 +15,10 @@ func readWorkflowReport(path string, lang string) (workflow.RepoReportResult, er
if err != nil {
return workflow.RepoReportResult{}, fmt.Errorf("read workflow JSON: %w", err)
}
data, err = normalizeJSONBytes(data)
if err != nil {
return workflow.RepoReportResult{}, err
}
data, err = unwrapWorkflowJSON(data)
if err != nil {
return workflow.RepoReportResult{}, err
@ -32,6 +37,34 @@ func readWorkflowReport(path string, lang string) (workflow.RepoReportResult, er
return workflow.RepoReportResult{}, fmt.Errorf("parse workflow JSON: expected workflow RepoReportResult or RepoReportInput")
}
func normalizeJSONBytes(data []byte) ([]byte, error) {
if len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF {
return data[3:], nil
}
if len(data) >= 2 && data[0] == 0xFF && data[1] == 0xFE {
return decodeUTF16(data[2:], true)
}
if len(data) >= 2 && data[0] == 0xFE && data[1] == 0xFF {
return decodeUTF16(data[2:], false)
}
return data, nil
}
func decodeUTF16(data []byte, littleEndian bool) ([]byte, error) {
if len(data)%2 != 0 {
return nil, fmt.Errorf("parse workflow JSON: invalid UTF-16 byte length")
}
words := make([]uint16, 0, len(data)/2)
for i := 0; i < len(data); i += 2 {
if littleEndian {
words = append(words, uint16(data[i])|uint16(data[i+1])<<8)
} else {
words = append(words, uint16(data[i])<<8|uint16(data[i+1]))
}
}
return []byte(string(utf16.Decode(words))), nil
}
func unwrapWorkflowJSON(data []byte) ([]byte, error) {
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {

View File

@ -1,7 +1,7 @@
---
name: gitlink-feishu
version: 1.0.0
description: "Export GitLink workflow JSON to Feishu bot cards, DocX/Wiki reports, and Bitable-ready dry-run records."
description: "Export GitLink workflow JSON to Feishu custom bot cards, weekly reports, and Bitable-ready dry-run records."
metadata:
requires:
bins: ["gitlink-cli"]
@ -10,47 +10,58 @@ metadata:
# gitlink-feishu
Use this skill when a user needs to send or export GitLink workflow analysis to Feishu.
Use this skill when a user needs to export GitLink workflow analysis into Feishu.
## Rules
## Purpose
- Prefer local preview first.
- Use `--send` only when the user explicitly wants a Feishu network write.
- Never use BotBuilder or Robot Assistant workflows.
- Do not write to GitLink resources.
- Do not print webhook URLs, app secrets, or access tokens.
- Use `+bitable-records` for dry-run output only; do not claim that Bitable has been written.
Stable path:
## Workflow
```text
workflow JSON -> Feishu bot card / weekly report / Bitable dry-run records
```
Generate workflow JSON:
Experimental path:
```text
workflow JSON -> Feishu DocX / Wiki export
```
## Inputs
Workflow JSON should usually come from:
```bash
gitlink-cli workflow +repo-report --owner <owner> --repo <repo> --format json > report.json
```
Preview a Feishu card:
## Safety Rules
- Preview first.
- Use `--send` only when the user explicitly wants a Feishu network write.
- Never use BotBuilder or Robot Assistant workflows.
- Do not write to GitLink resources.
- Do not print webhook URLs, app secrets, access tokens, or table tokens.
- Treat `+bitable-schema` and `+bitable-records` as local dry-run commands only.
- Treat `+doc-export` as experimental because it uses self-built app OpenAPI and document write permissions.
## Preview Flow
Preview a card:
```bash
gitlink-cli feishu +notify --from-workflow-json report.json --format json
```
Send a Feishu card:
Render a weekly report:
```bash
gitlink-cli feishu +notify --from-workflow-json report.json --send --format table
gitlink-cli feishu +weekly-report --from-workflow-json report.json --format markdown
```
Preview a document export:
Generate Bitable schemas:
```bash
gitlink-cli feishu +doc-export --from-workflow-json report.json --wiki-url "<wiki_url>" --format markdown
```
Export to DocX or Wiki:
```bash
gitlink-cli feishu +doc-export --from-workflow-json report.json --wiki-url "<wiki_url>" --send --format table
gitlink-cli feishu +bitable-schema --format markdown
```
Generate Bitable-ready records:
@ -59,7 +70,7 @@ Generate Bitable-ready records:
gitlink-cli feishu +bitable-records --from-workflow-json report.json --format json
```
## Feishu Setup
## Send Flow
Custom bot commands need:
@ -68,12 +79,46 @@ FEISHU_WEBHOOK_URL
FEISHU_WEBHOOK_SECRET optional
```
DocX/Wiki export needs:
Send a card:
```bash
gitlink-cli feishu +notify --from-workflow-json report.json --send --format table
```
Send a weekly report card:
```bash
gitlink-cli feishu +weekly-report --from-workflow-json report.json --send --format table
```
## Experimental Doc Export
DocX / Wiki export needs:
```text
FEISHU_APP_ID
FEISHU_APP_SECRET
```
The self-built app must also have permission to write the target DocX/Wiki page or folder.
Preview only:
```bash
gitlink-cli feishu +doc-export --from-workflow-json report.json --wiki-url "<wiki_url>" --format markdown
```
Write to Feishu:
```bash
gitlink-cli feishu +doc-export --from-workflow-json report.json --wiki-url "<wiki_url>" --send --format table
```
If Feishu returns `1770032: forBidden`, the app token is valid but the app cannot write to the target DocX/Wiki page or folder.
## Non-Goals
- No GitLink remote writes.
- No GitLink comments, issue closure, merge, or webhook creation.
- No Bitable real writes in the stable path.
- No BotBuilder integration.
- No automatic Feishu permission changes.