分类目录归档:技术

Near By Guru: 找到你身边的开源上师

在上一篇《开源,可能是新手软件工程师的唯一的路了,所以我做了 GoodFirstIssue.org》中,我介绍了 GoodFirstIssue.org ,今天我希望重点介绍一下其中的一个我个人很喜欢的项目 —— Near By Guru。 Near By Guru 可以帮助你找到你所在城市的开源开发者,你可以借此功能找到他们,并与他们取得联系,请他们帮助你,加入到开源社区当中。

有了合适的 Issue ,但贡献依然很难

对于新手来说,做贡献很难。开源和企业内部一个很不一样的点是,开源社区当中,没有人有义务为你提供帮助。大家会秉承着最大善意去帮你参与到社区的贡献当中;但并不意味着大家能像企业同事那样,做到当天甚至即时给你反馈;对于新手来说,这种等待反馈和确认无疑是煎熬的;而对于开源老炮们来说,这不过是他们参与到开源社区的一个又一个日常。

开源社区很好玩,但这些隐形规则的和企业内的不一样,容易让新手一开始就失去信心,使一个人放弃参与开源社区。这个时候,如果身边能够有一个已经在开源社区中浸润已久的人参与其中,给予心理支持,对于新手来说,可能就足以继续坚持下去。

当然,还不止于此,你身边的这些优秀的开源开发者们,他们可能已经成为很多企业的中流砥柱,你可以和他们取得联系,了解业界的最新进展,避免闭门造车,同时也可以对于业界有更多的了解。

Near By Guru 的功能到底是什么?

在设计 Near By Guru 的时候,我核心设计了三个功能:

  1. 读取大家写在 Github 上的 Location,并进行标准化处理;
  2. 以城市级的精度,进行地区排布,从而形成地区级别的开发者地图;
  3. 当用户登录后,获取用户的 Location,并基于 Location 来获取到自己所在地区的开源开发者的地图。

通过这样的方式,方便大家可以快速的找到自己城市的开源开发者们。同时,考虑到像湾区这种跨了好几个城市的地方或者是国内的京津冀、长三角、珠三角地区的情况,我还加了一个 60KM 的约束,一个城市地图上,除了加载本城市的开发者,还可以加载到自己所在城市周边 60KM 内的开发者,方便大家使用。

寻找 Guru 的正确姿势

  1. 作为新手,你不能预期每一位 Guru 都能回应你的联系请求,他们可能很忙,所以你要控制好自己的预期;
  2. 尊重 Guru 的时间:绝大多数时候,如果一个问题,你简单地通过 AI 就能获得答案,就不要拿来问你的 Guru。如果你问了 AI 依然不懂,也不要把问题直接抛给你的 Guru,而是要告诉他,你自己做了哪些研究,有哪些结果,你还有哪里不懂。这样才不至于浪费他的时间,在他心中给你留下不好的印象。
  3. 你们是在尝试建立朋友关系,不是招聘关系,不是师徒管理, Guru 没有义务回答你的任何问题;他的回答是出于善意和热心,而非义务,帮你那是情分,不帮是本分。

怎么使用 Near By Guru?

你可以直接打开 Near By Guru 的功能页面,浏览器会自动向你索要授权,并加载你的所在地,然后你就可以在地图上看到你所在地的开源开发者们。

需要注意的是,因为我们的精度是城市级别,所以固定大家是围绕着城市中心呈圆环状排列的。并不代表每一位开发者的真实位置。

你可以点击他的头像,或在地图或下方列表中查看他的详细信息(比如他的简介、Star 最多的项目)。我们在网站上并不直接提供联系方式,如果你需要和他取得联系,可以点击「前往 Github 联系 Ta」,通过他公开的联系方式,来和他取得联系。

此外,如果你所在城市没有人,但是你所在的省会有,你也可以向下滑动,找到你所在的国家和地区,按照城市级别去查找对应的人。

Near By Guru 的一些已知问题

由于使用的是 Github 的 Location 字段,所以存在多种问题:

  1. 这里的位置不一定是准的,因为大家不一定想起来要去更新,有可能你建立联系后,发现其实他已经离开了你的城市;
  2. 这个信息读取的是 Github 上的 Location ,如果你和你的 Guru 取得联系后,他不希望自己再被打扰,你可以告诉他:可以选择直接更新 Github Location 信息,一周后会自动更新;或者直接通过 GitHub 登录网站,把自己设置为 unlist即可。

以及,如果你受益于这个功能,通过这个功能找到了志同道合的朋友,并加入到开源社区里,希望你也能以对应的善意,在 Github 上写清楚你的 Location,方便帮助更多的新人加入开源社区。

总结

如果可以,我希望看到这篇文章的你,不管是开源新手,还是开源老炮,我们都能抱着善意,帮助我们身边的开发者。

开源,可能是新手软件工程师的唯一的路了,所以我做了 GoodFirstIssue.org

TL;DR
AI 能写代码之后,企业更想招一个熟手配上 AI,而不是一个新手。新手要被看见,开源是唯一的一条不用先被录取的路。我做了 GoodFirstIssue.org,按难度、语言和领域把 issue 筛出,让他们少翻、多改,用更多的时间,去构建自己在开源社区的信誉。

