Skill: ruoruochen/release-package (version 1.0.0)

--- name: Storage Manager description: Feishu Bitable Storage Manager - Integrated tool for item storage, retrieval, and location updates read_when: - Need to record item storage locations - Need to find item locations - Need to update item locations - Need to manage personal item storage metadata: {"clawdbot":{"emoji":"box","requires":{"bins":["python3"]}}} allowed-tools: Bash(storage-manager:*) --- # Storage Manager Intelligent storage management tool based on Feishu Bitable. ## Core Features 1. Item Storage (add_storage_item) 2. Item Retrieval (search_storage_item) 3. Location Update (update_storage_location) ## Usage - storage-manager add [item] [location] - storage-manager search [item] - storage-manager update [item] [new_location] ## Requirements - Python 3.6+ - requests >= 2.25.0 - Feishu app credentials

path: README.txt
content: 收纳管家技能包结构
================
1. SKILL.md - 技能核心文档
2. storage_manager.py - 核心功能模块
3. cli.py - 命令行接口
4. install.sh - 安装脚本
5. package.json - 发布配置
6. test_basic.py - 测试文件
7. config.example.env - 配置示例

核心功能：
- add_storage_item: 物品入库
- search_storage_item: 物品检索
- update_storage_location: 位置更新

依赖：
- Python 3.6+
- requests >= 2.25.0


path: SKILL-en.md
content: ---
name: Storage Manager
description: Feishu Bitable Storage Manager - Integrated tool for item storage, retrieval, and location updates
read_when:
  - Need to record item storage locations
  - Need to find item locations
  - Need to update item locations
  - Need to manage personal item storage
metadata: {"clawdbot":{"emoji":"🗃️","requires":{"bins":["python3"]}}}
allowed-tools: Bash(storage-manager:*)
---

# Storage Manager

## Overview

Intelligent storage management tool based on Feishu Bitable, integrating three core functions: item storage, retrieval, and location updates.

## Core Features

### 1. Item Storage Tool (add_storage_item)
- **Trigger:** User sends image with location description (e.g., "this goes under the bed")
- **Input parameters:**
  - item_name (String): Item name
  - location (String): Storage location
  - image_path (String, Optional): Local image path or URL
- **Logic:**
  a. If image exists, upload to Feishu to get file_token
  b. Call Feishu Bitable API to create new record
- **Return:** Success status and confirmation message

### 2. Item Retrieval Tool (search_storage_item)
- **Trigger:** User asks for item location (e.g., "Where is my passport?")
- **Input parameters:**
  - query_item (String): Item name to find
- **Logic:**
  a. Call Feishu Bitable search API
  b. Use fuzzy matching to find records containing query_item
- **Return:** Location and Image link of the item

### 3. Location Update Tool (update_storage_location)
- **Trigger:** User indicates item moved to new location (e.g., "I moved passport to backpack")
- **Input parameters:**
  - item_name (String): Item name to move
  - new_location (String): New storage location
- **Logic:**
  a. First call retrieval API to get record_id
  b. Call Feishu Bitable update API
- **Return:** Update success confirmation

## Configuration Requirements

### Feishu App Configuration
1. Create Feishu custom app
2. Enable permissions:
   - Bitable related permissions
   - File upload permissions
   - Document access permissions

### Environment Variables
- `FEISHU_APP_ID`: Feishu App ID
- `FEISHU_APP_SECRET`: Feishu App Secret
- `FEISHU_BITABLE_TOKEN`: Bitable app token
- `FEISHU_TABLE_ID`: Table ID

## Usage Examples

### Item Storage
```bash
# Record item location (with image)
storage-manager add "Medicine" "TV cabinet left drawer" --image="/path/to/image.jpg"

# Record item location (no image)
storage-manager add "Passport" "Backpack inner layer"
```

### Item Retrieval
```bash
# Find item location
storage-manager search "Passport"
```

