首页 / 资讯中心 / 文章详情

superpowers:用Node.js打造个人效率命令行工具箱

superpowers:用Node.js打造个人效率命令行工具箱 ★ FEATURED ARTICLE
1. 项目概述superpowers 是什么为什么值得折腾先说结论这个项目名字叫 superpowers本质是一个个人效率工具集。我一直觉得程序员和重度电脑用户真正需要的不是什么花哨的黑科技而是把自己天天都在做的重复劳动全部自动化。名字叫 superpowers意思很直白——装上它你就等于给自己加了一组“超能力”。项目本身并不复杂它是一个基于 Node.js 的命令行工具安装到全局之后你可以在终端里敲几个简短命令完成文件批量重命名、快速记录灵感、初始化开发环境、定时清理临时文件、甚至把常用目录结构一键生成出来。听起来好像每个功能单独拆开都有现成工具但把它们集成到一个统一入口里用一套配置管理体验是完全不同的。这篇博文会把整个 superpowers 项目的设计思路、核心代码、实操步骤、踩坑记录全部摊开讲。适合以下几类人参考想把手头重复性操作整理成自己工具箱的开发者、刚入门命令行想找一个实战练手项目的朋友、以及所有被日常文件整理和项目初始化折磨的“数字杂务工”。我假设你已经会装 Node.js、能看懂基础 bash 语法其余内容我会尽量写得细照着做就能跑起来。2. 整体设计与方案选型为什么用 Node.js 而不是 Shell2.1 先想清楚“超能力”到底是什么在动手写代码之前我花了一个晚上列需求。列完之后发现我真正想要的 not 是一个宏大的平台而是几件小事批量给下载文件夹里的文件改名、快速新建一篇带日期的笔记、一键把我的开发环境检查一遍、把堆积如山的临时文件丢进回收站。这些事如果分别去装不同软件反而更麻烦。于是我把需求收敛成一句话所有通过图形界面需要三步以上操作、通过命令行需要记一大串参数的事情都值得变成一个五个字符以内的短命令。这个项目所有功能都围绕这句话展开。比如 Windows 下给一百个文件加序号前缀正常操作要么写脚本要么装改名软件在 superpowers 里就是sp rename --prefix done。这种“一个词就能调用一个自动化流程”的设计思路才是这个项目叫“超能力”的真正原因。2.2 为什么不用 Python 或 Shell我承认这个项目用 Python 或者纯 Shell 也能写而且有些功能写出来更短。但经过实际对比我最终还是选了 Node.js原因有三个。第一是分发和安装的问题。Node.js 生态里把命令行工具发布到全局非常自然只需要在 package.json 里配置一个 bin 字段npm install -g之后全世界都能直接用不需要考虑 Python 版本、pip 源、虚拟环境这些问题。第二是跨平台字符串处理。Shell 在 Linux 下很舒服但一旦涉及到 Windows 的文件路径、中文文件名编码、PowerShell 兼容性就是一场灾难。Node.js 对路径处理的 API 非常成熟path模块一套代码在所有平台都能跑。第三是我本人对 JavaScript 最熟如果要在这个工具集里继续加功能、写插件维护成本最低。提示如果你完全没装过 Node.js先去官网下一个 LTS 版本一路下一步装完就行。装完在终端里执行node -v能输出版本号就说明环境 OK。选型这件事我从头到尾都在跟自己强调不要追求“最酷的”要追求“最省事的”。把这套工具做出来不是用来炫技的是用来过日子用的。2.3 命令设计每个子命令都是一个小杠杆整体结构上我参考了很多成熟 CLI 工具的做法用 commander.js 解析子命令。顶层命令名是spsuperpowers 的缩写下面挂了一组子命令子命令功能示例sp note快速新建带时间戳的笔记sp note 会议纪要sp rename批量重命名/添加前缀后缀sp rename --prefix donesp init初始化一个标准项目模板sp init myappsp doctor检查开发环境是否就绪sp doctorsp clean清理指定目录下的临时文件sp clean ./downloadssp list列出当前配置和常用目录sp list为了减少打字量子命令的别名也做了设计sp n等于sp notesp r等于sp renamesp i等于sp init。核心逻辑很简单每天要敲几百遍的命令必须让手指形成肌肉记忆。这套别名设计也是项目里我最满意的一部分。3. 核心细节解析与实操要点五个高频命令的内部逻辑3.1sp note不让灵感跑掉的极速记录这个命令是我平时用得最多的也是整个项目里最简单、最“优雅”的一个。它的核心逻辑非常简单在预设的笔记目录下以当天日期为文件名新建一个 markdown 文件如果文件已经存在就直接追加如果用户传了标题就自动生成带二级标题的模板。关键点在于三个细节。第一笔记目录不能写死应该从配置文件里读取并且在第一次运行时自动创建。第二文件名格式我用了YYYY-MM-DD.md这样所有日记按时间排序非常自然。第三当文件不存在时自动写入一个模板头包括日期、星期、用户传入的标题这样每次新建笔记都自带格式不需要再手动排版。// note.js 核心逻辑 const fs require(fs); const path require(path); const util require(util); async function createNote(config, title) { const date new Date(); const fileName ${date.getFullYear()}-${String(date.getMonth() 1).padStart(2, 0)}-${String(date.getDate()).padStart(2, 0)}.md; const filePath path.join(config.notesDir, fileName); if (!fs.existsSync(config.notesDir)) { fs.mkdirSync(config.notesDir, { recursive: true }); } if (!fs.existsSync(filePath)) { const week [日, 一, 二, 三, 四, 五, 六][date.getDay()]; let content # ${fileName.replace(.md, )} 星期${week}\n\n; if (title) { content ## ${title}\n\n; } fs.writeFileSync(filePath, content); console.log(笔记已创建${filePath}); } else { const lines title ? \n## ${title}\n\n : ; fs.appendFileSync(filePath, lines); console.log(笔记已追加${filePath}); } } module.exports { createNote };使用效果是我脑子里闪过一个想法立刻切到终端敲sp n 给API加缓存两秒钟完成后继续手头的事。这个动作现在已经变成我的条件反射比掏出手机打开备忘录还快。不要低估这个简单命令的威力记录速度决定了你把多少灵感真正变成了行动。如果你尝试复现可以把config.notesDir指向任意一个同步网盘文件夹比如 OneDrive 或坚果云这样就等于拥有了一个跨设备自动同步的随手记系统。3.2sp rename批量改名的安全姿势批量重命名是所有文件管理需求里最频繁、也最容易翻车的一类。这个命令我设计的时候优先保证的不是功能丰富而是安全。实现上有两层保障所有操作先进入“预览模式”模拟改名之后把结果打印出来加--force参数才会真正执行真正执行之前把所有旧文件名和新文件名写入一个 history 文件万一改错了还能用sp rename --undo回滚。// rename.js 核心逻辑 const fs require(fs); const path require(path); function buildNewName(oldName, options) { const ext path.extname(oldName); const base path.basename(oldName, ext); let newBase base; if (options.prefix) newBase options.prefix newBase; if (options.suffix) newBase newBase options.suffix; if (options.replace) { const [from, to] options.replace.split(:); newBase newBase.split(from).join(to); } if (options.number) { // 在扩展名前插入序号序号位数根据总文件数动态计算 } return newBase ext; }实际操作中最常用的场景是从网上下载一批图片文件名全是IMG_20241201_xxx.jpg这种我想统一改成旅行_2024_01.jpg这种风格。只需要sp rename --prefix 旅行_ --number系统会自动扫描当前目录所有图片文件套上一层序号后批量处理。这里我特别想分享一个经验任何批量操作脚本最核心的代码不是怎么改名而是怎么防止你把自己坑了。我在这个命令里加了两个硬性保护文件重名时直接报错而不是覆盖遇到系统文件、隐藏文件时默认跳过。另外整个操作只在--force后才真正执行预览模式输出结果用绿色显示差异肉眼确认之后再动手。你如果自己写类似的脚本我希望你把这段设计思路抄过去而不是只抄代码。3.3sp init三秒钟生成标准项目骨架如果你同时维护好几个项目一定经历过这种场景每次新建目录都要手动创建 src、docs、test 文件夹还要初始化 git、写 README、配 LICENSE。sp init就是为了把这个过程压缩到三秒以内。项目模板放在一个 templates 目录里每种模板是一个独立的文件夹结构。比如 default 模板包含的项目结构如下. ├── src/ ├── docs/ ├── test/ ├── .gitignore ├── README.md └── package.json命令的逻辑非常直白读取模板目录递归复制到目标位置然后根据用户输入的参数替换模板里的占位符。所谓占位符就是模板文件里写的{{PROJECT_NAME}}、{{AUTHOR}}这种字符串复制完成之后用正则全部替换掉。# 使用方式 sp init myblog --template site --author 张三模板机制带来一个好习惯你不需要在每次建项目时重新思考该建哪些文件只需要维护一次模板之后的所有项目都长成同一种安全的形状。我在实际使用中还维护了一个 python-lib 模板专门用来快速生成 Python 库项目里面甚至自带了 tox 和 pytest 的基础配置。这套方法可以扩展到任何语言和工作流里本质上是在用一个模板文件抵抗每次从头开始的熵增。3.4sp doctor环境体检一步到位换电脑、入职新团队、重装系统之后最烦的事情是忘了自己原来装过什么工具。sp doctor解决的问题就是用一段脚本把常见开发工具全部检查一遍输出一张表格告诉你哪些装了、哪些缺了、版本分别是多少。实现上其实就是循环执行系统命令判断返回码。Node.js 里可以用child_process.execSync去跑git --version、docker --version、python3 --version这样的命令能正常返回就标记为已安装抛异常就标记为缺失。需要注意 Windows 和 Linux 下同一个命令的检查方式可能不同因此实现里要根据process.platform走不同分支。检查项我用一个数组管理每个元素包含工具名、检查和安装提示信息const checks [ { name: git, cmd: git --version, hint: brew install git 或 https://git-scm.com }, { name: node, cmd: node --version, hint: https://nodejs.org }, { name: npm, cmd: npm --version, hint: 随 node 自动安装 }, { name: docker, cmd: docker --version, hint: https://docker.com }, { name: python3, cmd: python3 --version, hint: brew install python 或 https://python.org }, ];这个命令没有复杂的算法但它最大的贡献是给了我一种掌控感。环境出问题的时候不用再挨个命令试一条sp doctor直接看到全貌。我甚至把团队新人的环境检查写了进去新同学入职第一件事就跑这个能省半天排查时间。3.5sp clean让临时文件无处可藏最后这个命令解决的是磁盘碎片问题。我的 Downloads 文件夹常年堆着一百多个文件里面一半是安装包、压缩包、临时图片手动清理又累又容易误删。sp clean的逻辑是扫描指定目录把超过一定天数的、带有特定后缀的文件移到回收站默认后缀包括.tmp、.log、.dmg、.zip、.part等。这里有个很关键的安全设计不要直接rm而是移动到系统回收站。因为直接删除的文件无法恢复一失手就是永久损失。Node.js 里实现移入回收站可以用fs.trash这个第三方库也可以自己维护一个.trash目录统一收纳等确认没问题后再手动清空。为了让这个命令更符合日常习惯我默认只清理 30 天前的文件并且每次执行前打印将要清理的文件清单加--dry-run参数还能只预览不执行。写这个命令的时候我有一个体会真正好用的自动化工具一定会给你留下反悔的余地。4. 实操过程与核心环节实现从零搭建完整环境4.1 初始化项目package.json 和目录结构现在开始从头搭建。先在工作目录创建项目名字就叫 superpowersmkdir superpowers cd superpowers npm init -y然后安装两个依赖commander.js 负责命令解析ini 负责读取配置文件npm install commander ini接下来创建项目的主入口。目录结构如下这是参考了很多开源 CLI 项目之后定下的最佳布局所有文件各司其职后期加功能不会乱superpowers/ ├── package.json ├── bin/ │ └── sp.js # CLI 入口 ├── lib/ │ ├── config.js # 配置读写 │ ├── note.js # 笔记命令核心 │ ├── rename.js # 批量重命名核心 │ ├── initTemplate.js # 项目模板核心 │ ├── doctor.js # 环境检查核心 │ └── clean.js # 清理任务核心 ├── templates/ │ ├── default/ │ └── site/ └── README.mdpackage.json 里要加一个 bin 字段这是让sp命令全局可用的关键{ name: superpowers, version: 1.0.0, bin: { sp: ./bin/sp.js } }4.2 注册全局命令让sp随处可用在 bin/sp.js 里写入口代码核心工作是注册所有子命令并分发到对应的 lib 模块#!/usr/bin/env node const { program } require(commander); const config require(../lib/config); const { createNote } require(../lib/note); const { renameFiles } require(../lib/rename); const { initFromTemplate } require(../lib/initTemplate); const { checkEnvironment } require(../lib/doctor); const { cleanDirectory } require(../lib/clean); const cfg config.load(); program .version(1.0.0) .description(superpowers个人效率工具箱); program .command(note) .alias(n) .description(快速创建带日期的笔记) .option(-t, --title title, 笔记标题) .action((options) { createNote(config.resolve(cfg.notesDir), options.title); }); program .command(rename) .alias(r) .description(批量重命名文件) .option(--prefix prefix, 添加前缀) .option(--suffix suffix, 添加后缀) .option(--number, 添加序号) .option(--replace from:to, 替换字符) .option(--force, 真正执行而不只是预览) .action(renameFiles); program .command(init) .alias(i) .description(初始化项目模板) .argument(name, 项目名称) .option(--template template, 模板名, default) .option(--author author, 作者名) .action(initFromTemplate); program .command(doctor) .alias(d) .description(检查开发环境) .action(checkEnvironment); program .command(clean) .alias(c) .description(清理临时文件) .argument([dir], 要清理的目录, .) .option(--days days, 文件保留天数, 30) .option(--dry-run, 只预览不执行) .action(cleanDirectory); program.parse(process.argv);写完入口之后执行npm link把命令挂到全局路径然后验证npm link sp --version如果输出1.0.0说明全局命令已经注册成功。这一步做完你就在自己的机器上“安装”了 superpowers。4.3 配置文件让工具记住你的习惯配置文件是整个工具的“记忆体”。我把它默认放在用户主目录下的.superpowersrc文件里采用 keyvalue 的格式简单好懂。用户第一次运行任何命令时如果配置文件不存在就自动生成一份默认配置notesDir~/Notes backupDir~/SuperpowersBackup projectRoot~/Work trashDir~/.superpowers_trashconfig.js 模块负责三件事读取并解析配置文件、把~转换成真正的用户主目录、在配置文件缺失时写入默认值。// config.js 核心逻辑 const os require(os); const fs require(fs); const path require(path); const ini require(ini); const CONFIG_FILE path.join(os.homedir(), .superpowersrc); const DEFAULT_CONFIG { notesDir: path.join(os.homedir(), Notes), backupDir: path.join(os.homedir(), SuperpowersBackup), projectRoot: path.join(os.homedir(), Work), trashDir: path.join(os.homedir(), .superpowers_trash), }; function load() { if (!fs.existsSync(CONFIG_FILE)) { fs.writeFileSync(CONFIG_FILE, ini.stringify(DEFAULT_CONFIG)); } const raw fs.readFileSync(CONFIG_FILE, utf-8); const parsed ini.parse(raw); return { notesDir: expandHome(parsed.notesDir), backupDir: expandHome(parsed.backupDir), projectRoot: expandHome(parsed.projectRoot), trashDir: expandHome(parsed.trashDir), }; } function expandHome(dir) { if (dir.startsWith(~)) { return path.join(os.homedir(), dir.slice(1)); } return dir; } function resolve(dir) { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } return dir; } module.exports { load, resolve, CONFIG_FILE };配置独立成模块之后所有子命令都不用关心配置存在哪里统一调用config.load()就行。这个设计后期给我省了非常多事比如要迁移工具时只需要把.superpowersrc文件拷到新机器上。4.4 完整实现一个大一点的命令clean 命令的边界处理为了让演示更完整我把 clean 命令的核心实现也放出来这个命令涉及很多边界情况的处理非常适合作为学习参考// clean.js const fs require(fs); const path require(path); const os require(os); const TEMP_EXTENSIONS new Set([.tmp, .log, .dmg, .part, .old, .bak]); function cleanDirectory(dir, options) { const absoluteDir path.resolve(dir); if (!fs.existsSync(absoluteDir)) { console.error(目录不存在${absoluteDir}); process.exit(1); } const files fs.readdirSync(absoluteDir); const now Date.now(); const maxAgeMs (options.days || 30) * 24 * 60 * 60 * 1000; const toDelete []; for (const file of files) { const filePath path.join(absoluteDir, file); try { const stat fs.statSync(filePath); // 只处理文件跳过目录和隐藏文件 if (!stat.isFile() || file.startsWith(.)) continue; const ext path.extname(file).toLowerCase(); const isTemp TEMP_EXTENSIONS.has(ext); const isOld (now - stat.mtimeMs) maxAgeMs; if (isTemp || isOld) { toDelete.push(filePath); } } catch (error) { console.warn(跳过文件 ${file}${error.message}); } } if (toDelete.length 0) { console.log(没有需要清理的文件。); return; } console.log(发现 ${toDelete.length} 个可清理文件); toDelete.forEach((fp) console.log( - ${fp})); if (options.dryRun) { console.log(预览模式未执行删除。); return; } const trashDir options.trashDir || path.join(os.homedir(), .superpowers_trash); fs.mkdirSync(trashDir, { recursive: true }); for (const filePath of toDelete) { const dest path.join(trashDir, path.basename(filePath)); fs.renameSync(filePath, dest); console.log(已移至回收目录${dest}); } } module.exports { cleanDirectory };这里有三个细节值得划重点。第一fs.statSync的mtimeMs是文件最后修改时间算“是否超过 30 天”的时间差要用毫秒做单位第二遍历目录时一定要try...catch包住单个文件的操作否则一个权限报错就会导致整个清理中断第三移动文件到统一回收目录用了renameSync它在同一文件系统内是原子操作速度快且不会出现半截文件。等确认回收目录里的文件没问题再手动整体删除。4.5 模板文件与替换逻辑最后补上 init 模板机制的实现。在 templates/default 目录里放一个标准项目结构然后在 README.md 中写入占位符# {{PROJECT_NAME}} 作者{{AUTHOR}} 这是一个由 superpowers 自动生成的默认项目模板。initFromTemplate 的功能是递归复制模板里所有文件复制的同时替换掉所有占位符// initTemplate.js const fs require(fs); const path require(path); function copyAndReplace(src, dest, variables) { const stat fs.statSync(src); if (stat.isDirectory()) { fs.mkdirSync(dest, { recursive: true }); for (const entry of fs.readdirSync(src)) { copyAndReplace(path.join(src, entry), path.join(dest, entry), variables); } } else { let content fs.readFileSync(src, utf-8); content content.replace(/\{\{PROJECT_NAME\}\}/g, variables.projectName); content content.replace(/\{\{AUTHOR\}\}/g, variables.author || ); fs.writeFileSync(dest, content); } } function initFromTemplate(name, options) { const templateDir path.join(__dirname, .., templates, options.template || default); if (!fs.existsSync(templateDir)) { console.error(模板不存在${options.template}); process.exit(1); } const targetDir path.resolve(name); if (fs.existsSync(targetDir) fs.readdirSync(targetDir).length 0) { console.error(目标目录已存在且不为空${targetDir}); process.exit(1); } copyAndReplace(templateDir, targetDir, { projectName: name, author: options.author, }); console.log(项目已初始化${targetDir}); } module.exports { initFromTemplate };这里有一个值得强调的设计目标目录已存在且非空时直接拒绝执行。这个校验能避免很多灾难比如你手滑把命令的目标指向了已有重要文件的目录。所有的初始化代码都必须假设用户会犯错然后在入口处把错的路径堵死。5. 常见问题与排查技巧实录我踩过的坑和补救方法5.1 command not found 但明明装好了第一次在另一台机器上安装时我执行sp报 command not found但 npm link 明明没有报错。排查之后发现这台机器的 npm 全局 bin 目录没有加到 PATH 环境变量里。解决办法是把 npm 全局路径加到~/.bashrc或~/.zshrc里export PATH$(npm prefix -g)/bin:$PATH顺便说一句npm prefix -g这个命令可以随时查看当前全局安装目录非常有用的排查命令。5.2 npm link 报 EACCES 权限错误在部分环境下全局安装需要权限报错信息是EACCES: permission denied。最推荐的解决办法不是加 sudo而是修改 npm 的全局目录到用户可写的路径下npm config set prefix ~/.npm-global export PATH~/.npm-global/bin:$PATH然后把export那行写进 shell 配置文件。这个方案比 sudo 安全得多因为你的整个工具链都部署在用户目录下不依赖系统全局路径。5.3 批量删除 / 批量改名之后后悔了这是所有自动化工具有可能遇到的最心痛的问题。我的经验是三层保险第一个保险sp clean默认只移动到回收目录而不是直接删除第二个保险sp rename默认预览确认后才执行执行前生成 history 记录第三个保险备份目录里存着前一天的重要配置。我建议你也在自己的工具里保留这层设计给自己添麻烦一点总比某天眼睁睁看着重要文件被自动化逻辑干掉好。如果已经误删了而且是直接删除的那种赶紧停止对该磁盘的一切写入操作然后用数据恢复工具去扫。但最好的策略是从一开始就把“后悔药”内置到工具里。5.4 在 Windows 上脚本崩溃的中文路径问题项目里有一部分操作要打印中文提示在 Windows 终端下会出现乱码或直接报错。解决方法是在 bin/sp.js 最上面强制设定环境变量process.env.FORCE_COLOR 1; process.env.NO_COLOR undefined;然后所有输出都用 UTF-8 编码。Node.js 本身处理 Unicode 没问题问题通常出在 Windows 终端默认代码页上。在 cmd 里执行一次chcp 65001或者把终端默认切换为 Windows Terminal会省很多心。5.5 配置文件被误改导致所有命令打不开配置解析如果出错我的处理方式是让程序崩溃前先打印清楚错误原因而不是静默使用默认值。config.js 里加了 try...catch捕获到解析错误时提示用户检查.superpowersrc哪一行写错了同时把错误行号也输出。排查了半天之后我越来越觉得命令行工具的自检信息写得越友好用户越愿意继续用下去。6. 进阶扩展与日常维护让 superpowers 持续为工作流服务6.1 用系统计划任务挂上自动清理superpowers 最有价值的地方在于它可以跟系统计划任务联动。在 Linux/macOS 下我用 cron 每天凌晨自动执行一次清理任务0 3 * * * /usr/local/bin/sp clean /Users/me/Downloads --days 14在 Windows 下用任务计划程序创建一个每天触发的基本任务操作里填sp参数填clean C:\Users\me\Downloads --days 14一样的效果。这里稍微说一下 cron 的格式从左到右分别是分、时、日、月、星期0 3 * * *就代表每天凌晨 3 点整。把自动化任务交给系统之后你就不用再惦记“今天该不该清理一下下载文件夹”了这个决策被彻底移出了大脑。真正的高效是把每一个本该重复的决策变成一条规则。我把这个原则称为“规则替代决策”superpowers 里的每个命令本质都是这条原则的落实。6.2 把 superpowers 分享给团队写了一段时间之后我把它整理成一套内部工具发到团队仓库里。为了让团队其他同学也能快速上手我更新了 README在最开头放了一段演示和一张速查表。做这件事最大的收获是代码写得再好没有文档别人也不敢搞。所以我给每个命令都写了用法说明和真实场景示例。团队使用中还暴露了一个问题——有人想调整模板结构但不想自己改 JS 代码。为此我把模板目录放到了配置文件的相对路径下修改了projectRoot指向团队约定目录这样大家可以直接改模板文件而不需要碰任何代码。6.3 自己如何维护这套“超能力”这套工具本质上是我们自己给自己写的“手持操作手册”所以功能迭代一定要跟着真实需求走。我的习惯是在笔记里开一个“superpowers 需求备忘”小节每当发现自己在终端重复敲第三遍相同命令时就把它记下来攒够三五个需求就集中写一轮代码然后发布新版本。代码维护上最关键的规则是每个命令都保持单文件独立修改某个命令时不影响其他命令。另外我给所有对文件系统有改动的命令都加了一个--dry-run选项这让测试变得极其轻松不需要备份一份脏数据也能大胆验证逻辑。6.4 再分享一个终端里的日常小技巧最后再免费赠一个跟 superpowers 无关但很实用的终端技巧给常用目录加别名。我在 shell 配置里加了这样几行alias codecd ~/Work sp doctor code alias notescd ~/Notes ls -t | head -20第一种是把多条常做的事组合成一个入口第二种是快速查看最近写的笔记。这两个别名的思路和 superpowers 完全一致把高频动作的路径缩短把决策成本降为零。尝尝这种“所有常用操作都近在手边”的感觉你会彻底回不到以前那个到处点鼠标的自己。我在实际使用中最大的体会是superpowers 这个项目真正提升效率的部分并不是某个单独的命令有多惊艳而是它强制我给自己的日常操作建立了一套规则。每一次把重复劳动变成命令都是在给自己降低未来的时间开销。我强烈建议你动手复刻一个属于自己的版本功能不需要完全一致但一定要从自己最烦的那个重复操作开始。等你亲手把第一个命令跑通你会发现所谓的“超能力”其实就是把想清楚的动作变成肌肉记忆。
阅读完成 · 觉得有帮助?
咨询建站