AI 时代的到来,对于我来说,无疑是欣喜的:我早已进入这个行业,虽算不上什么知名人士,但至少在圈子里也有不少好友知道我的能力,因而会给我机会;但对于我刚毕业的堂弟堂妹,甚至今年刚进入大学软件工程相关专业的年轻人来说,这个世界又无比的残酷。因为他们能做的事情, AI 也能做,甚至还能做得更好。相比于招募一个新手配上一个 AI,企业老板更希望招募一个老人,再辅以 AI。

更残酷的是,我自己作为小微企业的老板,我也会选择找一个老人,来辅以 AI。

所以,路在何方?

作为一个从开源社区成长起来的人,我自然是相信开源的,而且我也确实觉得 —— 在今天,开源已经成为这些新手们唯一的路:他们需要一个场合,能够让他们锻炼自己的各项软件工程技能,他们需要犯错,改正,记住教训;他们需要让自己走上舞台的机会。对于他们来说,开源是唯一的机会

你想要在企业中走上舞台,你需要先有机会进去,才能走上舞台;而开源不需要,它的代码是开源的,它的场景是开放的,它的用户反馈可能也是公开的。对于你来说,只要你有一个热忱的心,你就可以加入到这个社区当中,参与社区贡献。在开源社区,走上舞台不需要你先成为,只需要你真的有心。

开源贡献,也没那么容易

当你有了一个热忱的心,你就可以开始走上开源的贡献之路了。开源的贡献可大可小,如果你无所求(像我一样),那对于你来说,开源贡献很多时候就是 —— I Can Fix It。在 Github 上激情 Coding,也完全 OK,你依然在为开源社区做贡献,很好。

但对于这些新人们来说,他们显然是有所求的,那么如何以最低的 effort 获得自己想要的,就成为问题。

什么样的项目会是好的?适合新人的项目:

  1. 项目本身具有一定的关注度和领域价值:大多数时候表现为 Star 多 / Fork 多 / Download 多
  2. 项目本身拥有完善的项目贡献机制和 Mentor 体系:大多表现为有标准的 Contributing 路径且有人在对项目的 issue 、问题进行归档、分类、安排。
  3. 要解决的问题本身复杂度可控:对于新手来说,改一个几千行的 Kernel 的 Bug 不是一个特别可控的方案,但如果只是调试某一个函数,倒也未尝不可;

Github 上有那么多开源项目,又有多少项目能满足我们的需求呢?新手们如何找到这些 issue 呢?理想情况是本身这些新手们已经在用一些开源项目了,那么直接为自己在用的开源项目做贡献,显然是更好的。但,更多的人需要一个工具,帮助他们找到值得他们花时间精力去参与的项目,同时,在「找 issue 」这件事上,让新手在找 issue 上少花时间,把更多时间花在提交代码和参与社区贡献上。

所以,你应该如何使用 GoodFirstIssue.org?

简单来说,如果你没有目标,那么直接用筛选,按照可量化的指标来筛选即可;如果有目标领域,不如直接进提前筛选好的领域分类里快速看要贡献的 Issue 。

如果你不知道自己要做什么,但有一些模糊的目标,那么最简单的方法是,打开高级筛选,根据你的喜好,选择适合你的 Issue;比如筛选已标记 Good First Issue 的 Issue,难度为 2/5 或者 3/5 (越高越难),然后选择你想要的 Star 的范围(虽然越高影响力越大,但意味着竞争越强)。然后点击应用筛选,就可以选择出你喜欢的想要的 issue 。

如果你已经有明确的发展的领域,那么提前分好的类别,可能更适合你。你可以选择语言、框架、领域,点击进去后,你可以找到这个领域内的各种问题,然后,参与到其中,让别人看见你在这个领域交过的 PR。

甚至是,如果你已经在参加一些社区活动,那么基于活动的分类,也可以帮助你快速找到属于 Hacktoberfest 或者是 Google Summer Of Code 的 Issue;对了,现在刚好就在 Hacktoberfest 的时间范围内,为什么不参与一下呢?(快速查看符合预期的 issue)

总结

开源不是唯一能写代码的地方。但它是少数不需要先被录取、就能把代码、场景和反馈都摊开的地方。我做 GoodFirstIssue.org,不是为了让新手少焦虑,是为了让他们把时间从翻 issue 挪到改代码上,从而更好的在开源社区中,去构建自己的信誉。

So,要不要来试试看 https://www.goodfirstissue.org/zh ?

使用 Docker 部署 Github Actions Self Hosted Runner

近些年来,我的个人项目基本上也是托管在 Github 上的。主要的原因一方面是 Github 身边的 Git Repository 使用起来体感还不错,另一方面,也是很重要的便是 Github 的 Actions。

作为一套开箱即用的 CICD 系统,Github Actions 给所有的 Public repo 提供了免费的运行时长,而对于 Private Repo,则没有那么多;我虽然购买了 Github Pro,每个月有 3000 分钟的额度可以使用,但随着你的项目越来越多,在进行的 CICD 检查越来越多,导致 3000 分钟的额度捉襟见肘,经常月中就没有了。

所以,我就瞄上了 Github Actions 的 self-hosted runners。其实如果你有足够的主机,配置起来挺简单的。打开 Github 仓库页面,找到 设置 – Actions – Runner 页面,就可以新增 self-hosted Runner,在新的引导页面,选择你要使用的操作系统,然后跟随下面的指引安装即可,简单易行。

不过,这个方案也有个问题,一台主机只能安装一个 Runner,而一个 Runner 只能工作于一个仓库,对于项目比较多的人来说,还是不方便,所以就有了我研究使用 Docker 化部署的方案,在研究了前人的工作之后,我对这个方案进行了一定的优化,最终形成了你所看到的这个版本。

