Skill: grill-glitch/internet-archive (version 1.0.0)

# Internet Archive Skill 提供与 Internet Archive (archive.org) 交互的能力：搜索、下载、上传和管理存档内容。 ## 描述 此技能允许你： - 搜索 Internet Archive 目录（支持高级查询语法） - 下载任何公开存档项目的文件 - 上传文件到你的存档账户（需要认证） - 管理已上传项目的元数据 - 列出项目中的文件 - 自动安装和配置 `ia` CLI 工具 ## 触发器 - "搜索 Internet Archive" - "从 archive.org 下载" - "上传到 Internet Archive" - "管理存档元数据" - "检查 Internet Archive 工具" - "安装 ia 命令行工具" - "存档相关"、"archive.org" ## 输入 用户请求与 Internet Archive 相关的操作。 ## 输出 执行具体的 archive.org 操作，返回搜索结果、下载状态、上传确认等信息。 ## 实现 - 主脚本: `internet-archive.py` - 参考文档: `references/` ## 使用方式 直接调用 Python 脚本： ```bash python3 skills/internet-archive/internet-archive.py <intent> [参数] ``` ### 支持的 Intents #### `check` - 检查工具状态 检查 `ia` CLI 是否已安装和配置。 ```bash python3 skills/internet-archive/internet-archive.py check ``` #### `install` - 安装 ia CLI 自动安装 `internetarchive` 包（使用 uv/pipx/pip）。 ```bash python3 skills/internet-archive/internet-archive.py install ``` #### `search` - 搜索存档 搜索 Internet Archive 目录。 ```bash # 基本搜索 python3 skills/internet-archive/internet-archive.py search "collection:nasa mediatype:image" # 仅返回标识符列表 python3 skills/internet-archive/internet-archive.py search "public domain books" --itemlist # 全文搜索 python3 skills/internet-archive/internet-archive.py search "climate change" --fts # 排序和字段过滤 python3 skills/internet-archive/internet-archive.py search "mediatype:movies" --sort="downloads desc" --field=title --field=identifier ``` 常用参数： - `--itemlist` - 仅输出标识符，每行一个 - `--fts` - 全文搜索（在文本内容中搜索，而非仅元数据） - `--sort="field asc|desc"` - 排序（如 `downloads desc`, `date asc`） - `--field=<name>` - 只返回指定字段（可重复使用） - `--parameters="rows=20&page=1"` - 原始查询参数 #### `download` - 下载项目文件 下载 Internet Archive 项目的文件。 ```bash # 下载整个项目 python3 skills/internet-archive/internet-archive.py download <identifier> # 只下载特定扩展名文件 python3 skills/internet-archive/internet-archive.py download <identifier> --glob="*.pdf" # 排除某些文件 python3 skills/internet-archive/internet-archive.py download <identifier> --exclude="*low*" # 下载到指定目录 python3 skills/internet-archive/internet-archive.py download <identifier> --destdir=./downloads # 预览（不实际下载） python3 skills/internet-archive/internet-archive.py download <identifier> --dry-run # 只下载原始文件，跳过衍生物 python3 skills/internet-archive/internet-archive.py download <identifier> --source=original ``` #### `upload` - 上传文件 上传文件到你的 Internet Archive 账户（需要认证）。 ```bash python3 skills/internet-archive/internet-archive.py upload <identifier> file1.pdf file2.jpg \ --metadata="mediatype:texts" \ --metadata="title:My Document" \ --metadata="creator:Your Name" # 上传并验证 python3 skills/internet-archive/internet-archive.py upload <id> document.pdf \ --metadata="mediatype:texts" --checksum --verify # 跳过衍生物生成（更快上传） python3 skills/internet-archive/internet-archive.py upload <id> video.mp4 \ --metadata="mediatype:movies" --no-derive ``` 必需参数： - `<identifier>` - 项目标识符（唯一、仅 ASCII 字母数字、连字符） - `files` - 要上传的文件路径列表 - `--metadata="key:value"` - 元数据（至少需要 `mediatype`） 可选参数： - `--checksum` - 使用校验和跳过已上传的文件 - `--verify` - 上传后验证数据完整性 - `--no-derive` - 跳过自动衍生物生成 - `--retries=N` - 重试次数（默认 0） #### `metadata` - 查看/修改元数据 查看或修改项目元数据。 ```bash # 查看完整元数据（JSON 格式） python3 skills/internet-archive/internet-archive.py metadata <identifier> # 列出包含的文件格式 python3 skills/internet-archive/internet-archive.py metadata <identifier> --formats # 修改字段 python3 skills/internet-archive/internet-archive.py metadata <identifier> --modify="title:New Title" # 添加值到列表字段（如 subjects） python3 skills/internet-archive/internet-archive.py metadata <identifier> --append-list="subject:new topic" # 删除字段 python3 skills/internet-archive/internet-archive.py metadata <identifier> --modify="oldfield:REMOVE_TAG" ``` #### `list` - 列出项目文件 列出 Internet Archive 项目中的所有文件。 ```bash # 列出所有文件 python3 skills/internet-archive/internet-archive.py list <identifier> # 只显示名称和大小 python3 skills/internet-archive/internet-archive.py list <identifier> --columns=name,size # 显示完整下载 URL python3 skills/internet-archive/internet-archive.py list <identifier> --location # 显示所有文件信息（包含格式、校验和等） python3 skills/internet-archive/internet-archive.py list <identifier> --all --verbose ``` ## 配置和认证 **上传和修改元数据需要认证**。搜索和下载公开项目不需要。 ### 配置步骤 1. **创建 Internet Archive 账户**：https://archive.org/account/signup 2. **获取 S3 兼容 API 密钥**：https://archive.org/account/s3.php - 点击 "Create new keys" 或使用现有密钥 - 复制 `Access Key ID` 和 `Secret Access Key` 3. **运行配置命令**： ```bash ia configure ``` 按提示输入密钥。 或者，设置环境变量： ```bash export IA_ACCESS_KEY_ID="your-access-key" export IA_SECRET_ACCESS_KEY="your-secret-key" ``` 配置文件保存在 `~/.config/ia.ini`。 验证配置： ```bash ia configure --whoami # 应显示你的用户名 ``` ### User-Agent 要求 **所有请求必须包含明确的 User-Agent**。`ia` CLI 默认包含你的访问密钥。对于自动化工具，建议设置自定义后缀： ```bash ia --user-agent-suffix "OpenClaw/1.0" ``` 或在 `~/.config/ia.ini` 中： ```ini [general] user_agent_suffix = OpenClaw/1.0 ``` 这有助于 Internet Archive 跟踪来源并维护服务质量。 ## 自动安装 如果 `ia` 命令未找到，此技能会提示安装。支持的安装方式（按顺序尝试）： 1. `uv tool install internetarchive`（推荐） 2. `pipx install internetarchive` 3. `pip install internetarchive` 你需要 Python 3.9+ 和 pip/uv/pipx 之一。 ## 重要概念 ### 项目（Item） archive.org 的基本单位。一个项目是一组相关文件的逻辑集合（一本书、一张专辑、一个数据集等）。每个项目有唯一的标识符。 项目包含： - 原始上传文件 - 衍生物（系统自动生成的转换版本） - `<identifier>_meta.xml` - 项目级元数据 - `<identifier>_files.xml` - 文件级元数据 ### 集合（Collection） 项目必须属于一个集合。只有 IA 员工可以创建新集合（通常需要至少 50 个项目）。公开上传集合包括： - `opensource_movies`, `opensource_audio`, `opensource_media` - `community_texts`, `community_video`, `community_audio` ### 衍生物（Derivatives） 上传后，IA 自动生成衍生物（不同格式和分辨率的转换版本）： - 视频 → h.264, Ogg, 多种码率 - 音频 → MP3, Ogg Vorbis, FLAC - 文本/书籍 → OCR, 可搜索 PDF, EPUB, DjVu - 图片 → 缩略图, JPEG 2000 使用 `--no-derive` 跳过衍生物生成（更快上传，但内容访问性降低）。 ### 元数据模式 必需字段：`identifier`, `mediatype` 推荐字段：`title`, `description`, `creator`, `date`, `subject`, `collection`, `language` 标识符要求： - 仅 ASCII 字母数字、下划线、连字符、句点 - 以字母数字开头 - 5-100 字符（5-80 推荐） - 一旦设置不可更改 ## 最佳实践 1. **上传前测试** - 使用 `test_collection` 验证上传（30 天后自动删除） 2. **使用有意义的标识符** - 小写、连字符分隔、描述性 3. **完整的元数据** - 至少包含 title, creator, description 4. **检查标识符冲突** - `ia metadata <id>` 看是否已存在 5. **大文件分卷** - 大文件集打包成 ZIP 再上传 6. **使用校验和** - `--checksum` 支持断点续传 7. **速率限制** - 批量操作时添加延迟（如 GNU Parallel 的 `--delay 1`） 8. **预览操作** - 使用 `--dry-run` 预览下载/上传 9. **保护敏感日志** - 避免在日志中记录用户搜索词、歌曲 ID、歌词内容等 关于日志保护： - 使用 `BuildConfig.DEBUG` 保护所有日志输出 - 不在任何日志中记录用户隐私数据 - 开发日志使用 `Log.d()`，生产环境自动静默 ## 常见错误及解决 | 错误 | 解决方案 | |-----|----------| | "not configured" | 运行 `ia configure` 或设置环境变量 | | "identifier exists" | 选择其他标识符（不可更改） | | "permission denied" | 检查 https://archive.org/account/s3.php 的密钥 | | "network error" | 重试操作，检查网络连接 | | "429 Too Many Requests" | 速率限制；等待 Retry-After 指定时间 | | 项目未出现在搜索中 | 通常几分钟内出现；最长 24 小时；检查 `ia tasks <identifier>` | ## 参考资源 - [Internet Archive 开发者文档](https://archive.org/developers/) - [Items API](https://archive.org/developers/items.html) - [元数据模式](https://archive.org/developers/metadata-schema/) - [Metadata Write API](https://archive.org/developers/md-write.html) - [Tasks API](https://archive.org/developers/tasks.html) - [Python 库文档](https://github.com/jjjake/internetarchive) ## 注意事项 - 此技能依赖外部 `ia` 命令；确保安装 `internetarchive` Python 包 - 上传和元数据修改需要有效的 Internet Archive 账户和 API 密钥 - 项目标识符一旦创建不可更改，选择时需谨慎 - 大文件上传可能需要较长时间，建议使用 `--checksum` 支持断点续传 - 遵循 Internet Archive 的使用条款和服务政策