### Location Update
```bash
# Update item location
storage-manager update "Passport" "Office desk drawer"
```

## Data Structure

### Bitable Field Structure
1. **Item_Name** - Text (Item name)
2. **Location** - Text (Storage location)
3. **Image** - Attachment (Item image)

## Technical Implementation

Based on Feishu Open Platform API, implementing complete item location management. Supports image upload, fuzzy search, location updates.

path: cli.py
content: #!/usr/bin/env python3
"""
收纳管家命令行接口
"""

import sys
import os

# 添加技能目录到路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from storage_manager import main

if __name__ == "__main__":
    main()

path: install.sh
content: #!/bin/bash
# 收纳管家安装脚本

set -e

echo "🚀 开始安装收纳管家技能..."
echo "=========================================="

# 检查Python环境
if ! command -v python3 &> /dev/null; then
    echo "❌ 需要Python 3环境"
    exit 1
fi

# 检查必要的Python包
echo "检查Python依赖..."
python3 -c "import requests" 2>/dev/null || {
    echo "安装requests库..."
    pip3 install requests
}

# 创建可执行文件
echo "创建可执行文件..."
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
chmod +x "$SCRIPT_DIR/cli.py"
chmod +x "$SCRIPT_DIR/storage_manager.py"

# 创建符号链接
if [ -d "$HOME/.local/bin" ]; then
    echo "创建符号链接到 ~/.local/bin/storage-manager"
    ln -sf "$SCRIPT_DIR/cli.py" "$HOME/.local/bin/storage-manager"
    chmod +x "$HOME/.local/bin/storage-manager"
fi

# 创建配置文件示例
echo "创建配置文件示例..."
CONFIG_EXAMPLE="$SCRIPT_DIR/config.example.env"
cat > "$CONFIG_EXAMPLE" << EOF
# 飞书收纳管家配置
# 复制此文件为 .env 并填写实际值

# 飞书应用配置
FEISHU_APP_ID=cli_xxxxxxxxxxxxxxx
FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

# 多维表格配置
FEISHU_BITABLE_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxx
FEISHU_TABLE_ID=tblxxxxxxxxxxxxxxx

# 可选：日志级别
LOG_LEVEL=INFO
EOF

echo ""
echo "✅ 安装完成！"
echo ""
echo "📋 下一步："
echo "1. 配置飞书应用"
echo "2. 创建多维表格"
echo "3. 配置环境变量"
echo "4. 使用技能命令"
echo ""
echo "📚 详细文档请参考 SKILL.md"

path: package.json
content: {
  "name": "storage-manager",
  "version": "1.0.0",
  "description": "飞书多维表格收纳管家 - 物品入库、检索、位置更新一体化工具",
  "main": "cli.py",
  "scripts": {
    "test": "python3 test_basic.py",
    "install": "./install.sh"
  },
  "keywords": [
    "feishu",
    "bitable",
    "storage",
    "manager",
    "inventory",
    "organization"
  ],
  "author": "OpenClaw AI Assistant",
  "license": "MIT",
  "dependencies": {
    "requests": ">=2.25.0"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/openclaw/skills/storage-manager"
  },
  "bugs": {
    "url": "https://github.com/openclaw/skills/issues"
  },
  "homepage": "https://skillhub.cn/skills/storage-manager",
  "clawhub": {
    "category": "productivity",
    "tags": ["feishu", "storage", "management", "bitable"],
    "emoji": "🗃️",
    "compatibility": ["openclaw>=2026.3.0"],
    "permissions": ["files:read", "network:outbound"]
  }
}

path: storage_manager.py
content: #!/usr/bin/env python3
"""
飞书多维表格收纳管家核心模块
"""

import os
import sys
import json
import requests
from typing import Dict, List, Optional, Any

