Skill: jack-yang-ai/zhide-offer (version 1.0.1)

--- name: zhide-offer description: 职得Offer校园求职助手。调用职得Offer MCP接口查询校园招聘岗位和面试经验。 当用户提到以下关键词时触发： - 查岗位、找工作、搜职位、招聘信息、校招、实习 - 查面经、面试经验、面试题、面试复盘、面经详情 - 职得Offer、offer、求职、笔试、面试准备 支持功能：搜索岗位列表、查看岗位详情、搜索面经列表、查看面经详情。 --- # 职得Offer校园求职技能 通过 MCP 协议调用职得Offer接口，查询校招岗位和面试经验。 适合的典型场景： - 想找某个方向的校招或实习岗位 - 想看某公司某岗位的真实面经 - 想快速做面试前的信息收集 ## 配置 API Key 从配置文件或环境变量读取，不硬编码： ```js const KEY = process.env.ZHIDE_OFFER_KEY || require('./config.json').zhideOfferKey; ``` 用户需在 `scripts/config.json` 中配置： ```json { "zhideOfferKey": "ofk_你的key" } ``` ## MCP 调用规范 - **端点**：`https://offer.yxzrkj.cn/mcp`（POST） - **Headers**：`Content-Type: application/json`、`Accept: application/json, text/event-stream`、`Authorization: Bearer <KEY>` - **每次调用前需先 initialize 握手**（无状态，每次独立 POST） - 脚本已封装，直接调用即可 ## 快速使用 ```bash # 查岗位 node ~/.openclaw/skills/zhide-offer/scripts/jobs_search.js 数据产品经理 # 看岗位详情 node ~/.openclaw/skills/zhide-offer/scripts/jobs_get.js <岗位ID> # 查面经 node ~/.openclaw/skills/zhide-offer/scripts/interviews_search.js 产品经理 --company 字节跳动 --tag 校招 --limit 5 # 看完整面经 node ~/.openclaw/skills/zhide-offer/scripts/interviews_get.js <面经ID> ``` 如果想用统一入口，也可以： ```bash bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh jobs-search 产品经理 --size 3 bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh interviews-search 产品经理 --limit 2 ``` ## 工作流 ### 搜索岗位 1. 运行 `scripts/jobs_search.js`，传入 `keyword`（可选 `company`/`city`/`pageSize`） 2. 结果在 `result.structuredContent.data.items[]`，字段见 references/api.md 3. 如需详情，用岗位 `id` 运行 `scripts/jobs_get.js` ### 搜索面经 1. 运行 `scripts/interviews_search.js`，传入 `position_query`（精准单词组效果最好，多词组合可能返回空） 2. 结果在 `result.structuredContent.data.items[]` 3. 如需全文，用面经 `id` 运行 `scripts/interviews_get.js` ## 注意事项 - 发布版本不要携带真实 `scripts/config.json`，公开包只保留模板文件 - `position_query` 用单一关键词（如"数据产品经理"），不要堆多个词 - `interviews.search` 响应较慢（约8秒），正常现象 - 每日调用限额200次（account.entitlements 可查剩余） - 详细字段说明见 `references/api.md`

path: README.local.md
content: # zhide-offer 本地接入说明

## 已安装位置

`~/.openclaw/skills/zhide-offer`

## 配置 API Key

二选一：

### 方式 1：环境变量（推荐）

```bash
export ZHIDE_OFFER_KEY='ofk_your_key_here'
```

### 方式 2：本地配置文件

复制模板并填写：

```bash
cp ~/.openclaw/skills/zhide-offer/scripts/config.json.template ~/.openclaw/skills/zhide-offer/scripts/config.json
```

然后把 `zhideOfferKey` 改成真实 key。

## 可用脚本

```bash
node ~/.openclaw/skills/zhide-offer/scripts/jobs_search.js 数据产品经理
node ~/.openclaw/skills/zhide-offer/scripts/jobs_get.js <岗位ID>
node ~/.openclaw/skills/zhide-offer/scripts/interviews_search.js 产品经理 --company 字节跳动 --tag 校招 --limit 5
node ~/.openclaw/skills/zhide-offer/scripts/interviews_get.js <面经ID>
```