path: references/QUICK_REFERENCE.md
content: # Internet Archive 快速参考

## 搜索语法速查

### 基础查询
```
collection:nasa                  # 在 NASA 集合中搜索
mediatype:movies                 # 按媒体类型过滤
creator:"Mark Twain"             # 精确匹配创作者
title:"climate change"           # 在标题中搜索
```

### 布尔操作
```
collection:nasa AND mediatype:image
mediatype:texts OR mediatype:audio
"public domain" NOT collection:opensource
```

### 范围查询
```
date:[2020-01-01 TO 2024-12-31]
downloads:[1000 TO null]         # 下载量 >= 1000
item_size:[1000000000 TO null]   # 大小 >= 1GB
```

### 全文搜索
```
ia search -F 'collection:books "exact phrase"'
ia search -F 'author:Smith AND "content text"'
```

## 常用 ia 命令速查

```bash
# 工具
ia --version
ia configure                    # 首次配置
ia configure --whoami           # 查看当前用户

# 搜索
ia search 'query' --itemlist     # 仅标识符
ia search 'query' --sort='downloads desc' -n  # 仅计数

# 下载
ia download <id>                # 全部文件
ia download <id> --glob='*.pdf' # 通配符
ia download <id> --dry-run      # 预览

# 上传
ia upload <id> file.txt \
  --metadata='mediatype:texts' \
  --metadata='title:My File'

# 元数据
ia metadata <id>                # 查看全部
ia metadata <id> --formats      # 列出文件格式
ia metadata <id> --modify='title:New'  # 修改

# 列出
ia list <id> --location         # 显示 URL
ia list <id> --columns=name,size,format

# 批量（配合 GNU Parallel）
cat items.txt | parallel 'ia download {}'
```