class FeishuStorageManager:
    """飞书收纳管家核心类"""
    
    def __init__(self):
        self.app_id = os.getenv("FEISHU_APP_ID", "")
        self.app_secret = os.getenv("FEISHU_APP_SECRET", "")
        self.bitable_token = os.getenv("FEISHU_BITABLE_TOKEN", "")
        self.table_id = os.getenv("FEISHU_TABLE_ID", "")
        
        self.access_token = None
        self.token_expire = 0
        
    def get_tenant_access_token(self) -> str:
        """获取Tenant Access Token"""
        if self.access_token and self.token_expire > 0:
            return self.access_token
            
        url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"
        data = {
            "app_id": self.app_id,
            "app_secret": self.app_secret
        }
        
        try:
            response = requests.post(url, json=data, timeout=10)
            if response.status_code == 200:
                result = response.json()
                if result.get("code") == 0:
                    self.access_token = result["tenant_access_token"]
                    self.token_expire = result.get("expire", 0)
                    return self.access_token
                else:
                    raise Exception(f"获取Token失败: {result.get('msg')}")
            else:
                raise Exception(f"HTTP错误: {response.status_code}")
        except Exception as e:
            raise Exception(f"获取Token异常: {e}")
    
    def upload_image(self, image_path: str) -> str:
        """上传图片到飞书，返回file_token"""
        if not self.access_token:
            self.get_tenant_access_token()
        
        try:
            with open(image_path, "rb") as f:
                image_data = f.read()
            file_size = len(image_data)
        except Exception as e:
            raise Exception(f"读取图片失败: {e}")
        
        url = "https://open.feishu.cn/open-apis/drive/v1/medias/upload_all"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        
        form_data = {
            "file_name": os.path.basename(image_path),
            "parent_type": "bitable_image",
            "parent_node": self.bitable_token,
            "size": str(file_size)
        }
        
        files = {"file": (os.path.basename(image_path), image_data, "image/jpeg")}
        
        try:
            response = requests.post(
                url,
                headers=headers,
                data=form_data,
                files=files,
                timeout=30
            )
            
            if response.status_code == 200:
                result = response.json()
                if result.get("code") == 0 and "data" in result:
                    file_token = result["data"].get("file_token")
                    if file_token:
                        return file_token
                    else:
                        raise Exception("响应中没有file_token字段")
                else:
                    raise Exception(f"上传失败: {result.get('msg', '未知错误')}")
            else:
                raise Exception(f"HTTP错误 {response.status_code}: {response.text[:200]}")
                
        except Exception as e:
            raise Exception(f"上传请求异常: {e}")
    
    # ==================== 核心工具函数 ====================
    
    def add_storage_item(self, item_name: str, location: str, image_path: Optional[str] = None) -> Dict:
        """
        物品入库工具
        """
        print(f"[ADD] 物品入库: {item_name} -> {location}")
        
        fields = {
            "AI收纳管家-物品位置记录": item_name,
            "Location": location
        }
        
        if image_path and os.path.exists(image_path):
            try:
                file_token = self.upload_image(image_path)
                fields["Image"] = [{"file_token": file_token}]
                print(f"[ADD] 图片已关联: {file_token}")
            except Exception as e:
                print(f"[WARN] 图片上传失败，继续创建记录: {e}")
                fields["Location"] = f"{location} [图片上传失败: {e}]"
        
        return self._create_bitable_record(fields)
    
    def search_storage_item(self, query_item: str) -> List[Dict]:
        """
        物品检索工具
        """
        print(f"[SEARCH] 搜索物品: {query_item}")
        
        all_records = self._list_all_records()
        
        matched_records = []
        for record in all_records:
            item_field = record["fields"].get("AI收纳管家-物品位置记录", "")
            if query_item.lower() in item_field.lower():
                matched_records.append(record)
        
        print(f"[SEARCH] 找到 {len(matched_records)} 条匹配记录")
        return matched_records
    
    def update_storage_location(self, item_name: str, new_location: str) -> Dict:
        """
        位置更新工具
        """
        print(f"[UPDATE] 更新位置: {item_name} -> {new_location}")
        
        records = self.search_storage_item(item_name)
        
        if not records:
            raise Exception(f"未找到物品: {item_name}")
        
        target_record = records[0]
        record_id = target_record["record_id"]
        
        fields = {"Location": new_location}
        
        if "Image" in target_record["fields"]:
            fields["Image"] = target_record["fields"]["Image"]
        
        return self._update_bitable_record(record_id, fields)
    
    # ==================== 底层API函数 ====================
    
    def _create_bitable_record(self, fields: Dict) -> Dict:
        """创建多维表格记录"""
        if not self.access_token:
            self.get_tenant_access_token()
        
        url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{self.bitable_token}/tables/{self.table_id}/records"
        headers = {
            "Authorization": f"Bearer {self.access_token}",
            "Content-Type": "application/json"
        }
        
        data = {"fields": fields}
        
        try:
            response = requests.post(url, headers=headers, json=data, timeout=10)
            if response.status_code == 200:
                result = response.json()
                if result.get("code") == 0:
                    record = result.get("record", {})
                    print(f"[API] 记录创建成功，ID: {record.get('record_id')}")
                    return result
                else:
                    raise Exception(f"创建失败: {result.get('msg')}")
            else:
                raise Exception(f"HTTP错误 {response.status_code}: {response.text[:200]}")
        except Exception as e:
            raise Exception(f"创建请求异常: {e}")
    
    def _update_bitable_record(self, record_id: str, fields: Dict) -> Dict:
        """更新多维表格记录"""
        if not self.access_token:
            self.get_tenant_access_token()
        
        url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{self.bitable_token}/tables/{self.table_id}/records/{record_id}"
        headers = {
            "Authorization": f"Bearer {self.access_token}",
            "Content-Type": "application/json"
        }
        
        data = {"fields": fields}
        
        try:
            response = requests.put(url, headers=headers, json=data, timeout=10)
            if response.status_code == 200:
                result = response.json()
                if result.get("code") == 0:
                    print(f"[API] 记录更新成功，ID: {record_id}")
                    return result
                else:
                    raise Exception(f"更新失败: {result.get('msg')}")
            else:
                raise Exception(f"HTTP错误 {response.status_code}: {response.text[:200]}")
        except Exception as e:
            raise Exception(f"更新请求异常: {e}")
    
    def _list_all_records(self) -> List[Dict]:
        """获取所有记录"""
        if not self.access_token:
            self.get_tenant_access_token()
        
        url = f"https://open.feishu.cn/open-apis/bitable/v1/apps/{self.bitable_token}/tables/{self.table_id}/records"
        headers = {"Authorization": f"Bearer {self.access_token}"}
        
        all_records = []
        page_token = None
        
        try:
            while True:
                params = {"page_size": 100}
                if page_token:
                    params["page_token"] = page_token
                
                response = requests.get(url, headers=headers, params=params, timeout=10)
                if response.status_code == 200:
                    result = response.json()
                    if result.get("code") == 0:
                        records = result.get("data", {}).get("items", [])
                        all_records.extend(records)
                        
                        if result.get("data", {}).get("has_more"):
                            page_token = result["data"].get("page_token")
                        else:
                            break
                    else:
                        raise Exception(f"获取记录失败: {result.get('msg')}")
                else:
                    raise Exception(f"HTTP错误 {response.status_code}: {response.text[:200]}")
        except Exception as e:
            raise Exception(f"获取记录异常: {e}")
        
        return all_records