TL;DR

如果你不想看下面的细节描述,那比较简单粗暴,直接执行下面这个命令,就可以在你的 Docker 服务上启动一个容器作为 Github Actions 的 Self-hosted Runner。

docker run -d \
  --name actions-runner \
  --restart unless-stopped \
  -e RUNNER_URL=https://github.com/<owner>/<repo> \
  -e RUNNER_REGISTRATION_TOKEN=<token-from-config-sh-command> \
  -v /var/run/docker.sock:/var/run/docker.sock \
  bestony/actions-runner:latest
Code language: Bash (bash)

其中,第四行的 Runner URL 是指你自己的 Github 的仓库地址,直接配置上就行;

而第五行的 Token 则是你在 Runner 指引页面看到的 Config 的 Token

当你执行完成后,2-3 分钟,就可以在 Github Actions 设置页面的 Runner 看到刚刚启动的 Runner 了。

接下来,就是在你所有要使用 self-hosted runner 的 job 上,修改他对应的 run-on 配置即可

# Use this YAML in your workflow file for each job
runs-on: self-hosted
Code language: YAML (yaml)

支持哪些平台

我在代码层面支持了 Linux 和 Windows ,并预打包了对应的 Docker 镜像。Linux 使用的是 Ubuntu 24.04;Windows 使用的是 Windows 2022;此外,支持了 Linux的 x64,arm x64 和 arm v7, 如果你是本地 NAS 使用,应该也可以跑。不过我自己还没测试 ARM 的环境,如果你遇到问题,也可以直接提 issue 来反馈。

打包好的 Docker 镜像放在 https://hub.docker.com/r/bestony/actions-runner ,你可以在 DockerHub 页面上看到。

原理是什么?

这个实现的原理本质上就是借助 VM 的 Container 机制,将 Github Actions 的 Runner 二进制文件放在 Docker 镜像中,并通过托管 Docker Socket,来实现让容器内的 Runner 文件可以管理 Docker 容器,从而在后续执行 Job 的时候,创建对应的容器。

代码在 https://github.com/bestony/actions-runner (欢迎来 Star)

配置缓存

Github Actions 提供了缓存能力,从而可以让不同的 job 和 不同的 run 可以使用相同的缓存,减少一部分缓存的时间和成本。 Self Runner 如果想要使用缓存,并让后续的 Runner 都可以使用缓存,你可以这样配置。关于缓存服务的自部署的更多信息,你可以参考 GHA Cache Server 的文档

创建一个网络

首先,你需要创建一个 Runner 专属的网络,从而让所有的 Runner 共享一个 Cache Server,方便后续使用。比如我们这里创建一个名为 Runner 的网络

docker network create runner

启动一个 Runner

当你已经有了提前创建好的 Repo 后,就可以根据需要来创建 Runner 了。你可以直接复制我下面的这段配置,存储成为 DockerCompose.yml ,修改环境变量配置,并使用 docker compose up -d 命令来启动,从而创建一个 Runner ,并启动相应的缓存服务器,来实现启动一个带缓存的 Runner。