## 标识符命名规则

- 仅 ASCII 字母、数字、下划线 `_`、连字符 `-`、句点 `.`
- 必须以字母或数字开头
- 5-100 字符（推荐 5-80）
- **一旦创建不可更改**
- 建议：小写 + 连字符分隔（`my-document-2024`）

## 常用集合（Collection）

```
opensource_movies    # 开源电影（任何人可上传）
opensource_audio    # 开源音频
opensource_media    # 通用开源媒体
community_texts     # 社区文本
community_video     # 社区视频
community_audio     # 社区音频
test_collection     # 测试集合（30 天后自动删除）
```

## 媒体类型（mediatype）

```
texts       # 书籍、文档、PDF
movies      # 视频
audio       # 音频、播客
software    # 程序、游戏
image       # 照片、图形
data        # 数据集、归档
```

## 有用的排序字段

```
downloads        # 总下载量
week/month       # 周/月下载量
publicdate       # 发布日期（降序=最新）
addeddate        # 添加日期
date             # 内容日期
titleSorter      # 标题字母序
creatorSorter    # 创作者字母序
item_size        # 项目大小
files_count      # 文件数量
```

## 环境变量

```bash
export IA_ACCESS_KEY_ID="your-key"
export IA_SECRET_ACCESS_KEY="your-secret"
```