## 快捷入口

直接用 skill 自带包装命令就行：

```bash
bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh jobs-search 产品经理 --size 3
bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh job-get <岗位ID>
bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh interviews-search 产品经理 --limit 2
bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh interview-get <面经ID>
bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh quota
```

## 说明

这个包本质上是“技能说明 + Node 查询脚本”，不是带 tool schema 的 OpenClaw 原生扩展。
当前已经完成本地安装落位，可直接在本机通过这些脚本调用。
发布到市场时不要携带真实 `scripts/config.json`，公开版本只保留模板或占位值。
如果要进一步变成完全原生的 OpenClaw 一等工具，还需要额外封装 tool 接口或 MCP bridge。


path: README.md
content: # 职得Offer校园求职助手

一个面向校招和实习场景的 OpenClaw Skill。

它把职得Offer的 MCP 接口封装成一组本地可直接调用的查询脚本，适合用来：

- 搜校招/实习岗位
- 查看岗位详情和 JD
- 搜真实面经
- 查看完整面经轮次、题目和回答

适用人群：

- 正在找校招、实习、秋招机会的同学
- 想快速收集岗位信息、公司方向、城市分布的人
- 想做面试准备、看真实面经和常见题的人

## 能力概览

- `jobs.search`：搜索岗位列表
- `jobs.get`：查看岗位详情
- `interviews.search`：搜索面经列表
- `interviews.get`：查看面经详情

## 为什么值得装

- 信息集中：岗位和面经放在一套查询入口里
- 上手直接：安装后就能用 Node 脚本查，不用自己拼 MCP 请求
- 支持筛选：公司、城市、招聘类型都能带上
- 面试准备友好：可直接拉完整轮次、题目和回答

## 安装后怎么配

二选一即可。

### 方式 1：环境变量（推荐）

```bash
export ZHIDE_OFFER_KEY='ofk_your_key_here'
```

### 方式 2：本地配置文件

```bash
cp ~/.openclaw/skills/zhide-offer/scripts/config.json.template ~/.openclaw/skills/zhide-offer/scripts/config.json
```

然后把 `scripts/config.json` 里的 `zhideOfferKey` 改成你的真实 key。

## 快速开始

### 查岗位

```bash
node ~/.openclaw/skills/zhide-offer/scripts/jobs_search.js 数据产品经理
node ~/.openclaw/skills/zhide-offer/scripts/jobs_search.js 产品经理 --company 字节跳动 --city 北京 --size 5
```

### 看岗位详情

```bash
node ~/.openclaw/skills/zhide-offer/scripts/jobs_get.js <岗位ID>
```

### 查面经

```bash
node ~/.openclaw/skills/zhide-offer/scripts/interviews_search.js 产品经理 --company 字节跳动 --tag 校招 --limit 5
```

### 看完整面经

```bash
node ~/.openclaw/skills/zhide-offer/scripts/interviews_get.js <面经ID>
```

## 更顺手的统一入口

如果你不想记多个脚本名，也可以直接用这个包装命令：

```bash
bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh jobs-search 产品经理 --size 3
bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh job-get <岗位ID>
bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh interviews-search 产品经理 --limit 2
bash ~/.openclaw/skills/zhide-offer/scripts/zhide_offer.sh interview-get <面经ID>
```

## 使用建议

- 搜面经时，`position_query` 最好用单一精准词组，比如 `产品经理`、`数据产品经理`
- 不要把很多词硬堆在一起，不然容易空结果
- `interviews.search` 响应通常比岗位搜索慢一些，约 8 秒，属于正常现象
- 每日接口调用有限额，建议先缩小关键词再查

## 目录说明

- `SKILL.md`：Skill 描述和触发说明
- `README.md`：市场展示和使用说明
- `references/api.md`：接口字段说明
- `scripts/`：实际可执行脚本

## 当前形态说明

这个 Skill 当前是“技能说明 + 可直接运行的本地脚本”形态。