def main():
    """命令行入口"""
    import argparse
    
    parser = argparse.ArgumentParser(description="飞书收纳管家命令行工具")
    subparsers = parser.add_subparsers(dest="command", help="可用命令")
    
    add_parser = subparsers.add_parser("add", help="物品入库")
    add_parser.add_argument("item", help="物品名称")
    add_parser.add_argument("location", help="存放位置")
    add_parser.add_argument("--image", help="图片路径")
    
    search_parser = subparsers.add_parser("search", help="物品检索")
    search_parser.add_argument("item", help="要查找的物品名称")
    
    update_parser = subparsers.add_parser("update", help="位置更新")
    update_parser.add_argument("item", help="物品名称")
    update_parser.add_argument("new_location", help="新位置")
    
    args = parser.parse_args()
    
    if not args.command:
        parser.print_help()
        return
    
    manager = FeishuStorageManager()
    
    try:
        if args.command == "add":
            result = manager.add_storage_item(args.item, args.location, args.image)
            print(f"✅ 物品入库成功: {args.item} -> {args.location}")
            if args.image:
                print(f"   图片已关联")
            
        elif args.command == "search":
            records = manager.search_storage_item(args.item)
            if records:
                print(f"🔍 找到 {len(records)} 条记录:")
                for i, record in enumerate(records, 1):
                    fields = record["fields"]
                    print(f"  {i}. {fields.get('AI收纳管家-物品位置记录')}")
                    print(f"     位置: {fields.get('Location')}")
                    if "Image" in fields:
                        print(f"     有图片附件")
                    print()
            else:
                print(f"❌ 未找到物品: {args.item}")
                
        elif args.command == "update":
            result = manager.update_storage_location(args.item, args.new_location)
            print(f"✅ 位置更新成功: {args.item} -> {args.new_location}")
            
    except Exception as e:
        print(f"❌ 操作失败: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()

path: test_basic.py
content: #!/usr/bin/env python3
"""
基本功能测试
"""

import sys
import os

# 添加技能目录到路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from storage_manager import FeishuStorageManager

def test_initialization():
    """测试初始化"""
    print("1. 测试初始化...")
    manager = FeishuStorageManager()
    
    # 测试配置加载
    assert manager.app_id == ""
    assert manager.app_secret == ""
    assert manager.bitable_token == ""
    assert manager.table_id == ""
    
    print("   ✅ 初始化测试通过")

def test_search_function():
    """测试搜索功能（使用环境变量）"""
    print("2. 测试搜索功能...")
    
    # 设置环境变量
    os.environ["FEISHU_APP_ID"] = "cli_a956c03ffcb9dcbb"
    os.environ["FEISHU_APP_SECRET"] = [REDACTED_SECRET]
    os.environ["FEISHU_BITABLE_TOKEN"] = "AO6rbfj7aa8nbGsG7Rfc90honjK"
    os.environ["FEISHU_TABLE_ID"] = "tbl0T6d9uTv4Fk3c"
    
    manager = FeishuStorageManager()
    
    try:
        results = manager.search_storage_item("梳子")
        print(f"   找到 {len(results)} 条记录")
        
        if results:
            first_result = results[0]
            assert "record_id" in first_result
            assert "fields" in first_result
            
            fields = first_result["fields"]
            assert "AI收纳管家-物品位置记录" in fields
            assert "Location" in fields
            
            print(f"   ✅ 搜索功能测试通过，找到 {len(results)} 条记录")
        else:
            print("   ⚠️ 未找到记录，但API调用成功")
            
    except Exception as e:
        print(f"   ❌ 搜索功能测试失败: {e}")
        raise

def main():
    print("🚀 开始测试收纳管家技能...")
    print("=" * 50)
    
    tests = [
        test_initialization,
        test_search_function
    ]
    
    passed = 0
    total = len(tests)
    
    for test_func in tests:
        try:
            test_func()
            passed += 1
        except Exception as e:
            print(f"❌ 测试失败: {e}")
    
    print("=" * 50)
    print(f"📊 测试结果: {passed}/{total} 通过")
    
    if passed == total:
        print("✅ 所有测试通过！")
        return 0
    else:
        print(f"❌ {total - passed} 项测试失败")
        return 1

if __name__ == "__main__":
    sys.exit(main())

Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)