services:
  runner:
    image: bestony/actions-runner:latest
    restart: unless-stopped
    env_file:
      - path: .env
        required: false
    networks:
      - runner
    environment:
      RUNNER_URL: ${RUNNER_URL:-}
      RUNNER_REGISTRATION_TOKEN: ${RUNNER_REGISTRATION_TOKEN:-}
      REPO: ${REPO:-}
      TOKEN: ${TOKEN:-}
      ACTIONS_RESULTS_URL: ${ACTIONS_RESULTS_URL:-http://cache:3000/}
      RUNNER_NAME: ${RUNNER_NAME:-}
      RUNNER_LABELS: ${RUNNER_LABELS:-}
      RUNNER_WORKDIR: ${RUNNER_WORKDIR:-_work}
      RUNNER_EPHEMERAL: ${RUNNER_EPHEMERAL:-false}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mode: replicated
      replicas: ${RUNNER_REPLICAS:-4}
      resources:
        reservations:
          cpus: "${RUNNER_RESERVED_CPUS:-0.5}"
          memory: ${RUNNER_RESERVED_MEMORY:-1024M}
        limits:
          cpus: "${RUNNER_LIMIT_CPUS:-2.0}"
          memory: ${RUNNER_LIMIT_MEMORY:-4096M}

  cache:
    container_name: cache
    image: ghcr.io/falcondev-oss/github-actions-cache-server:latest
    restart: unless-stopped
    networks:
      - runner
    ports:
      - "3000:3000"
    environment:
      API_BASE_URL: http://cache:3000
    volumes:
      - cache:/app/.data

volumes:
  cache:

networks:
  runner:
    external: true
Code language: YAML (yaml)

上面这段配置主要有几个核心配置需要关注:

  1. deploy 段:这部分主要是你会跑多少个 Runner,如果你的项目需要更多的 Runner 来执行 Job ,这里就可以调整的大一些;包括每个 runner 预约多少资源,能实际使用多少资源,都可以配置。
  2. networks 段:这部分核心是要使用之前创建好的 Runner 的网络,从而让后续的所有的 Runner 都统一使用一个网络。

启动一个新的 Runner

当你完成上述的配置,但发现你需要一个新的 Self-hosted Runner 的时候,就可以复制下面不包含 Cache Server 相关的配置,直接使用了。非常的方便


services:
  runner:
    image: bestony/actions-runner:latest
    restart: unless-stopped
    env_file:
      - path: .env
        required: false
    networks:
      - runner
    environment:
      RUNNER_URL: ${RUNNER_URL:-}
      RUNNER_REGISTRATION_TOKEN: ${RUNNER_REGISTRATION_TOKEN:-}
      REPO: ${REPO:-}
      TOKEN: ${TOKEN:-}
      ACTIONS_RESULTS_URL: ${ACTIONS_RESULTS_URL:-http://cache:3000/}
      RUNNER_NAME: ${RUNNER_NAME:-}
      RUNNER_LABELS: ${RUNNER_LABELS:-}
      RUNNER_WORKDIR: ${RUNNER_WORKDIR:-_work}
      RUNNER_EPHEMERAL: ${RUNNER_EPHEMERAL:-false}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mode: replicated
      replicas: ${RUNNER_REPLICAS:-4}
      resources:
        reservations:
          cpus: "${RUNNER_RESERVED_CPUS:-0.5}"
          memory: ${RUNNER_RESERVED_MEMORY:-1024M}
        limits:
          cpus: "${RUNNER_LIMIT_CPUS:-2.0}"
          memory: ${RUNNER_LIMIT_MEMORY:-4096M}

networks:
  runner:
    external: true
Code language: YAML (yaml)

总结

如果你和我一样,喜欢用 Github,但又没有足够多的 Github Actions 运行时长可用,或者你需要依赖一些你自己的内网环境,那么这个 self-hosted runner,就是为你准备的。

工程师如何把多个 Coding Agent 真正带起来:一套比“开更多聊天窗口”更像工程流程的方法

随着 Agent 的时代的到来和 AI Coding 工具的兴起,被 AI 冲击的最狠的软件工程领域也迎来一轮一轮变化;我也在这个过程中一轮轮迭代,使用不同的 Agent 的工具,来帮助我自己提升自己的工作效率。而随着我的 Coding Agent 的使用越来越多,我的问题不再是「有没有 Agent」、「有没有用好 Agent」,而是 —— 「在保证工程质量的基础上,如何把多个 Agent 同时安排出去,把结果收回来」。

现在的问题是 「如何 Scale UP 你的 Agent 」

虽然目前 AI Coding 的覆盖率依然有待提升,但是,对于不少逛少数派的工程师来说,大家大多已经会用 Claude Code、Codex、OpenCode 一类工具。大家所面临的问题不再是将 AI 引入自己的工作流,而是

  1. 你已经在使用 AI 了,但你手头有 100 个任务都需要推进,每个都是 P0 任务。
  2. 你其实也知道每个任务怎么做,但终究只有一个你;
  3. 你的精力足以支撑你一个个盯这些任务,你可能会考虑多屏幕开始干活,但终究还是只能同时看 3-4 个,就很难再更大规模的扩容。

真正卡住你的不是代码能力,而是并发调度能力

为什么 Codex / Claude 的 Terminal UI 不够?

Terminal UI 从体验上来看,还是一个 Chat Bot ,你给他安排工作,让他把事情做完,这个还是单个线程的工作流;如果你有多个任务、多条分支、多种不同风格的 Agent,不可避免的要用上 Terminal 自带的多 Tab、多窗口,抑或是使用 tmux 来帮助你复用窗口,开发者本人会成为任务调度的瓶颈。

而在这个过程中, Terminal UI 的限制也有贡献,让我们集中在小范围的核心信息中把工作完成。

而这个过程中,我认为最接近当下我眼中最优解的便是 —— Vibe Kanban。

vibekanban

Vibe Kanban 在解决什么问题

如果你认真用过一段时间 coding agent,就会发现单 agent chat 界面有几个很明显的问题。

  1. 它天然更适合顺序执行。你可以让它完成一个明确任务,但一旦任务开始变多,吞吐量很快就会被人类自己卡住。
  2. 上下文很容易混。你本来只是想修一个 bug,结果顺手又让它改了另一个功能,再过一会儿,你已经忘了这段对话到底是为了解决哪个问题。
  3. 计划和执行是黏在一起的。很多时候不是 agent 不会写,而是你还没把任务定义清楚,它就已经开始跑了。
  4. review 成本越来越高。你开了更多 agent,并不自动等于你获得了更高的工程效率,很多时候只是获得了更多需要你亲自收拾的 diff。

围绕着上面的这些问题,Vibe Kanban 做了一些优化,它不是新的 coding agent,而是一个把 planning、workspace、review、preview、PR 流程连起来的管理层。

这让软件工程任务从 Agent Chat 窗口中的单线程流转变成了我们更熟悉的 Kanban 机制,有了状态、有了任务,每个任务还有单独的 workspace ,我们可以使用我们熟悉的工作流,来优化完整的 Agent 落地行为。

  • 先拆 issue
  • 再明确计划
  • 再开 workspace
  • 再让不同 agent 去执行
  • 再 review diff
  • 再决定是否收敛、合并

我自己的 Vibe Kanban 的工作流

目前我是一个 VibeKanban 的重度依赖者;我会把我平时看到的一些任务直接添加到 Vibe Kanban 上。

Vibekanban.png

在完成任务细节的补充后,直接创建一个新的任务工作区,选择 Agent,并让 Agent 强制开启 Plan Mode,通过 Plan Mode 消除任务中的歧义。

H8P1JaMQIe.png

最终汇总形成完整的设计方案后,批量让其执行起来;

任务

因为 Agent 执行是需要周期的,所以我可以同步开启 N 个 Issue ,让 Agent 并行处理,并在 Agent 需要我介入的时候,参与到任务当中。

并行描述.png

通过这样的方式,我快速且稳定的交付了大量的高质量的需求。

我所看中的 Vibe Kanban 的几个设计细节

1. Kanban 机制

当你 Scale 的 Agent 足够多的时候,你会发现,真正限制你的不是 Agent 的数量(这个很好突破,是钱的问题),真正限制的是你的认知带宽、你的管幅。一个你更熟悉的方式,会帮助你减少 Landing 和理解的成本。

Kanban 简单的 TODO、Doing、DONE 机制,让任务本身一目了然,我们可以轻松的把任务的当前状态、Next Action讲清楚、想清楚,也可以收敛每个任务的上下文。

2. 强制开启 Plan Mode

大部分人类在给 AI / Peer 安排任务的时候,其实是很差的,我们并没有说清楚需要做什么。这是因为我们脑海中有远超我们所表达的上下文。但如果我们没有安排出清楚的上下文时,不管是 AI 还是我们的 Peer ,其实都很难做好这些事。如果想要更好的让 AI / Peer 把事情做好,就需要把上下文交代的清楚。

既然人类不习惯,那不妨让 AI 来主动追问,通过 Plan Mode 来强制让 AI 主动发现任务细节中的歧义,并主动对话来消除歧义。

3. Agent 并发接入 & 自动的 Workspace 接入;

想要最大化你的时间和精力,一个比较好的办法是让你的下属 AI 能够并发进行处理任务,并充分的利用你的精力去做重要的决策。那么就需要同时启动多个 Agent,他们并行做计划、并行推进,从而打满你,让你可以始终处在决策、确认、检查、休息的环节,而不是无所事事。

Vibe Kanban 和 Git Worktree 做了非常好的集成,当你开始一个工作区的时候,他会自动为你创建一个新的 Worktree,并可以在你的任务完成后, Merge 进入你的 Main 分支当中,非常的方便。不仅如此,当你的变更和上游发生了冲突的时候,它还提供了 AI 解决冲突的能力,从而将复杂的分支管理给简化了。

4. 一丝丝幽默感

Vibe Kanban 是支持完成任务通知你的,而通知的方式也颇为幽默。他们提供了很多种音频来提醒你,比如 —— 牛的哞哞叫。是的,我的提醒音频就是牛叫,提醒我,我的 AI 牛马把活干完了,需要我去看了。

很好玩。

Vibe Kanban 适合谁?

从我自己的时候来看, Vibe Kanban 非常适合所有的「软件工程师」,他们的问题是虽然知道每一个任务应该怎么做,但没有足够的上下文带宽来并行,Vibe Kanban 提供了串行转并行的能力,帮助你更加快速的将自己 Scale 起来。

Vibe Kanban 真的像 —— 你将自己复制了 100 份,去分别执行不同的任务。你觉得自己分身乏术?这个就是那个分身之术。

但同样的,Vibe Kanban 也不是银弹,它不适合:

  1. 不适合还不会拆任务的人
  2. 不适合把 AI 当自动许愿机的人
  3. 不适合只想偶尔用一下 agent 的轻量用户
  4. 不适合还没有 review 能力的人

遗憾的是, Vibe Kanban 终将 Sunset

虽然 Vibe Kanban 我说的很好,但不得不说,就如标题一般 —— Vibe Kanban 终将 Sunset。这也是促使我来写这篇文章的重要原因,即将转向的 Vibe Kanban 值得你尽早体验。

VibeKanban 的方向是明确的,它代表了一种易于理解、易于拆解方案的 Agent Scale 实现。作为一种工作方式和实现,他无疑是成功的,但作为一个商业化产品来说,他可能又是失败的。 Vibe Kanban 的母公司 Bloop AI 已经宣布 Shutdown ,Vibe Kanban 项目也将专项 Open Source & community maintained。

在今天,如果你还困惑于如何 Scale Up 自己,最大化发挥出 Agent 的潜力,我觉得 Vibe Kanban 是一个你的必经之路。未来或许会有更好的解决方案,但 Vibe Kanban 的 Sunset 也浇灌出了新的可能性。

写在最后

Vibe Kanban 不仅仅是一个工具,他更代表着 —— 让软件工程师更加「软件工程」,我们更关心软件工程架构,而不是写代码。每一个软件工程师都可以依靠自己的经验管理一组 Coding Agent ,去做计划、去做执行。

Vibe Kanban 未必适合所有人,但如果你已经不满足于每次只用一个 Agent Chat 窗口,那么 Vibe Kanban 非常适合你试一试。

如何使用 Vercel Seat Saver skill 帮你将 Vercel 组织订单账单降低至 20 美元?

前些日子,我发了条推说,我说看到了一个 Skill ,可以帮助你得组织快速降低你的订单金额,并说要写篇 Blog 来介绍他。现在,它来了。

https://x.com/xiqingongzi/status/2044784860058001618

这个 Skill 叫 「vercel seat saver」,我注意到它还是身边的朋友告诉我 —— Hey ,我发现一个很有用的 Skill,你也试试看。

原理

Vercel 的计费逻辑是,如果你是付费团队服务,那么你的费用实际上是席位费,按照组织协作的席位来收费。但按照席位收费的同时,Vercel 并不会将你的可用用量给提升,实际上即使你团队是 20 个人,你拿到的可用的用量额度还是和一个人一样。

而我们使用 vercel 的主要原因是我们希望享受到 vercel的自动构建服务,快速预览服务。而这些服务,其实并不一定通过 vercel 自身的账号关联来完成,完全可以通过 vercel API 或者 vercel CLI 来完成。就像我这篇 Blog 一样,其实可以通过配置来完成。只不过,vercel-seat-saver 提供了更 Agentic 的方式,使用一个 Skill ,帮你完成所有的配置和操作。

之前的 Blog: 如何免费为你的组织项目配置 Vercel

如何使用 Vercel Seat Saver?

和把大象装进冰箱里一样,使用 Vercel Seat Saver 一共需要三步

1. 安装 Vercel Seat Saver Skill

打开你要处理的代码仓库,并执行 如下命令来安装 Vercel Seat Saver Skill

npx skills add actionbook/postagent

5qur2T7t7t.png

2. 使用 Claude 打开,并调用 Vercel seat saver 来优化你的配置

安装完成后,直接使用 claude 打开你的项目,并输入 /vercel-seat-saver 来唤起 vercel seat saver.

36qVBQ5AWX.png

3. 跟着Claude 配置

vercel seat saver 会在启动完成后,自动获取你的当前仓库的情况,并指引你去完成具体的动作,并给出对应的命令,来完成配置。

lwcx57fdRu.png

配置完成后,和他说,继续,等待他的自行处理

pG9phyocmI.png

这个过程中,你只需要跟随他的建议,去做一些简单的处理和判断即可

CPZDiZorvk.png

做完所有决策,他就会自动帮你去取消 Vercel 的关联,然后替换成 Github Actions 的自动构建和推送。

一切配置完成后,你接下来要做的,就是去 Vercel 当中,移除组织当中的人,让大家在 Github 上协作就好,不再需要占用 Vercel 席位,降低月账单。

感想

以前我要自己摸索很久,甚至还值得我写 Blog 记录下来的事情,今天一个 Skill 就完成了。。。就。。。有种自己被蒸馏的感觉。

使用腾讯云云迁移服务将 R2 存储中的文件迁移到腾讯云 COS 中

最近把这个域名重新备案了一下,就可以利用起我在腾讯云上的闲置服务器。既然要迁移服务器,不妨将图床一并迁移,这样后续使用起来也方便,国内的读者加载起来速度也快。

不过,这些年大量使用,我的文件还是挺多的….足足有 13GB 的文件,手动一个个搬迁可就累死了;于是乎,我决定试试腾讯云的迁移服务,来帮助我把 R2 上的文件迁移过来。

获取配置信息

想要使用腾讯云提供的云迁移(CMG)服务,则需要获取一些配置信息,具体包括:

  • Cloudflare R2 的 Access ID 和 Secret Key
  • 腾讯云的 Access ID 和 Access Key,创建好的 Bucket(要迁移的目标)

R2 的相关配置可以在 CloudFlare R2的配置页面找到;如果没有的话,你就创建一个新的。

腾讯云的则可以在腾讯云密钥管理中获取,建议创建一个新的用户,并授予 QcloudMSPFullAccess 及 QcloudCOSAccessForMSPRole 策略,点击子账号可以看到如下图的两个权限。

配置云迁移

完成账号的确认后,接下来就是配置云迁移。打开云迁移中的「对象存储迁移」,或者直接打开这个链接,就直接进入云迁移的页面。

源站配置

接下来配置云迁移的具体配置,点击新建人数,在新的页面中,输入你的 CloudFlare 配置信息,具体可以参考下面的截图:

  • AK/SK: 你从 Cloudflare 获取的相关参数;
  • 桶名称:你的 R2 Bucket 的名称;
  • 空间域名:你的 R2 的域名,是 uid.r2.cloudflarestoage.com,比如我的是 https://24071135c3ad9d9196e7e45e33948d28.r2.cloudflarestorage.com。
  • 桶的所在地:比如我的是亚洲,就选 apac。

源站中的其他选项可以根据需要选择,如果你是完整迁移,和我保持一致即可。

目标站点配置

接下来是配置迁移目标,这里指标支持迁移到腾讯云自家的 COS 上;填入你的 Secret ID 和 Secret Key,然后可以直接在下面输入具体的 Bucket 名称,或者填完后点击下拉框右侧的刷新按钮后,选择合适的。

其他的选项,如果你和我一样是整个 Bucket 迁移,则可以保持相同的配置,直接整个迁移。

配置完成后,点击最下方的新建并启动,就会启动搬迁。接下来就回到任务列表等刷新即可,等待他自己搬迁完即可。实测搬迁速度很快,13G 的文件,8 分钟就搬迁完成了(还是我限制了搬迁的带宽),如果是不限制,估计 2 分钟就能搬迁完成。

如果你需要和我一样,从外部的 S3 将文件搬迁到腾讯云的 COS 上,不妨试试看这个方法~

django-storages 配置使用 S3 Provider 支持 Aliyun OSS 使用

阿里云 OSS 提供了 S3 的兼容,所以如果你在 Django 应用当中,希望使用 OSS 作为文件存储的话,可以参考下方的说明,来使用。

安装

首先,你需要执行如下命令安装 django-storages 的 S3 兼容

# uv
uv add django-storages[s3]
# pip
pip install django-storages[s3]
Code language: CSS (css)

配置

接下来,就是在你的项目文件夹中的 settings.py 中添加如下配置

AWS_S3_ACCESS_KEY_ID = env("AWS_S3_ACCESS_KEY_ID") # 你在阿里云拿到的 ACCESS_KEY
AWS_S3_SECRET_ACCESS_KEY = env("AWS_S3_SECRET_ACCESS_KEY") # 你在阿里云拿到的 Secret Key
AWS_STORAGE_BUCKET_NAME = env("AWS_STORAGE_BUCKET_NAME")# 你在阿里云拿到的 Bucket Name
AWS_LOCATION = env("AWS_LOCATION") # 你的文件上传路径,比如  uploads/,你的所有文件都会上传到这个路径下
AWS_S3_REGION_NAME = env("AWS_S3_REGION_NAME") # 你的 OSS 的可用区,比如 oss-cn-beijing
AWS_S3_CUSTOM_DOMAIN = env("AWS_S3_CUSTOM_DOMAIN") # 你自己的自定义域名,以便于后续访问的时候使用。如果不知道的话,可以填 bucket 的默认域名。
AWS_S3_ENDPOINT_URL = env("AWS_S3_ENDPOINT_URL") #  你的阿里云 Endpoint URL,比如 https://oss-cn-beijing.aliyuncs.com

AWS_S3_ADDRESSING_STYLE = "virtual" # 阿里云只支持二级域名的形式
AWS_S3_SIGNATURE_VERSION = "s3" # 阿里云只支持 v2 版的签名逻辑

# 配置默认使用 S3 Storage,即使用 OSS 的 URL
STORAGES = {
    "default": {
        "BACKEND": "storages.backends.s3.S3Storage", # 使用 S3  Storage
    },
}

Code language: PHP (php)

参考上方的配置,添加配置项后,保存,并重启服务器,即可在代码中进行测试。

测试代码

你可以执行 python manage.py shell 并执行如下代码,如果无报错,且可以在 OSS 控制台看到文件,则说明你的配置成功了。


from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
content = ContentFile(b"Hello World!")
path = default_storage.save('test_file2.txt', content)
print(f"文件保存路径: {path}")
# 测试文件读取
if default_storage.exists(path):
    with default_storage.open(path, 'r') as f:
        content = f.read()
        print(f"文件内容: {content}")
# 测试文件URL生成
url = default_storage.url(path)
print(f"文件URL: {url}")
# 测试文件删除
default_storage.delete(path)
print(f"文件是否存在: {default_storage.exists(path)}")

Code language: PHP (php)

一些我自己用的还不错的 Chrome 插件

英语/日语翻译:沉浸式翻译

我的日常要看到很多英文文章和网站,因此,可以借助沉浸式翻译,帮助我快速翻译多种语言为中文,从而降低我在阅读不同语言内容的障碍。

快速搜索:超级搜索

超级搜索支持我快速的选中词汇并进行搜索,对于一些特定的场景下,会非常有帮助。比如我在浏览网页的时候,发现了一部电影,想快速在豆瓣电影中找到并标记,就可以借助超级搜索,配置一个搜索关键词来实现。

SEO 检查器:AITDK

AI TDK 是我用来检查自己的网页是否完成了一些基本的 SEO 设置的工具。当我上线了一个新的网站后,就会打开 AITDK,然后查看哪里的信息还不完整,需要补充的。就可以继续去补充相应的内容。

JSON 查看:JSON Viewer

在开发的时候,经常会有要查看服务端返回的 JSON 的情况, 借助 JSON Viewer 可以将不容易看明白的 JSON 给格式化了,方便你快速定位要看的 JSON。

广告拦截:AdGuard

广告拦截我选择了 AdGuard,有了它,我看 Youtube 再也没有广告了。。。

复制为 Markdown:Copy as Markdown

因为经常要将部分内容复制为 Markdown,方便在我的其他工具中使用,所以我安装了这个 Copy as Markdown 插件,方便自己随意复制。

页面增强:篡改猴

当我需要对一些网页做一些快速的改造,但同时又不想写成 Chrome 插件的时候,就会选择写成油猴脚本,然后放在篡改猴里来用,非常方便。

灵感记录:Memos

我自己部署了一个 Memos ,用于记录自己的灵感和想法。因此,我使用了一个 Chrome 插件,来方便我记录。

记一次发现小宇宙 iOS 版的跳转注入漏洞

漏洞风险:可以在其他应用借助小宇宙端内跳转任何网页。

此文章发布前,小宇宙已经修复了这个问题,所以你们可能不能复现这个问题了。

先看漏洞效果,这个漏洞的问题是你可以在小宇宙里跳转到任何网站,甚至是 PornHub。不过这个 Bug 不重要,重要的是发现 Bug 的过程。

漏洞效果

0. 背景

在一天晚上,我在和朋友聊小宇宙的 URL Scheme,想要做个功能,可以实现一键打开小宇宙的节目页面。但卡在我面前的是,我不知道小宇宙的 URL Scheme 到底是什么。于是便开始了我的 Hack 之旅,也就找到了小宇宙的这个安全漏洞。

1. 找到小宇宙的 URL Scheme

由于 Scheme 是在 App 中定义的,所以当我想到要找 Scheme 之后,第一反应的是去拿 iOS App 的 Info.plist(因为 iOS 是将 Scheme 定义在 info.plist 当中)。

过去需要通过 IPA 备份、越狱等方式来获取到这个文件,不过得益于 M 系列支持在 macOS 上运行的原因,现在 IPA 的获得变得非常的简单。 先在 App Store 安装小宇宙,并在「应用程序」中找到小宇宙。

然后右键点击小宇宙,点击「显示包内容」

然后看到这样的内容,WrappedBundle 是一个假的应用程序,所以继续点击 Wrapper 往里跳转。

然后会发现里面还有一个 Podcast 应用,这个才是真正的小宇宙的 IPA 包。然后继续点击「显示包内容」

在包内容当中可以看到 info.plist 文件,使用 Xcode 或者 VSCode 打开 info.plist 文件。

在 info.plist 文件中,搜索 CFBundleURLSchemes ,找到了小宇宙的 URL Scheme(里面有很多个,但很多都是其他应用的,试一下就可以发现):cosmos://。

2. 找到 URL Scheme 能够打开的页面

找到了 URL Scheme,只能通过 cosmos:// 打开应用首页,无法满足我的需求,于是开始继续寻找可能的 URL Scheme 。一般来说,这个时候就只能继续反编译 IPA 包或者 APK 包了。不过对于我来说,这些不是一个好的选项(成本太高)。

然后想到,小宇宙的网页似乎是提供了打开客户端的能力,所以可以从网页版找到突破口。通过简单的搜索,果然让我在网页前端找到了突破口。找到了 7 个 Scheme 。

当然,中间存在一些重复的 Scheme。所以最终梳理出来的 Scheme URL:

  • cosmos://page.cos/discover:打开发现页
  • cosmos://page.cos/shownotes/EPISODE_ID:打开节目的 Shownote 页面
  • cosmos://page.cos/episode/EPISODE_ID:打开节目的详情页
  • cosmos://page.cos/webView?url=:意义不明,看起来像是打开一个特定的 URL
  • cosmos://page.cos/web?url=:意义不明,看起来像是打开一个特定的 URL

3. 发现问题 URL Scheme

前面的三个很正常,但后面的两个带 URL 的引起我的注意 —— as a Hacker,你知道的,任何一个可能的输入框都可能成为我们的注入点,于是乎,我就构建了一个链接,来打开我的 Blog

cosmos://page.cos/web?url=https%3A%2F%2Fwww.ixiqin.com
Code language: JavaScript (javascript)

将这个链接使用 Safari 打开,就会自动唤起小宇宙,并打开我的播客。

至此,我发现了小宇宙这个跳转注入漏洞,并快速将其反馈给小宇宙官方同学。

复盘:如何规避这样的问题

在这个 Case 当中,小宇宙因为没有设置 URL 跳转的白名单,导致实际上出现了跳转恶意网站的风险。理论上,作为应用提供商,出于安全合规的视角,最好是控制 URL 跳转的域名和空间,避免被恶意滥用。

或者也可以参考我们现在见到的很多网站,在外部跳转时加一个风险提醒。

如果在这个 Case 当中,小宇宙在一开始就限制了可以打开有限域名,那也不会出现如今我这次的漏洞问题。

这个问题风险大么?

取决于如何定义和如何使用。如果只是跳转一些常规网站,自然是风险不大的。但如果不受限制的,比如跳转到一些诈骗网站,可能风险就是大的。

如何快速清空七牛的存储空间中的所有文件,并删除存储空间

如果你和我一样,有很多历史的文件存储在七牛上,但如今已经不再需要使用,那么就可以考虑删除七牛的存储空间,来节省费用。

但七牛为了保证安全,所以要求必须删除所有的文件后才能删除空间,以避免误删除,所以需要一个个删除所有的文件。为了快速删除七牛存储空间的文件,我写了个简单的脚本,帮助你快速删除七牛空间下的所有文件。

具体操作可参考如下脚本,你只需要

  1. 在本地安装七牛 SDK :pip install qiniu;
  2. 创建一个新文件 run.py 并复制下方的代码,修改其中的访问密钥和存储空间名称
  3. 执行 python run.py 就可以了。
# -*- coding: utf-8 -*-
# 导入七牛云 SDK 所需的模块
from qiniu import Auth
from qiniu import BucketManager, build_batch_delete
# 七牛云账号的访问密钥
access_key = '你的 ACCESS Key'
secret_key = '你的 Secret Key'
# 要清理的存储空间名称
bucket_name = '你要清空的空间名称'


# 使用 AK、SK 初始化授权对象
q = Auth(access_key, secret_key)
# 初始化存储空间管理器
bucket = BucketManager(q)

# 设置每次列举的最大条目数
limit = 1000

# 循环列举并删除存储空间中的文件
while True:
    # 列举存储空间中的文件
    # ret: 包含文件信息的字典
    # eof: 是否已列举完所有文件
    # info: 请求的状态信息
    ret, eof, info = bucket.list(bucket=bucket_name)
    # 从返回结果中提取文件名列表
    keys = [item['key'] for item in ret['items']]
    # 构建批量删除操作
    ops = build_batch_delete(bucket_name, keys)
    # 执行批量删除操作
    ret, info = bucket.batch(ops)
    # 检查删除操作是否成功
    if info.status_code == 200:
        print(f"success delete {len(keys)} files!")
    
    # 判断是否已经列举完所有文件
    if eof:
        break
    else:
        continue
# 输出清理完成的提示信息
print(f"delete all files in {bucket_name}")
Code language: Python (python)

执行成功后,你会看到如下面这样的命令,接下来等他自动执行即可,你就不用做任何事情了。

当工具提示你 delete all files in 你的 kodo 名时,你就可以回到七牛控制台,删除掉空的 kodo 了。