优点是简单、透明、好改；缺点是它还不是 OpenClaw 原生一等工具扩展。如果后续要做成更原生的交互形式，可以继续封装成 tool schema 或 MCP bridge 版本。


path: references/api.md
content: # 职得Offer MCP API 文档

## 接口列表

| 工具名 | 说明 |
|--------|------|
| account.entitlements | 查询账户权益和每日调用配额 |
| jobs.search | 搜索岗位列表 |
| jobs.get | 获取岗位详情 |
| jobs.facets | 获取账户权益+筛选项（同 entitlements） |
| interviews.search | 搜索面经列表 |
| interviews.get | 获取面经详情 |

---

## account.entitlements

**参数**：无

**返回**：
```json
{
  "userId": "...",
  "userLevel": 2,
  "userLevelDesc": "大会员",
  "expirationTime": "2027-04-10",
  "mcpDailyLimit": 200,
  "mcpUsedToday": 5
}
```

---

## jobs.search

**参数**：
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| keyword | string | 否 | 搜索关键词 |
| company | string | 否 | 公司名 |
| city | string | 否 | 城市 |
| pageSize | integer | 否 | 返回数量，默认10 |

**结果路径**：`result.structuredContent.data.items[]`

**item 字段**：
| 字段 | 说明 |
|------|------|
| id | 岗位ID（用于 jobs.get） |
| company | 公司名 |
| city | 城市 |
| （title 字段在列表中可能为空，详情用 jobs.get） |

---

## jobs.get

**参数**：
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | string | ✅ | 岗位ID |

**结果路径**：`result.structuredContent.data`

**data 字段**：
| 字段 | 说明 |
|------|------|
| title | 岗位名称 |
| company | 公司名 |
| city | 城市 |
| description | 岗位描述（JD） |

---

## interviews.search

**参数**：
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| position_query | string | 建议 | 岗位检索词（推荐，精准） |
| query | string | - | 等价于 position_query |
| positionQuery | string | - | 等价于 position_query（camelCase） |
| job_id | string | 否 | 单个岗位ID过滤 |
| job_ids | string | 否 | 多个岗位ID，逗号分隔 |
| company | string | 否 | 公司名过滤 |
| city | string | 否 | 城市过滤 |
| recruitment_tag | string | 否 | 招聘类型：校招/实习/社招 |
| industry | string | 否 | 行业 |
| limit | integer | 否 | 返回数量，1-20，默认10 |

**⚠️ 注意**：position_query 用单一精准词组，多词组合可能返回0条。

**结果路径**：`result.structuredContent.data.items[]`

**item 字段**：
| 字段 | 说明 |
|------|------|
| id | 面经ID（用于 interviews.get） |
| title | 标题（格式："公司的岗位名的面经"） |
| company | 公司名 |
| position | 岗位方向 |
| city | 城市 |
| difficulty | 招聘类型（校招/实习/社招） |
| tags | 标签数组 |
| summary | 面经摘要 |

---

## interviews.get

**参数**：
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | string | ✅ | 面经ID |

**结果路径**：`result.structuredContent.data`

**data 结构**：
```
data
├── title
├── company
├── position
├── city
├── summary
└── content
    ├── background    # 面试者背景介绍
    ├── outcome       # 结果（如"结果待更新"）
    ├── takeaways[]   # 关键要点列表
    └── rounds[]      # 面试轮次
        ├── key       # round1/round2/round3
        ├── title     # 轮次名称（如"一面"）
        ├── duration  # 时长
        ├── tags[]    # 考察重点标签
        ├── characteristics  # 面试官风格描述
        └── questionAnswers[]
            ├── question  # 面试题
            └── answer    # 回答
```

---

## MCP 请求格式

```http
POST https://offer.yxzrkj.cn/mcp
Content-Type: application/json
Accept: application/json, text/event-stream
Authorization: Bearer <KEY>

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "<工具名>",
    "arguments": { ...参数 }
  }
}
```

initialize 握手（每次调用前需先发送）：
```json
{
  "jsonrpc": "2.0",
  "id": 0,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {},
    "clientInfo": { "name": "client", "version": "1.0" }
  }
}
```