## 配置文件位置

```
~/.config/ia.ini
```

示例配置：
```ini
[general]
user_agent_suffix = OpenClaw/1.0
```

## 安全注意事项

- **永远不要**在日志中记录：
  - 用户搜索词（敏感查询）
  - 歌曲 ID、内部标识符
  - 歌词内容
  - API 密钥
- 使用 `BuildConfig.DEBUG` 保护所有调试日志
- 生产环境避免 `Log.d()` 输出敏感数据

## 常见问题

**Q: 上传失败 "identifier exists"**
A: 标识符已存在且不可更改，选择新标识符。

**Q: 项目搜索不到**
A: 上传后通常在几分钟内索引，最长 24 小时。检查 `ia tasks <id>`。

**Q: 429 错误**
A: 速率限制，等待 Retry-After 秒数。

**Q: 大文件上传中断**
A: 使用 `--checksum` 支持断点续传。

**Q: 如何只下载原文件，不要衍生物？**
A: `ia download <id> --source=original`

## 有用链接

- 开发者文档: https://archive.org/developers/
- 获取 API 密钥: https://archive.org/account/s3.php
- 完整命令参考: 运行 `ia --help` 或查看 https://github.com/jjjake/internetarchive


path: README.md
content: # Internet Archive Skill for OpenClaw

将 Internet Archive 的强大功能集成到 OpenClaw 中。上传、下载、搜索和管理 archive.org 上的内容。

## 功能

- 搜索存档目录（支持 Lucene 查询语法和全文搜索）
- 下载任何公开项目的文件（支持通配符、格式过滤）
- 上传文件到你的存档账户（含完整元数据支持）
- 查看和修改项目元数据
- 列出项目文件
- 自动检测和安装 `ia` CLI 工具

## 安装

技能已放置在 OpenClaw 的 `skills/` 目录中。使用前确保：

1. 安装 `internetarchive` Python 包（可选，按需自动安装）：
   ```bash
   uv tool install internetarchive
   # 或
   pipx install internetarchive
   ```

2. 配置认证（仅上传所需）：
   ```bash
   ia configure
   ```
   从 https://archive.org/account/s3.php 获取密钥。

## 使用方法

从 OpenClaw 直接调用，或在命令行测试：

```bash
# 检查工具状态
python3 skills/internet-archive/internet-archive.py check

# 搜索
python3 skills/internet-archive/internet-archive.py search "collection:nasa mediatype:image"

# 下载
python3 skills/internet-archive/internet-archive.py download <identifier> --glob="*.pdf"

# 上传
python3 skills/internet-archive/internet-archive.py upload <id> file.txt \
  --metadata="mediatype:texts" --metadata="title:My File"

# 查看元数据
python3 skills/internet-archive/internet-archive.py metadata <identifier>

# 列出文件
python3 skills/internet-archive/internet-archive.py list <identifier>
```

## 技能结构

```
skills/internet-archive/
├── internet-archive.py    # 主脚本
├── SKILL.md               # 技能定义和文档
├── README.md              # 本文件
├── references/            # 附加参考文档
└── scripts/               # 辅助脚本（如有）
```

## 与原始仓库的对应