path: scripts/account_entitlements.js
content: #!/usr/bin/env node
/**
 * 查询当前账号权益和今日剩余额度
 * 用法: node account_entitlements.js
 */
const { mcpCall } = require('./mcp_client');

async function main() {
  const result = await mcpCall('account.entitlements', {});
  const data = result?.data || result || {};
  const limit = Number(data.mcpDailyLimit || 0);
  const used = Number(data.mcpUsedToday || 0);
  const remaining = Math.max(limit - used, 0);

  console.log('========== 账号权益 ==========');
  console.log('用户等级:   ' + (data.userLevelDesc || data.userLevel || '-'));
  console.log('到期时间:   ' + (data.expirationTime || '-'));
  console.log('每日限额:   ' + (limit || '-'));
  console.log('今日已用:   ' + used);
  console.log('今日剩余:   ' + remaining);
}

main().catch(e => { console.error('错误:', e.message); process.exit(1); });


path: scripts/config.json
content: zhideOfferKey: ofk_your_key_here

path: scripts/interviews_get.js
content: #!/usr/bin/env node
/**
 * 获取面经完整详情
 * 用法: node interviews_get.js <面经ID>
 *
 * 示例:
 *   node interviews_get.js 69d76d511489a0e96aa770a4
 */
const { mcpCall } = require('./mcp_client');

async function main() {
  const id = process.argv[2];
  if (!id) {
    console.error('请提供面经ID，例如: node interviews_get.js 69d76d511489a0e96aa770a4');
    process.exit(1);
  }

  const result = await mcpCall('interviews.get', { id });
  const det = result?.data || result;
  const c = det?.content || {};

  console.log('========== 面经详情 ==========');
  console.log('标题:   ' + (det.title || '-'));
  console.log('公司:   ' + (det.company || '-'));
  console.log('岗位:   ' + (det.position || '-'));
  console.log('城市:   ' + (det.city || '-'));
  console.log('结果:   ' + (c.outcome || '-'));
  console.log('');
  console.log('--- 面试者背景 ---');
  console.log(c.background || '（无）');
  console.log('');

  const rounds = c.rounds || [];
  rounds.forEach((round, ri) => {
    console.log('===== ' + round.title + ' (' + (round.duration || '') + ') =====');
    console.log('考察重点: ' + (round.tags || []).join(' / '));
    if (round.characteristics) {
      console.log('面试风格: ' + round.characteristics);
    }
    console.log('');
    const qas = round.questionAnswers || [];
    qas.forEach((qa, qi) => {
      console.log('Q' + (qi + 1) + ': ' + qa.question);
      console.log('A' + (qi + 1) + ': ' + (qa.answer || '（无作答）'));
      console.log('');
    });
  });

  if (c.takeaways && c.takeaways.length > 0) {
    console.log('--- 关键要点 ---');
    c.takeaways.forEach((t, i) => console.log((i + 1) + '. ' + t));
  }
}

main().catch(e => { console.error('错误:', e.message); process.exit(1); });


path: scripts/interviews_search.js
content: #!/usr/bin/env node
/**
 * 搜索面经列表
 * 用法: node interviews_search.js <关键词> [--company 公司] [--city 城市] [--tag 校招|实习|社招] [--limit 数量]
 *
 * 示例:
 *   node interviews_search.js 数据产品经理
 *   node interviews_search.js 产品经理 --company 字节跳动 --tag 校招 --limit 5
 *
 * ⚠️ 建议使用单一精准关键词，多词组合可能返回空结果
 */
const { mcpCall } = require('./mcp_client');

async function main() {
  const args = process.argv.slice(2);
  const params = {};

  let i = 0;
  while (i < args.length) {
    if (args[i] === '--company' && args[i+1]) { params.company = args[++i]; }
    else if (args[i] === '--city' && args[i+1]) { params.city = args[++i]; }
    else if (args[i] === '--tag' && args[i+1]) { params.recruitment_tag = args[++i]; }
    else if (args[i] === '--limit' && args[i+1]) { params.limit = parseInt(args[++i]); }
    else if (!args[i].startsWith('--')) { params.position_query = args[i]; }
    i++;
  }

  if (!params.position_query) {
    console.error('请提供搜索关键词，例如: node interviews_search.js 数据产品经理');
    process.exit(1);
  }

  console.log('搜索参数:', params);
  console.log('（面经搜索约需8秒，请稍候...）');
  const result = await mcpCall('interviews.search', params);
  const items = result?.data?.items || [];

  if (items.length === 0) {
    console.log('\n未找到相关面经。建议尝试更精准的单词组（如"产品经理"而不是"产品经理 互联网"）');
    return;
  }

  console.log('\n找到 ' + items.length + ' 条面经:\n');
  items.forEach((it, idx) => {
    console.log((idx + 1) + '. ' + it.title);
    console.log('   公司: ' + (it.company || '-') + '  |  岗位: ' + (it.position || '-'));
    console.log('   城市: ' + (it.city || '-') + '  |  类型: ' + (it.difficulty || '-'));
    console.log('   标签: ' + (it.tags || []).join(', '));
    console.log('   摘要: ' + (it.summary || '').substring(0, 80) + '...');
    console.log('   ID: ' + it.id);
    console.log('');
  });

  console.log('提示: 使用 node interviews_get.js <ID> 查看完整面经');
}

main().catch(e => { console.error('错误:', e.message); process.exit(1); });


path: scripts/jobs_get.js
content: #!/usr/bin/env node
/**
 * 获取岗位详情
 * 用法: node jobs_get.js <岗位ID>
 *
 * 示例:
 *   node jobs_get.js 69269dd194a970ef706ba044
 */
const { mcpCall } = require('./mcp_client');

async function main() {
  const id = process.argv[2];
  if (!id) {
    console.error('请提供岗位ID，例如: node jobs_get.js 69269dd194a970ef706ba044');
    process.exit(1);
  }

  const result = await mcpCall('jobs.get', { id });
  const job = result?.data || result;

  console.log('========== 岗位详情 ==========');
  console.log('岗位名称: ' + (job.title || '-'));
  console.log('公司:     ' + (job.company || '-'));
  console.log('城市:     ' + (job.city || '-'));
  console.log('');
  console.log('--- 岗位描述 ---');
  console.log(job.description || job.desc || '（无描述）');
  console.log('');
  console.log('原始数据:');
  console.log(JSON.stringify(job, null, 2));
}

main().catch(e => { console.error('错误:', e.message); process.exit(1); });


path: scripts/jobs_search.js
content: #!/usr/bin/env node
/**
 * 搜索岗位列表
 * 用法: node jobs_search.js [keyword] [--company 公司] [--city 城市] [--size 数量]
 *
 * 示例:
 *   node jobs_search.js 数据产品经理
 *   node jobs_search.js 产品经理 --company 字节跳动 --city 北京 --size 5
 */
const { mcpCall } = require('./mcp_client');

async function main() {
  const args = process.argv.slice(2);
  const params = {};

  // 解析参数
  let i = 0;
  while (i < args.length) {
    if (args[i] === '--company' && args[i+1]) { params.company = args[++i]; }
    else if (args[i] === '--city' && args[i+1]) { params.city = args[++i]; }
    else if (args[i] === '--size' && args[i+1]) { params.pageSize = parseInt(args[++i]); }
    else if (!args[i].startsWith('--')) { params.keyword = args[i]; }
    i++;
  }

  if (!params.keyword && !params.company) {
    console.error('请提供搜索关键词，例如: node jobs_search.js 数据产品经理');
    process.exit(1);
  }

  console.log('搜索参数:', params);
  const result = await mcpCall('jobs.search', params);
  const items = result?.data?.items || [];

  if (items.length === 0) {
    console.log('未找到相关岗位');
    return;
  }

  console.log('\n找到 ' + items.length + ' 个岗位:\n');
  items.forEach((job, idx) => {
    console.log((idx + 1) + '. ' + (job.title || '(岗位名见详情)'));
    console.log('   公司: ' + (job.company || '-'));
    console.log('   城市: ' + (job.city || '-'));
    console.log('   ID: ' + job.id);
    console.log('');
  });

  console.log('提示: 使用 node jobs_get.js <ID> 查看岗位详情');
}