这个 OpenClaw 技能基于 [internetarchive/internet-archive-skills](https://github.com/internetarchive/internet-archive-skills) 仓库，将其从 Claude Code 插件格式转换为 OpenClaw 本地技能格式。

差异：
- 使用 Python 脚本直接实现功能，而非依赖 Claude 内置能力
- 适配 OpenClaw 的技能目录结构
- 简化的调用方式（命令行参数而非自然语言触发）
- 保持与 `ia` CLI 的兼容性

## 贡献

如有问题或改进建议，请参考原仓库：
https://github.com/internetarchive/internet-archive-skills

## 许可证

GNU AGPL v3.0 (与原仓库相同)

## 资源

- [Internet Archive](https://archive.org)
- [API 文档](https://archive.org/developers/)
- [ia CLI 工具](https://github.com/jjjake/internetarchive)


path: internet-archive.py
content: #!/usr/bin/env python3
"""
Internet Archive Skill for OpenClaw

This skill enables interaction with the Internet Archive (archive.org)
using the `ia` command-line tool from the `internetarchive` Python package.

Usage:
    python3 internet-archive.py <intent> [args...]

Intents:
    search      - Search the archive catalog
    download    - Download files from an item
    upload      - Upload files to the archive
    metadata    - View or modify item metadata
    list        - List files in an item
    check       - Check if ia CLI is installed and configured
    install     - Install the ia CLI tool
"""

import subprocess
import json
import sys
import os
from typing import Dict, List, Any

def run_ia_command(args: List[str], input_data: str = None) -> Dict[str, Any]:
    """Run an ia command and return the result."""
    try:
        cmd = ['ia'] + args
        result = subprocess.run(
            cmd,
            input=input_data,
            capture_output=True,
            text=True,
            timeout=300
        )
        return {
            'success': result.returncode == 0,
            'stdout': result.stdout,
            'stderr': result.stderr,
            'returncode': result.returncode
        }
    except subprocess.TimeoutExpired:
        return {
            'success': False,
            'stdout': '',
            'stderr': 'Command timed out after 300 seconds',
            'returncode': -1
        }
    except FileNotFoundError:
        return {
            'success': False,
            'stdout': '',
            'stderr': 'ia command not found. Please install the internetarchive package.',
            'returncode': -1
        }

def check_ia_installed() -> bool:
    """Check if ia CLI is installed."""
    result = run_ia_command(['--version'])
    return result['success']

def install_ia() -> Dict[str, Any]:
    """Install the ia CLI using uv, pipx, or pip."""
    methods = [
        ['uv', 'tool', 'install', 'internetarchive'],
        ['pipx', 'install', 'internetarchive'],
        ['pip', 'install', 'internetarchive']
    ]
    
    for method in methods:
        try:
            result = subprocess.run(method, capture_output=True, text=True, timeout=120)
            if result.returncode == 0:
                return {
                    'success': True,
                    'method': ' '.join(method),
                    'stdout': result.stdout,
                    'stderr': result.stderr
                }
        except (subprocess.TimeoutExpired, FileNotFoundError):
            continue
    
    return {
        'success': False,
        'stderr': 'Failed to install internetarchive. Please install manually using uv, pipx, or pip.'
    }

def intent_search(query: str, **kwargs) -> Dict[str, Any]:
    """Search the Internet Archive catalog."""
    args = ['search', query]
    
    # Add parameters
    if kwargs.get('itemlist', False):
        args.append('--itemlist')
    if kwargs.get('num_found', False):
        args.append('--num-found')
    if 'sort' in kwargs:
        args.extend(['--sort', kwargs['sort']])
    if 'fields' in kwargs:
        for field in kwargs['fields']:
            args.extend(['--field', field])
    if kwargs.get('fts', False):
        args.append('--fts')
    if 'parameters' in kwargs:
        args.extend(['--parameters', kwargs['parameters']])
    
    result = run_ia_command(args)
    
    if result['success']:
        return {
            'success': True,
            'output': result['stdout'],
            'format': 'text' if kwargs.get('itemlist', False) else 'full'
        }
    else:
        return {
            'success': False,
            'error': result['stderr']
        }

def intent_download(identifier: str, **kwargs) -> Dict[str, Any]:
    """Download files from an Internet Archive item."""
    args = ['download', identifier]
    
    if 'glob' in kwargs:
        args.extend(['--glob', kwargs['glob']])
    if 'exclude' in kwargs:
        args.extend(['--exclude', kwargs['exclude']])
    if 'format' in kwargs:
        args.extend(['--format', kwargs['format']])
    if 'source' in kwargs:
        args.extend(['--source', kwargs['source']])
    if 'destdir' in kwargs:
        args.extend(['--destdir', kwargs['destdir']])
    if kwargs.get('dry_run', False):
        args.append('--dry-run')
    if kwargs.get('checksum', False):
        args.append('--checksum')
    if kwargs.get('no_directories', False):
        args.append('--no-directories')
    
    result = run_ia_command(args)
    
    return {
        'success': result['success'],
        'output': result['stdout'],
        'error': result['stderr']
    }

def intent_upload(identifier: str, files: List[str], metadata: Dict[str, str], **kwargs) -> Dict[str, Any]:
    """Upload files to the Internet Archive."""
    args = ['upload', identifier] + files
    
    # Add metadata
    for key, value in metadata.items():
        args.extend(['--metadata', f'{key}:{value}'])
    
    # Add other options
    if kwargs.get('checksum', False):
        args.append('--checksum')
    if kwargs.get('verify', False):
        args.append('--verify')
    if kwargs.get('no_derive', False):
        args.append('--no-derive')
    if 'retries' in kwargs:
        args.extend(['--retries', str(kwargs['retries'])])
    if 'destdir' in kwargs:
        args.extend(['--destdir', kwargs['destdir']])
    
    result = run_ia_command(args)
    
    return {
        'success': result['success'],
        'output': result['stdout'],
        'error': result['stderr']
    }

def intent_metadata(identifier: str, **kwargs) -> Dict[str, Any]:
    """View or modify item metadata."""
    args = ['metadata', identifier]
    
    if 'modify' in kwargs:
        args.extend(['--modify', kwargs['modify']])
    if 'append' in kwargs:
        args.extend(['--append', kwargs['append']])
    if 'append_list' in kwargs:
        args.extend(['--append-list', kwargs['append_list']])
    if 'remove' in kwargs:
        args.extend(['--remove', kwargs['remove']])
    if kwargs.get('formats', False):
        args.append('--formats')
    if 'target' in kwargs:
        args.extend(['--target', kwargs['target']])
    
    result = run_ia_command(args)
    
    return {
        'success': result['success'],
        'output': result['stdout'],
        'error': result['stderr']
    }

def intent_list(identifier: str, **kwargs) -> Dict[str, Any]:
    """List files in an Internet Archive item."""
    args = ['list', identifier]
    
    if 'columns' in kwargs:
        args.extend(['--columns', kwargs['columns']])
    if 'glob' in kwargs:
        args.extend(['--glob', kwargs['glob']])
    if kwargs.get('location', False):
        args.append('--location')
    if kwargs.get('all', False):
        args.append('--all')
    if kwargs.get('verbose', False):
        args.append('--verbose')
    
    result = run_ia_command(args)
    
    return {
        'success': result['success'],
        'output': result['stdout'],
        'error': result['stderr']
    }

def intent_check() -> Dict[str, Any]:
    """Check if ia CLI is installed and configured."""
    if not check_ia_installed():
        return {
            'success': False,
            'installed': False,
            'message': 'ia CLI is not installed. Use "install" intent to install it.'
        }
    
    # Check configuration
    result = run_ia_command(['configure', '--whoami'])
    if result['success']:
        user = result['stdout'].strip()
        return {
            'success': True,
            'installed': True,
            'configured': True,
            'user': user,
            'message': f'ia CLI is installed and configured. Authenticated as: {user}'
        }
    else:
        # If --whoami fails but command exists, it's likely not configured
        return {
            'success': True,
            'installed': True,
            'configured': False,
            'message': 'ia CLI is installed but not configured. Run "ia configure" to set up credentials.',
            'stderr': result['stderr']
        }

def intent_install() -> Dict[str, Any]:
    """Install the ia CLI tool."""
    result = install_ia()
    if result['success']:
        return {
            'success': True,
            'message': f'Successfully installed internetarchive using {result["method"]}',
            'details': result['stdout']
        }
    else:
        return {
            'success': False,
            'error': result['stderr']
        }

def main():
    """Main entry point."""
    try:
        if len(sys.argv) < 2:
            print("Usage: python3 internet-archive.py <intent> [args...]")
            print("\nIntents:")
            print("  check       - Check if ia CLI is installed and configured")
            print("  install     - Install the ia CLI tool")
            print("  search      - Search the archive (requires query)")
            print("  download    - Download from an item (requires identifier)")
            print("  upload      - Upload files (requires identifier, files, metadata)")
            print("  metadata    - View/modify metadata (requires identifier)")
            print("  list        - List files in an item (requires identifier)")
            sys.exit(1)
        
        intent = sys.argv[1]
        
        # Simple dispatch based on intent
        if intent == 'check':
            result = intent_check()
        elif intent == 'install':
            result = intent_install()
        elif intent == 'search':
            if len(sys.argv) < 3:
                print("Error: search requires a query", file=sys.stderr)
                sys.exit(1)
            query = sys.argv[2]
            # Parse optional flags from remaining args
            kwargs = {}
            i = 3
            while i < len(sys.argv):
                if sys.argv[i] == '--itemlist':
                    kwargs['itemlist'] = True
                    i += 1
                elif sys.argv[i] == '--fts':
                    kwargs['fts'] = True
                    i += 1
                elif sys.argv[i] == '--sort' and i + 1 < len(sys.argv):
                    kwargs['sort'] = sys.argv[i + 1]
                    i += 2
                elif sys.argv[i] == '--parameters' and i + 1 < len(sys.argv):
                    kwargs['parameters'] = sys.argv[i + 1]
                    i += 2
                else:
                    i += 1
            result = intent_search(query, **kwargs)
        elif intent == 'download':
            if len(sys.argv) < 3:
                print("Error: download requires an identifier", file=sys.stderr)
                sys.exit(1)
            identifier = sys.argv[2]
            kwargs = {}
            i = 3
            while i < len(sys.argv):
                if sys.argv[i] == '--glob' and i + 1 < len(sys.argv):
                    kwargs['glob'] = sys.argv[i + 1]
                    i += 2
                elif sys.argv[i] == '--exclude' and i + 1 < len(sys.argv):
                    kwargs['exclude'] = sys.argv[i + 1]
                    i += 2
                elif sys.argv[i] == '--format' and i + 1 < len(sys.argv):
                    kwargs['format'] = sys.argv[i + 1]
                    i += 2
                elif sys.argv[i] == '--destdir' and i + 1 < len(sys.argv):
                    kwargs['destdir'] = sys.argv[i + 1]
                    i += 2
                elif sys.argv[i] == '--dry-run':
                    kwargs['dry_run'] = True
                    i += 1
                elif sys.argv[i] == '--checksum':
                    kwargs['checksum'] = True
                    i += 1
                else:
                    i += 1
            result = intent_download(identifier, **kwargs)
        elif intent == 'list':
            if len(sys.argv) < 3:
                print("Error: list requires an identifier", file=sys.stderr)
                sys.exit(1)
            identifier = sys.argv[2]
            kwargs = {}
            i = 3
            while i < len(sys.argv):
                if sys.argv[i] == '--columns' and i + 1 < len(sys.argv):
                    kwargs['columns'] = sys.argv[i + 1]
                    i += 2
                elif sys.argv[i] == '--location':
                    kwargs['location'] = True
                    i += 1
                elif sys.argv[i] == '--all':
                    kwargs['all'] = True
                    i += 1
                else:
                    i += 1
            result = intent_list(identifier, **kwargs)
        elif intent == 'metadata':
            if len(sys.argv) < 3:
                print("Error: metadata requires an identifier", file=sys.stderr)
                sys.exit(1)
            identifier = sys.argv[2]
            kwargs = {}
            i = 3
            while i < len(sys.argv):
                if sys.argv[i] == '--modify' and i + 1 < len(sys.argv):
                    kwargs['modify'] = sys.argv[i + 1]
                    i += 2
                elif sys.argv[i] == '--append' and i + 1 < len(sys.argv):
                    kwargs['append'] = sys.argv[i + 1]
                    i += 2
                elif sys.argv[i] == '--formats':
                    kwargs['formats'] = True
                    i += 1
                else:
                    i += 1
            result = intent_metadata(identifier, **kwargs)
        else:
            print(f"Unknown intent: {intent}", file=sys.stderr)
            sys.exit(1)
        
        # Output result
        if result.get('success', False):
            if 'output' in result:
                print(result['output'])
            else:
                print(json.dumps(result, indent=2, ensure_ascii=False))
        else:
            # Prefer 'error', then 'stderr', then 'message', else 'Unknown error'
            error_msg = result.get('error') or result.get('stderr') or result.get('message') or 'Unknown error'
            print(error_msg, file=sys.stderr)
            sys.exit(1)
    except Exception as e:
        import traceback
        print(f"Error: {e}", file=sys.stderr)
        traceback.print_exc()
        sys.exit(1)

if __name__ == '__main__':
    main()


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