main().catch(e => { console.error('错误:', e.message); process.exit(1); });


path: scripts/mcp_client.js
content: /**
 * 职得Offer MCP 公共模块
 * 从 scripts/config.json 或环境变量 ZHIDE_OFFER_KEY 读取 API Key
 */
const https = require('https');
const path = require('path');
const fs = require('fs');

function getKey() {
  if (process.env.ZHIDE_OFFER_KEY) return process.env.ZHIDE_OFFER_KEY;
  const cfgPath = path.join(__dirname, 'config.json');
  if (fs.existsSync(cfgPath)) {
    const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
    if (cfg.zhideOfferKey) return cfg.zhideOfferKey;
  }
  throw new Error('未找到 API Key。请在 scripts/config.json 中设置 zhideOfferKey，或设置环境变量 ZHIDE_OFFER_KEY');
}

function post(key, body) {
  return new Promise((resolve, reject) => {
    const data = JSON.stringify(body);
    const r = https.request({
      hostname: 'offer.yxzrkj.cn',
      port: 443,
      path: '/mcp',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json, text/event-stream',
        'Authorization': 'Bearer ' + key,
        'Content-Length': Buffer.byteLength(data)
      },
      timeout: 30000
    }, res => {
      let b = '';
      res.on('data', d => b += d);
      res.on('end', () => resolve({ status: res.statusCode, body: b }));
    });
    r.on('error', reject);
    r.on('timeout', () => { r.destroy(); reject(new Error('请求超时（30s）')); });
    r.write(data);
    r.end();
  });
}

async function mcpCall(toolName, args) {
  const key = getKey();
  // Step 1: initialize
  await post(key, {
    jsonrpc: '2.0', id: 0, method: 'initialize',
    params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'zhide-offer-skill', version: '1.0' } }
  });
  // Step 2: tools/call
  const r = await post(key, {
    jsonrpc: '2.0', id: 1, method: 'tools/call',
    params: { name: toolName, arguments: args }
  });
  const d = JSON.parse(r.body);
  if (d?.result?.isError) throw new Error('接口返回错误: ' + JSON.stringify(d.result));
  return d?.result?.structuredContent || d?.result;
}

module.exports = { mcpCall };


path: scripts/zhide_offer.sh
content: #!/usr/bin/env bash
set -euo pipefail
BASE_DIR="$(cd "$(dirname "$0")" && pwd)"

# Let users keep the key in the environment or a local config file.
if [[ -z "${ZHIDE_OFFER_KEY:-}" && -f "$BASE_DIR/config.json" ]]; then
  :
fi

cmd="${1:-}"
shift || true

case "$cmd" in
  jobs-search)
    exec node "$BASE_DIR/jobs_search.js" "$@"
    ;;
  job-get)
    exec node "$BASE_DIR/jobs_get.js" "$@"
    ;;
  interviews-search)
    exec node "$BASE_DIR/interviews_search.js" "$@"
    ;;
  interview-get)
    exec node "$BASE_DIR/interviews_get.js" "$@"
    ;;
  quota)
    exec node "$BASE_DIR/account_entitlements.js" "$@"
    ;;
  help|--help|-h|"")
    cat <<'EOF'
Usage:
  zhide_offer.sh jobs-search <keyword> [--company 公司] [--city 城市] [--size 数量]
  zhide_offer.sh job-get <岗位ID>
  zhide_offer.sh interviews-search <关键词> [--company 公司] [--city 城市] [--tag 校招|实习|社招] [--limit 数量]
  zhide_offer.sh interview-get <面经ID>
  zhide_offer.sh quota
EOF
    ;;
  *)
    echo "未知命令: $cmd" >&2
    echo >&2
    "$0" help >&2
    exit 1
    ;;
esac


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