Compare commits

..
21 Commits
Author SHA1 Message Date
Luk cb06f094ce 让 worklog 文件名带 wl 前缀 2026-10-27 13:29:43 +08:00
Luk 781ea07e05 更新 worklog 示例 2026-10-25 13:49:31 +08:00
Luk 1687ac4a41 升级 worklog 规范,从单文件 worklog.md 变成多 json 文件 worklog/utc时间戳.json 2026-10-25 13:32:59 +08:00
Luk 14f6d79a1c 改进 AGENTS.md 2026-10-23 11:25:30 +08:00
luk 4e1909e090 说明 aimemo/ 可以分布在不同层级的目录里 2026-10-22 15:57:27 +08:00
Luk 7538d8c186 告知 agent 通过 @simsim.gitomit.sfomit/ 来交接机密信息。 2026-10-21 12:53:05 +08:00
luk b9810d662d add resourceMap.md 2026-10-14 21:08:09 +08:00
luk 450592c7c6 查看某个域名的mihomo分流路径 2026-10-14 13:36:57 +08:00
luk 7be962b105 在有的机器(例如 serv00.com) 上不认识特殊字符,因此替换掉 2026-10-14 09:50:15 +08:00
luk 2abbc95a8b detest my ip's location 2026-10-14 09:44:42 +08:00
Luk 1857f86ff2 DoH *.mobileconfig 2026-10-13 12:58:58 +08:00
luk 320536896b update AGENTS.md 2026-10-09 07:58:41 +08:00
Luk fb4b6a4322 improve AGENTS.md 2026-10-07 21:37:12 +08:00
lukandAtomCode bce18ceaa4 syncAgentRule 增加 aimemo 工作记忆目录初始化
在调用时的工作目录下创建 aimemo/,并按 AGENTS.md 约定
创建缺失的 productLens.md / projectPlan.md / worklog.md /
userProfile.md(空白文件);已存在的文件一律沿用,不覆盖。

Co-Authored-By: AtomCode (qwen3.8-27b) <[email protected]>
2026-10-07 11:10:27 +08:00
luk abe3d227e2 add 'build/' to seafile-ignore.global.txt 2026-10-07 10:44:49 +08:00
lukandAtomCode a9372a9742 新增 git_ignore_find2merge.js:Node 跨平台移植,与 seafile 版同套修复
- 忠实移植 .sh 语义:以 .gitignore 存在为候选判定(非 .git,部分仓库隐藏 .git
  保隐私)、深度 3 树搜索、跳过 node_modules 等目录、global + .gitignore.local.txt
  合并生成 .gitignore
- 同套修复:留空 globalPath 回退默认 URL;ask() 处理 stdin EOF 不挂起;
  默认 URL 用 fetch(30s 超时 + 重试一次),.sh 对 URL 走的是 curl 分支;
  根目录自身参与合并(.sh 的 find -name '[^.]*' 匹配不到根)
- 非 y 为 dry-run 并列出候选(.sh 直接静默退出)
- 验证:/tmp 夹具树非根目录产物与 .sh 逐字节一致(根目录差异为已知修正项);
  productX 全树 dry-run 候选 115 条与 .sh 等价 find 管道完全一致

Co-Authored-By: AtomCode (qwen3.8-27b) <[email protected]>
2026-10-07 10:40:17 +08:00
lukandAtomCode 01fdf16213 seafile_ignore_find2merge.js 修复留空 globalPath 不回退默认 URL,加固 fetch
- 交互留空 globalPath 时回退默认 URL(与 .sh 一致);初版漏掉了这步回退,
  导致留空时把空串传给路径检查,报 "Not found [[]]"
- ask() 处理 stdin EOF(非 TTY 无输入时按留空处理,不再卡住)
- fetch 默认 URL 加长超时至 30s 并失败重试一次(git.tic.cc 的 TCP 连接
  有时超过 node 默认 10s 连接超时,实测时通时断)

Co-Authored-By: AtomCode (qwen3.8-27b) <[email protected]>
2026-10-07 10:29:19 +08:00
luk 73c5eb6a8e 添加一些信息在 global ignore 文件里,帮助 AI agents 理解 2026-10-07 10:17:31 +08:00
lukandAtomCode b49f8c9f11 seafile_ignore_find2merge 移植 Node 跨平台,global 增加 Flutter 产物忽略规则
- 新增 seafile_ignore_find2merge.js:忠实移植 .sh 语义(深度3树搜索、跳过
  node_modules 等目录、seafile-ignore.txt/.local.txt 触发合并、dry-run 模式),
  修正 .sh 三个问题:默认 global 是 URL 时 cat 失败(改用 fetch)、去掉误抄的
  .git 检测、根目录自身参与合并(find -name '[^.]*' 匹配不到根)
- .sh 同步修正后两项(.git 检测、根目录),保持与 .js 行为一致
- nixhome/seafile-ignore.global.txt 增加 Flutter/Dart 通用机器本地产物:
  .dart_tool/、.flutter-plugins-dependencies、Pods/、local.properties、
  .gradle/、.kotlin/(build/ 因 vue 仓库跟踪 build/ 而仅在项目层 local 忽略)
- 验证:/tmp 夹具树 .sh 与 .js 产物逐字节一致;productX 全树 dry-run 候选 87 条一致

Co-Authored-By: AtomCode (qwen3.8-27b) <[email protected]>
2026-10-07 09:58:09 +08:00
Luk c6e9a47b1c 增加 speed.sh 2026-09-25 12:48:24 +08:00
Luk 248217f3b7 u 2026-09-25 12:24:08 +08:00
20 changed files with 821 additions and 36 deletions
+51 -13
View File
@@ -1,29 +1,62 @@
# Agents 行为规范 # Agents 行为规范
你是这个项目的开发者,你配合老板(即和你对话的人),进行设计、开发、测试、运营等等工作。 你是这个项目的开发者,你与老板(即和你对话的人)合作,进行设计、开发、测试、运营等等工作。
你对本项目里的所有目录、文件都拥有读写权限。 你对本项目里的所有普通目录、文件都默认拥有读写权限,除非是机密信息,或者老板对你明确设定了权限。
你不仅执行任务,你还应当深入思考独立判断,老板也有许多知识欠缺和判断失误,在你认为有歧义、有错误、有更好选项的时候,你要主动提出问题、建议、选项,与老板进行核对、澄清、深化、确认方案后,再真正开始执行。 对各种机密信息(例如 apikey, password),严禁读取、上传。你应当只使用环境变量名、存放机密的文件名来间接使用机密。
你和老板是平等的,你不仅执行任务,还应当深入思考独立判断,老板也有许多知识欠缺和判断失误,在你认为有歧义、有错误、有更好选项的时候,你要主动提出问题、建议、选项,与老板进行核对、澄清、深化、确认方案后,再真正开始执行。
## aimemo 工作记忆空间 ## aimemo 工作记忆空间
@aimemo/ 目录是你自己的工作与记忆空间,由你**全权自主掌控**。你可以任意处理这里的一切子目录和文件,只要有助于更好的管理和辅助你的工作。记录一切有用的信息,使得工作能够跨 sessions、跨设备、跨 ai models、跨 ai agents 的连续进行下去。持续更新,随时保持和项目里其他内容的同步。 在本项目根目录、以及每个子目录下,都可以有 aimemo/ 目录。这是供你全权自主使用的工作与记忆空间,你可以任意处理这里的一切子目录和文件。你应当在 aimemo/ 里记录一切有用的信息,使得工作能够跨 sessions、跨设备、跨 ai models、跨 ai agents 的连续进行。aimemo/ 应当持续更新,随时保持和项目里其他内容的同步。
每个 aimemo/ 目录,主要用于管理它自己所属目录下的工作,例如 项目根目录下 @aimemo/ 管理本项目所有工作,@app/aimemo/ 管理在 app/ 目录下的工作。各级 aimemo/ 之间可以互相引用,构建灵活的项目管理地图。
### 基础框架 ### 基础框架
建议以四个文件作为基础框架,从四个基本视角覆盖项目: 以下面的文件作为 aimemo/ 的基础框架,从不同视角覆盖项目:
| 参考文件名 | 内容定位 | 用法建议 | - productLens.md
| -------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - 内容定位: (产品视角) 未来目标、此刻现状、愿景、定位、架构、约束、模块、功能、原则、等等。
| productLens.md | 产品视角:未来目标、此刻现状、愿景、定位、架构、约束、模块、功能、原则、等等。 | 透视产品的方方面面,记录已经实现的事实,也记录尚待开发的目标。对每一项产品特性,应当区分`[待评审]`、`[待开发]`、`[已实现]`、以及你可按需自定义的其他状态,避免混淆现实和设想。本文档应长期稳定,但每当状态变化或认知深化时,要及时同步更新。 | - 使用建议: 透视产品的方方面面,记录已经实现的事实,也记录尚待开发的目标。对每一项产品特性,应当区分`[待评审]`、`[待开发]`、`[已实现]`、以及你可按需自定义的其他状态,避免混淆现实和设想。本文档应长期稳定,但每当状态变化或认知深化时,要及时同步更新。
| projectPlan.md | 项目视角:开发计划、任务列表、进度跟踪等。 | 沿时间顺序划分阶段,记录每个阶段的目标、验收标准和任务列表;每次执行任务前后,及时原地更新状态标记,包括`[ ]`待办、`[>]`进行中、`[⏸]`被阻塞、`[x]`已完成,你还可按需自定义更多状态。 |
| worklog.md | 事件视角:工作日志,按时间线记录工作过程与重要变化。 | 记录工作过程、决策、状态、结果等,方便复盘和回溯。持续追加内容,别把它覆写成仅包含当前状态。 | - projectPlan.md
| userProfile.md | 人的视角:老板的个人背景、习惯、偏好与要求的。 | 随时补充你日常观察到的、或老板主动提供的个性化信息,帮助你和老板之间更流畅、更高效的协作。 | - 内容定位: (项目视角) 开发计划、任务列表、进度跟踪等。
- 使用建议: 沿时间顺序划分阶段,记录每个阶段的目标、验收标准和任务列表;每次执行任务前后,及时原地更新状态标记,包括`[ ]`待办、`[>]`进行中、`[⏸]`被阻塞、`[x]`已完成,你还可按需自定义更多状态。
- resourceMap.md
- 内容定位:(物资的视角) 开发、运行的设备、网络、软件等资源。
- 用法建议: 整个项目中牵涉到的软硬件、网络、第三方服务等资源。
- userProfile.md
- 内容定位:(人的视角) 老板的个人背景、习惯、偏好与要求。
- 用法建议:随时补充你日常观察到的、或老板主动提供的个性化信息,帮助你和老板之间更流畅、更高效的协作。
- worklog/(目录)
- 内容定位: (事件视角) 工作日志,按时间线记录工作过程、决策、状态、结果等,方便复盘和回溯。
- 用法建议:持续追加内容,别把它覆写成仅包含当前状态。所有条目存放在扁平目录 `aimemo/worklog/` 下,分两类文件:
- **当月条目**:
- 每条一个文件,文件名为 'wl' + 落盘时刻的 UTC 时间戳,格式 `wl<yyyymmddTHHMMSS.ffffffZ>.json`(精确到**微秒**,微秒位保证多 agent 并行落盘几乎不撞名;万一撞名,在末尾加 `-2` 后缀)。不使用单机递增序号(多 agent 并行会冲突),不使用本地时间做序号(跨时区、写法不统一)。示例 `aimemo/worklog/wl20261013T043501.514823Z.json`
- 每个文件内容是**一个 JSON 对象,多行美化格式**,文件本身即一条完整记录,行布局自由,人和 agent 都可逐行扫读。属性包括:元数据 `utc` 与文件名相同、`time`(本地时间,带时区偏移 `YYYY-MM-DD HH:MM±HH:MM`,便于人读和消歧)、`host`、`os`、`user`、`agent`;`notes` 对象数组放正文内容,每个元素 `{ "brief": "简短标题/要点", "detail": "具体事实、教训、结论等" }`,两者都可选,一条详情本身也可拆分为一个字符串数组。你可以自主决定添加更多有用的属性。
- **历史归档**:进入新月后,把上月的全部条目文件按文件名升序(字典序=时间序)逐条压缩成单行,拼接为 `wl<yyyymm>.jsonl`(如 `202610.jsonl`)——归档面向行式工具批量检索,一行=一条记录,grep 命中行即该条;然后删除那些条目散文件。归档文件只读,原则上不改写。示例 `aimemo/worklog/wl202609.jsonl`
- 检索:一次 grep/glob 递归覆盖 `aimemo/worklog/` 整个目录,即同时覆盖当月条目与历史归档;文件名模式区分两类——归档 `^wl\d{6}\.jsonl$`,当月条目 `^wl\d{8}T\d{6}\.\d{6}Z\.json$`。
- 存量旧格式 `aimemo/worklog.md`(单文件、不规范的老条目)保留原样,不迁移、不回改。
```
{ "utc": "20261013T123503.000000Z", "time": "2026-10-13 12:35+08:00", "host": "mac15", "os": "MacOS", user": "lukas", "agent": "codex",
"notes": [
{ "brief": "聊天输入框白底", "detail": "chat_page.dart 输入框加 filled:true 白底" },
{ "brief": "修改 worklog 规范", "detail": ["事项1", "事项2", "事项3"] },
{ "brief": "只记要点的事项" },
{ "detail": "只记详情的事项" }
] }
```
以上的文件名,还可以含有语言标签,对应不同语言的文档,例如 productLens.enus.md, productLens.zhcn.md 以上的文件名,还可以含有语言标签,对应不同语言的文档,例如 productLens.enus.md, productLens.zhcn.md
对新项目,如果 `@aimemo/` 是空的,建议先按上表初始化。 对新项目,如果 `@aimemo/` 是空的,先按上表初始化。
### 自主扩展 ### 自主扩展
@@ -34,10 +67,15 @@
- **价值优先**:只记录对后续工作有帮助的信息,避免无意义堆砌。 - **价值优先**:只记录对后续工作有帮助的信息,避免无意义堆砌。
- **主动更新**:每次完成一个任务或项目有任何变化时,及时更新相关记录。 - **主动更新**:每次完成一个任务或项目有任何变化时,及时更新相关记录。
- **保持可读**:老板也会参与 @aimemo/ 的查阅、编写、更新,请确保内容清晰、信息同步。 - **保持可读**:老板也会参与 @aimemo/ 的查阅、编写、更新,请确保内容清晰、信息同步。
- **日期作为文件名前缀**:额外添加的文件,以创建日期 yyyymmdd\_ 作为文件名前缀,例如 20260901_newFeature.md,方便和基础框架文件区分开来。
## 工作规范 ## 工作规范
每次修改并验证成功之后,自动做一次 git commit。但不要自动推送,直到老板明确要求推送才推送。 每次完成了一个完整的特性、修复了一个bug、做了一定量修改并验证成功之后,更新 worklog 并做一次 git commit,注意worklog的更新应当与其记录的其他更新一起提交。但如果是很微小的改动,特别是界面的改动,那就不必commit,先让老板验收。不要推送,除非老板明确要求。
你在一个协同工作的环境中。老板可能自己直接改一些简单代码或文案;可能在办公室、家里、旅途中使用不同的电脑启动 agent;可能使用多个 agents 进行交替的、或同时的工作。你要注意,记录及时、准确的文档,有利于互相协作;不要互相干扰,不要提交别人的工作,除非老板明确要求。
有需要机密信息(例如 apikey, password 等)时,你可向老板提出需求,老板通常会放在 @\_simsim.gitomit.sfomit/ 目录下供你使用。
## 命名规范 ## 命名规范
+21 -2
View File
@@ -1,13 +1,18 @@
// 功能:从 https://git.tic.cc/opx/sysconfig/ 同步 agents/AGENTS.md 和 agents/CLAUDE.md // 功能:从 https://git.tic.cc/opx/sysconfig/ 同步 agents/AGENTS.md 和 agents/CLAUDE.md
// 到本项目根目录(覆盖现有文件)。 // 到本项目根目录(覆盖现有文件);并在当前目录初始化 aimemo/ 工作记忆目录,
// 按 AGENTS.md 约定创建缺失的 productLens.md / projectPlan.md / worklog.md / userProfile.md(空白文件),已存在的保持不动。
// 使用 Node 内置 fetch,跨 Windows / Linux / macOS 平台,无需额外工具。 // 使用 Node 内置 fetch,跨 Windows / Linux / macOS 平台,无需额外工具。
// 调用:在 package.json 的 scripts 里添加 "syncAgentRule": "node -e \"fetch('https://git.tic.cc/opx/sysconfig/raw/branch/main/agents/syncAgentRule.mjs').then(r=>{if(!r.ok)throw new Error('HTTP '+r.status);return r.text()}).then(async t=>{await import('data:text/javascript;base64,'+Buffer.from(t).toString('base64'))})\"" // 调用:在 package.json 的 scripts 里添加 "syncAgentRule": "node -e \"fetch('https://git.tic.cc/opx/sysconfig/raw/branch/main/agents/syncAgentRule.mjs').then(r=>{if(!r.ok)throw new Error('HTTP '+r.status);return r.text()}).then(async t=>{await import('data:text/javascript;base64,'+Buffer.from(t).toString('base64'))})\""
import { writeFile } from 'node:fs/promises'; import { writeFile, mkdir, access } from 'node:fs/promises';
const BASE_URL = 'https://git.tic.cc/opx/sysconfig/raw/branch/main/agents/'; const BASE_URL = 'https://git.tic.cc/opx/sysconfig/raw/branch/main/agents/';
const FILES = ['AGENTS.md', 'CLAUDE.md']; const FILES = ['AGENTS.md', 'CLAUDE.md'];
// aimemo 工作记忆目录:按 AGENTS.md 约定初始化,仅在调用时的工作目录(process.cwd())下创建缺失文件,已存在的一律沿用
const AIMEMO_DIR = 'aimemo';
const AIMEMO_FILES = ['productLens.md', 'projectPlan.md', 'worklog.md', 'userProfile.md'];
for (const name of FILES) { for (const name of FILES) {
const response = await fetch(BASE_URL + name); const response = await fetch(BASE_URL + name);
if (!response.ok) { if (!response.ok) {
@@ -18,3 +23,17 @@ for (const name of FILES) {
} }
console.log('agents 文件同步完成'); console.log('agents 文件同步完成');
// 初始化 aimemo/(相对路径解析到调用时的工作目录,即 process.cwd())
await mkdir(AIMEMO_DIR, { recursive: true });
for (const name of AIMEMO_FILES) {
const filePath = `${AIMEMO_DIR}/${name}`;
try {
await access(filePath);
console.log(`已存在,沿用 ${filePath}`);
} catch {
await writeFile(filePath, '');
console.log(`已创建 ${filePath}`);
}
}
console.log('aimemo 目录初始化完成');
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>DNSSettings</key>
<dict>
<key>DNSProtocol</key>
<string>HTTPS</string>
<key>ServerURL</key>
<string>https://dns.alidns.com/dns-query</string>
</dict>
<key>PayloadDisplayName</key>
<string>DoH AliDNS</string>
<key>PayloadIdentifier</key>
<string>cc.tic.einz.doh.alidns</string>
<key>PayloadType</key>
<string>com.apple.dnsSettings.managed</string>
<key>PayloadUUID</key>
<string>A1B2C3D4-E5F6-4A7B-8C9D-0E1F2A3B4C5D</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>DoH AliDNS</string>
<key>PayloadIdentifier</key>
<string>cc.tic.einz.doh</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>B2C3D4E5-F6A7-4B8C-9D0E-1F2A3B4C5D6E</string>
<!-- macOS 26.1+ 必需:声明系统级范围,否则报"无法安装VPN服务负载"(系统bug) -->
<key>PayloadScope</key>
<string>System</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>DNSSettings</key>
<dict>
<key>DNSProtocol</key>
<string>HTTPS</string>
<key>ServerURL</key>
<string>https://cloudflare-dns.com/dns-query</string>
</dict>
<key>PayloadDisplayName</key>
<string>DoH Cloudflare (1.1.1.1)</string>
<key>PayloadIdentifier</key>
<string>cc.tic.einz.doh.cloudflare</string>
<key>PayloadType</key>
<string>com.apple.dnsSettings.managed</string>
<key>PayloadUUID</key>
<string>E5F6A7B8-C9D0-4E1F-2A3B-4C5D6E7F8091</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>DoH Cloudflare (1.1.1.1)</string>
<key>PayloadIdentifier</key>
<string>cc.tic.einz.doh.cloudflare</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>F6A7B8C9-D0E1-4F2A-3B4C-5D6E7F8091A2</string>
<!-- macOS 26.1+ 必需:声明系统级范围,否则报"无法安装VPN服务负载"(系统bug) -->
<key>PayloadScope</key>
<string>System</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
+43
View File
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>DNSSettings</key>
<dict>
<key>DNSProtocol</key>
<string>HTTPS</string>
<key>ServerURL</key>
<string>https://doh.pub/dns-query</string>
</dict>
<key>PayloadDisplayName</key>
<string>DoH DNSPod (doh.pub)</string>
<key>PayloadIdentifier</key>
<string>cc.tic.einz.doh.dnspod</string>
<key>PayloadType</key>
<string>com.apple.dnsSettings.managed</string>
<key>PayloadUUID</key>
<string>C3D4E5F6-A7B8-4C9D-0E1F-2A3B4C5D6E7F</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDisplayName</key>
<string>DoH DNSPod (doh.pub)</string>
<key>PayloadIdentifier</key>
<string>cc.tic.einz.doh.dnspod</string>
<key>PayloadRemovalDisallowed</key>
<false/>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>D4E5F6A7-B8C9-4D0E-1F2A-3B4C5D6E7F80</string>
<!-- macOS 26.1+ 必需:声明系统级范围,否则报"无法安装VPN服务负载"(系统bug) -->
<key>PayloadScope</key>
<string>System</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>
+2 -2
View File
@@ -2,11 +2,11 @@ echo "***************************************"
echo "find \$FOLDER -mindepth \$MINDEPTH -maxdepth \$MAXDEPTH -type d_f_l -name \"\$PATTERN\" | while read item; do echo \"\$item\"; done" echo "find \$FOLDER -mindepth \$MINDEPTH -maxdepth \$MAXDEPTH -type d_f_l -name \"\$PATTERN\" | while read item; do echo \"\$item\"; done"
echo "***************************************" echo "***************************************"
echo "To find in which path? Enter [root path] or [leave blank] for the current path [[`pwd`]]:" echo "To find in which path? Enter [root path] or [leave blank] for the current path [[$(realpath `pwd`)]]:"
read -p "path ***:: " FIND_FOLDER read -p "path ***:: " FIND_FOLDER
if [ ! "$FIND_FOLDER" ] if [ ! "$FIND_FOLDER" ]
then then
FIND_FOLDER=`pwd` FIND_FOLDER=$(realpath "`pwd`")
fi fi
read -p "mindepth ***:: " MINDEPTH read -p "mindepth ***:: " MINDEPTH
+190
View File
@@ -0,0 +1,190 @@
#!/usr/bin/env node
// Cross-platform (node) port of git_ignore_find2merge.sh / .bat
//
// Search [ROOTPATH], merge [GLOBALPATH/.gitignore_global] and [ROOTPATH/<dir>/.gitignore.local.txt]
// into [ROOTPATH/<dir>/.gitignore], for every <dir> that has a .gitignore.
// (Judged from .gitignore, not from .git — some git repos keep privacy and hide .git.)
//
// usage:
// node git_ignore_find2merge.js [rootPath] [globalPath] [y|other]
// rootPath start of tree search, must be a directory (default: prompt, blank = current dir)
// globalPath path to .gitignore_global, or its parent dir, or an http(s) URL
// (default: https://git.tic.cc/opx/sysconfig/raw/branch/main/nixhome/.gitignore_global)
// yesNo 'y' to write, anything else = dry-run (list candidates without writing)
//
// node git_ignore_find2merge.js # interactive, dry-run
// node git_ignore_find2merge.js ~/Shared y
// node git_ignore_find2merge.js /srv/projects /srv/proj/nixhome y
//
// Same fixes as seafile_ignore_find2merge.js:
// - blank globalPath falls back to the default URL, as in the .sh
// - ask() treats stdin EOF as blank, so non-interactive pipes don't hang
// - the default global is a URL: fetched with fetch() at 30s timeout + one retry
// (the .sh's `cat $GLOBALPATH` branch never ran for URLs — it went to the curl branch)
// - the root dir itself is processed too (the .sh's find -name '[^.]*' never matched the root)
// - non-'y' is a dry-run that still lists the candidate dirs (the .sh just exited silently)
const fs = require('fs')
const os = require('os')
const path = require('path')
const readline = require('readline')
const DEFAULT_GLOBAL_URL = 'https://git.tic.cc/opx/sysconfig/raw/branch/main/nixhome/.gitignore_global'
const GLOBAL_FILE_NAME = '.gitignore_global'
// same skip list as: grep -E -v 'node_modules|uni_modules|\.deploy_git|\.git|.svn|\.vscode|\.wrangler|unpackage|_webroot|_logstore|_datasotre|_archive|_filestore|_ssl'
const SKIP_DIR_PATTERN = /node_modules|uni_modules|\.deploy_git|\.git|\.svn|\.vscode|\.wrangler|unpackage|_webroot|_logstore|_datasotre|_archive|_filestore|_ssl/
const MAX_DEPTH = 3 // relative to ROOTPATH, matching find -mindepth 0 -maxdepth 3
function resolveHome (p) {
if (p === '~') return os.homedir()
if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2))
return p
}
async function ask (question) {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
const answer = await new Promise(resolve => {
let settled = false
const finish = value => { if (!settled) { settled = true; resolve(value) } }
rl.question(question, a => finish(a))
rl.on('close', () => finish('')) // stdin ended without input (EOF): treat as blank, like the .sh's `read`
})
rl.close()
return answer.trim()
}
// find . -mindepth 0 -maxdepth 3 -type d -name '[^.]*' (relative paths, root itself excluded)
function collectCandidateDirs (rootPath) {
const candidates = []
function walk (dir, depth) {
if (depth > MAX_DEPTH) return
let entries
try {
entries = fs.readdirSync(dir, { withFileTypes: true })
} catch {
return
}
for (const entry of entries) {
if (!entry.isDirectory()) continue
if (entry.name.startsWith('.')) continue // -name '[^.]*' excludes hidden dirs
const absDir = path.join(dir, entry.name)
const relDir = path.relative(rootPath, absDir)
if (relDir.split(path.sep).length > MAX_DEPTH) continue
candidates.push(relDir)
walk(absDir, depth + 1)
}
}
walk(rootPath, 1)
return candidates
}
async function loadGlobalContent (globalPathInput) {
const globalPath = resolveHome(globalPathInput)
if (/^https?:\/\//i.test(globalPath)) {
console.log('√√√ GLOBALPATH (url) = [[' + globalPath + ']]')
// git.tic.cc's TCP connect can exceed node's default 10s connect timeout; allow 30s and retry once.
let lastError
for (let attempt = 1; attempt <= 2; attempt++) {
try {
const response = await fetch(globalPath, { signal: AbortSignal.timeout(30000) })
if (!response.ok) throw new Error('HTTP ' + response.status)
return await response.text()
} catch (error) {
lastError = error
console.log('... fetch attempt ' + attempt + ' failed: ' + (error.cause?.code || error.message))
}
}
console.log('××× Fetch failed: ' + (lastError.cause?.code || lastError.message) + ' for [[' + globalPath + ']]. Exit now...')
process.exit(1)
}
let globalFile = globalPath
if (fs.existsSync(globalPath) && fs.statSync(globalPath).isDirectory()) {
globalFile = path.join(globalPath, GLOBAL_FILE_NAME)
}
if (!fs.existsSync(globalFile)) {
console.log('××× Not found [[' + globalFile + ']]. Exit now...')
process.exit(1)
}
console.log('√√√ GLOBALPATH = [[' + globalFile + ']]')
return fs.readFileSync(globalFile, 'utf8')
}
async function main () {
console.log('')
console.log('Search in [ROOTPATH], merge [GLOBALPATH/.gitignore_global] and [ROOTPATH/*/.gitignore.local.txt] files to .gitignore')
console.log('')
// --- ROOTPATH ---
let rootPath = process.argv[2]
const rootIsDir = p => {
try { return fs.statSync(p).isDirectory() } catch { return false }
}
if (!(rootPath && rootIsDir(resolveHome(rootPath)))) {
console.log('::*** Enter [root path] or [leave blank] for default [[' + process.cwd() + ']] to start tree search for git repositories')
rootPath = await ask('***:: ')
if (!rootPath) rootPath = process.cwd()
rootPath = path.resolve(resolveHome(rootPath))
} else {
rootPath = path.resolve(resolveHome(rootPath))
}
if (!rootIsDir(rootPath)) {
console.log('××× [[' + rootPath + ']] not exist! Exit now. ***')
process.exit(1)
}
console.log('√√√ ROOTPATH = [[' + rootPath + ']]')
console.log('')
// --- GLOBALPATH ---
let globalPathInput = process.argv[3]
if (!globalPathInput) {
console.log('::*** Enter [path to .gitignore_global] or [leave blank] for default [[' + DEFAULT_GLOBAL_URL + ']]')
globalPathInput = await ask('***:: ')
if (!globalPathInput) globalPathInput = DEFAULT_GLOBAL_URL // blank → default URL, as in the .sh
}
const globalContent = await loadGlobalContent(globalPathInput)
console.log('')
// --- y/N ---
let yesNo = process.argv[4]
if (!yesNo) {
console.log('::*** Enter [y] to start updating, or [anything else] for dry-run')
yesNo = await ask('***:: ')
}
const willWrite = yesNo === 'y'
console.log('*** Starting from [[' + rootPath + ']] ***')
console.log('')
// --- search + merge ---
// A dir is a candidate iff it has a .gitignore (judged from .gitignore, not from .git).
const candidateDirs = collectCandidateDirs(rootPath).sort()
// The root dir itself is also a candidate: the old .sh's find -name '[^.]*' never matched the root.
if (fs.existsSync(path.join(rootPath, '.gitignore'))) candidateDirs.unshift('.')
let updatedCount = 0
for (const relDir of candidateDirs) {
const absDir = relDir === '.' ? rootPath : path.join(rootPath, relDir)
if (relDir !== '.' && SKIP_DIR_PATTERN.test(relDir)) continue
const targetFile = path.join(absDir, '.gitignore')
const localFile = path.join(absDir, '.gitignore.local.txt')
if (!fs.existsSync(targetFile)) continue
console.log('---- updating [[' + relDir + '/.gitignore]] ----')
if (willWrite) {
let merged = globalContent
if (fs.existsSync(localFile)) merged += fs.readFileSync(localFile, 'utf8')
fs.writeFileSync(targetFile, merged)
updatedCount++
}
console.log('')
}
if (willWrite) {
console.log('*** done: ' + updatedCount + ' file(s) updated ***')
} else {
console.log('*** dry-run: no file written (enter y to update) ***')
}
}
main().catch(err => {
console.error(err)
process.exit(1)
})
+1 -1
View File
@@ -9,4 +9,4 @@ fi
echo Public IP: `curl -s ifconfig.me` echo Public IP: `curl -s ifconfig.me`
echo echo
curl ipinfo.io net.sh
+7
View File
@@ -0,0 +1,7 @@
echo https://ipinfo.io/country == $(curl -s https://ipinfo.io/country) # 返回国家代码,例如 CN, DE, US
echo https://myip.ipip.net == $(curl -s https://myip.ipip.net) # 国内站应显示无锡/电信(直连)
echo https://ipinfo.io/json ==
curl -s https://ipinfo.io/json # 详情:IP、城市、组织
# curl ipinfo.io
echo
+2 -2
View File
@@ -146,9 +146,9 @@ elif [ -f /etc/ubuntu_version ]; then
MYOSVERSION=Ubt`cat /etc/ubuntu_version 2>/dev/null` MYOSVERSION=Ubt`cat /etc/ubuntu_version 2>/dev/null`
fi fi
if [ "$color_prompt" = yes ]; then if [ "$color_prompt" = yes ]; then
PS1='\n \[\033[$PSTYLE;${PTYPE}2m\]\t\[\033[00m\] \[\033[$PSTYLE;${PTYPE}5m\]\u\[\033[00m\] @\[\033[$PSTYLE;${PTYPE}2m\]\h\[\033[00m\] =\[\033[$PSTYLE;${PTYPE}5m\]$MYIPPUB\[\033[00m\] #\[\033[$PSTYLE;${PTYPE}2m\]$(uname -m),$(uname),$MYOSVERSION\[\033[00m\] \[\033[$PSTYLE;${PTYPE}5m\]$PWD/\[\033[00m\] \n[ \[\033[05;31m\]\W/\[\033[00m\] ]§ ' PS1='\n#[ \[\033[$PSTYLE;${PTYPE}2m\]\t\[\033[00m\] \[\033[$PSTYLE;${PTYPE}5m\]\u\[\033[00m\] @\[\033[$PSTYLE;${PTYPE}2m\]\h\[\033[00m\] =\[\033[$PSTYLE;${PTYPE}5m\]$MYIPPUB\[\033[00m\] #\[\033[$PSTYLE;${PTYPE}2m\]$(uname -m),$(uname),$MYOSVERSION\[\033[00m\] \[\033[$PSTYLE;${PTYPE}5m\]$PWD/\[\033[00m\] ]#\n#[ \[\033[05;31m\]\W/\[\033[00m\] ]# '
else else
PS1='\n \t \u @\h =$MYIPPUB #$(uname -m),$(uname),$MYOSVERSION $PWD/ \n[ \W/ ]§ ' PS1='\n#[ \t \u @\h =$MYIPPUB #$(uname -m),$(uname),$MYOSVERSION $PWD/ ]#\n#[ \W/ ]# '
fi fi
unset color_prompt force_color_prompt unset color_prompt force_color_prompt
+3
View File
@@ -6,6 +6,7 @@
# https://gitignore.io # https://gitignore.io
### .gitignore_global ### ### .gitignore_global ###
### global ignore settings in opx/sysconfig/nixhome/.gitignore_global
# Self defined pattern to ignore # Self defined pattern to ignore
?*.gitignore ?*.gitignore
@@ -83,4 +84,6 @@ _desktop.ini
.env.*.local .env.*.local
### .gitignore.local.txt ### ### .gitignore.local.txt ###
### Followings are project-specific ignore settings merged by opx/sysconfig/git_ignore_find2merge.js
+27 -1
View File
@@ -7,6 +7,7 @@
# seafile-ignore.txt 只能忽略还没有被同步的文件。对于已经被同步的文件,如果后来把它添加到 seafile-ignore.txt 中,系统只会忽略后续更改,已经上传的版本不会受影响。 # seafile-ignore.txt 只能忽略还没有被同步的文件。对于已经被同步的文件,如果后来把它添加到 seafile-ignore.txt 中,系统只会忽略后续更改,已经上传的版本不会受影响。
### seafile-ignore.global.txt ### ### seafile-ignore.global.txt ###
### global ignore settings in opx/sysconfig/nixhome/seafile-ignore.global.txt
# 自定义的后缀名,凡有 sfignore 后缀的都不进行同步 # 自定义的后缀名,凡有 sfignore 后缀的都不进行同步
*.sfignore *.sfignore
@@ -83,5 +84,30 @@ OneDrive/Icon
.playwright-mcp/ .playwright-mcp/
*/.playwright-mcp/ */.playwright-mcp/
### seafile-ignore.local.txt ### # Flutter/Dart 机器本地产物(各端由 `flutter pub get` / `pod install` / 构建在本机重生成,不跨机同步)。
# 双机开发时 .dart_tool/package_config.json 内是绝对路径(file:///Users/<用户>/.pub-cache/...),同步过去会让另一台机器编译报 No such file or directory。
# 注:pub 缓存在 ~/.pub-cache,位于资料库根之外,seafile 同步不到,无需在此列出。
# 注:build/ 未全局忽略,以防波及用了该常见词的目录;按需放各项目 seafile-ignore.local.txt。
# ---- Dart / Flutter:包解析 + 构建产物(核心,换机必跑 `flutter pub get` 重生成)----
.dart_tool/
*/.dart_tool/
.flutter-plugins-dependencies
*/.flutter-plugins-dependencies
build/
*/build/
# ---- iOS / macOS:CocoaPods 检出目录(Podfile.lock 仍提交共享,只忽略 Pods/ 实体)----
Pods/
*/Pods/
# ---- Android:本机 SDK 路径 + 构建缓存(换机必跑 `flutter build` 重生成 local.properties)----
local.properties
*/local.properties
.gradle/
*/.gradle/
.kotlin/
*/.kotlin/
### seafile-ignore.local.txt ###
### Followings are project-specific ignore settings merged by opx/sysconfig/seafile_ignore_find2merge.js
+1 -3
View File
@@ -41,6 +41,4 @@ echo all_proxy=$all_proxy
echo http_proxy=$http_proxy echo http_proxy=$http_proxy
echo https_proxy=$https_proxy echo https_proxy=$https_proxy
# ip.gs, ip.sb, ipinfo.io net.sh
curl ipinfo.io
echo " "
+59
View File
@@ -0,0 +1,59 @@
#!/bin/bash
# route.sh 域名 —— 输出该域名在 mihomo 的分流结果,如: DIRECT / PROXY[de1-vless] / REJECT
# 用法: route.sh www.xiaohongshu.com [更多域名...]
# 自适应两套环境(同一份脚本,sysconfig 同步到所有电脑):
# 1) Mac + Clash Verge service:unix socket + 7897(无 TCP API、无需 secret)
# 2) Linux 服务器(如 cn1):TCP API + secret + 7890
# 可靠性设计:每域名独立一条 8s 日志流(长流/连续流会被内核丢弃),流内 grep 纯域名
#(JSON 里 "-->" 被转义成 \u003e,不能按 "--> " 前缀匹配)
# ==== ENV(按需修改)====
MAC_SOCK="/var/run/clash-verge-service/users/501/verge-mihomo.sock" # Verge service unix socket
CN1_API="http://100.100.86.1:9090" # cn1 mihomo REST API
CN1_SECRET="pl1pool-2026" # cn1 API secret
PROXY_MAC="http://127.0.0.1:7897" # Mac mixed 端口
PROXY_CN1="http://127.0.0.1:7890" # cn1 mixed 端口
# ========================
command -v curl >/dev/null || { echo "curl is required"; exit 1; }
# ---- 探测环境:优先 unix socket(Mac),否则 TCP+secret(cn1/其他)----
if [ -S "$MAC_SOCK" ]; then
MODE="mac"
SOCK_ARGS=(--unix-socket "$MAC_SOCK")
API_URL="http://localhost"
AUTH=()
PROXY="$PROXY_MAC"
else
MODE="tcp"
SOCK_ARGS=()
API_URL="$CN1_API"
AUTH=(-H "Authorization: Bearer $CN1_SECRET")
PROXY="$PROXY_CN1"
PING=$(curl -s -m 5 "${AUTH[@]}" -o /dev/null -w '%{http_code}' "$API_URL/version" 2>/dev/null)
[ "$PING" = "200" ] || { echo "API unreachable: $API_URL (code=$PING) — 检查网络/secret"; exit 1; }
fi
if [ $# -eq 0 ]; then
echo "Usage: $(basename $0) domain [domain2 ...] (mode: $MODE)"
exit 1
fi
# 每域名独立短会话:8s 日志流 + 1s 后请求 + 7s 收尾(此形态实测最稳)
for domain in "$@"; do
LOG=$(mktemp)
curl -s --max-time 8 "${SOCK_ARGS[@]}" "${AUTH[@]}" "$API_URL/logs?level=info" > "$LOG" 2>/dev/null &
LPID=$!
sleep 1
curl -s -m 5 -x "$PROXY" -o /dev/null "https://$domain" 2>/dev/null
sleep 7
LINE=$(grep -m1 "$domain" "$LOG" 2>/dev/null | head -1)
if [ -z "$LINE" ]; then
printf "%-40s ?? (no match line captured)\n" "$domain"
else
RESULT=$(echo "$LINE" | sed -n 's/.* match .* using //p' | sed 's/"}$//' | sed 's/"$//')
RULE=$(echo "$LINE" | sed -n 's/.* match \(.*\) using .*/\1/p')
printf "%-40s %-24s (rule: %s)\n" "$domain" "${RESULT:-??}" "${RULE:-?}"
fi
kill $LPID 2>/dev/null; wait $LPID 2>/dev/null; rm -f "$LOG"
done
+113
View File
@@ -0,0 +1,113 @@
# https://help.seafile.com/syncing_client/excluding_files/
# 注释。通配符:* 匹配0到若干个字符,包括代表目录的/。? 匹配1个字符,包括/。
# seafile-ignore.txt 只能控制在客户端需要忽略哪些文件。你依然可以在 seahub 的 web 界面创建这些被客户端忽略的文件。
# 在这种情况下,
# 这些文件会被同步到客户端,但是用户在客户端对这些文件的后续修改会被忽略,不会被同步回服务器。
# 文件在服务器端的后续更改会被同步到客户端,如果客户端也同时修改了这些文件,系统会生成冲突文件。
# seafile-ignore.txt 只能忽略还没有被同步的文件。对于已经被同步的文件,如果后来把它添加到 seafile-ignore.txt 中,系统只会忽略后续更改,已经上传的版本不会受影响。
### seafile-ignore.global.txt ###
### global ignore settings in opx/sysconfig/nixhome/seafile-ignore.global.txt
# 自定义的后缀名,凡有 sfignore 后缀的都不进行同步
*.sfignore
*.sfignore/
*.sfignore.*
*.sfignore.*/
*.sfomit
*.sfomit.*
*.sfomit/
*.sfomit.*/
*.nosf
*.nosf.*
*.nosf/
*.nosf.*/
## everything 'git pull or fetch' will update `.git/FETCH_HEAD`, even if the content doesn't change. To avoid too many useless updates of this file in Seafile history:
FETCH_HEAD
*/FETCH_HEAD
.Trash/
.Trashes/
.DS_Store
*/.DS_Store
*.aae # AAE 文件主要在苹果的照片应用程序中使用,保存对原始照片所做的编辑,比如,裁剪、旋转或调整亮度等操作的信息。
.thumbnails
*/.thumbnails
Thumbs.db
*/Thumbs.db
thumbs.db
*/thumbs.db
_desktop.ini
*/_desktop.ini
._*
*/._*
.$*
*/.$*
~$*
*/~$*
node_modules/
*/node_modules/
pages4loader.json5
*/pages4loader.json5
.deploy_git/
*/.deploy_git/
# next.js 项目
.next/
*/.next/
# HBuilder 目录
unpackage/
*/unpackage/
Icon
OneDrive/Icon
# wrangler project
.dev.vars*
*/.dev.vars*
.wrangler/
*/.wrangler/
.playwright-mcp/
*/.playwright-mcp/
# Flutter/Dart 机器本地产物(各端由 `flutter pub get` / `pod install` / 构建在本机重生成,不跨机同步)。
# 双机开发时 .dart_tool/package_config.json 内是绝对路径(file:///Users/<用户>/.pub-cache/...),同步过去会让另一台机器编译报 No such file or directory。
# 注:pub 缓存在 ~/.pub-cache,位于资料库根之外,seafile 同步不到,无需在此列出。
# 注:build/ 未全局忽略,以防波及用了该常见词的目录;按需放各项目 seafile-ignore.local.txt。
# ---- Dart / Flutter:包解析 + 构建产物(核心,换机必跑 `flutter pub get` 重生成)----
.dart_tool/
*/.dart_tool/
.flutter-plugins-dependencies
*/.flutter-plugins-dependencies
build/
*/build/
# ---- iOS / macOS:CocoaPods 检出目录(Podfile.lock 仍提交共享,只忽略 Pods/ 实体)----
Pods/
*/Pods/
# ---- Android:本机 SDK 路径 + 构建缓存(换机必跑 `flutter build` 重生成 local.properties)----
local.properties
*/local.properties
.gradle/
*/.gradle/
.kotlin/
*/.kotlin/
### seafile-ignore.local.txt ###
### Followings are project-specific ignore settings merged by opx/sysconfig/seafile_ignore_find2merge.js
+183
View File
@@ -0,0 +1,183 @@
#!/usr/bin/env node
// Cross-platform (node) port of seafile_ignore_find2merge.sh / .bat
//
// Search [ROOTPATH], merge [GLOBALPATH/seafile-ignore.global.txt] and [ROOTPATH/<dir>/seafile-ignore.local.txt]
// into [ROOTPATH/<dir>/seafile-ignore.txt], for every <dir> that has a seafile-ignore.txt OR a seafile-ignore.local.txt.
// (Unlike git_ignore_find2merge, a .git dir is NOT a trigger — seafile ignore rules are independent of git.)
//
// usage:
// node seafile_ignore_find2merge.js [rootPath] [globalPath] [y|other]
// rootPath start of tree search (default: current dir)
// globalPath path to seafile-ignore.global.txt, or its parent dir, or an http(s) URL
// (default: https://git.tic.cc/opx/sysconfig/raw/branch/main/nixhome/seafile-ignore.global.txt)
// yesNo 'y' to write, anything else = dry-run (default: dry-run)
//
// node seafile_ignore_find2merge.js # dry-run from cwd
// node seafile_ignore_find2merge.js ~/Shared y
// node seafile_ignore_find2merge.js /srv/projects /srv/proj/seafile-ignore.global.txt y
//
// Semantics ported from the .sh (two .sh gaps fixed):
// - the root dir itself is processed too (the .sh's find -name '[^.]*' never matched the root dir)
// - a dir is updated iff it has seafile-ignore.txt OR seafile-ignore.local.txt (the .sh's `.git` check was dropped)
// - tree search is max depth 3, skipping hidden dirs and the known build/dep dirs
// - blank globalPath falls back to the default URL, as in the .sh (first draft missed this → "Not found [[]]")
// - the .sh does `cat $GLOBALPATH` which fails when the default is a URL; here http(s) is fetched with fetch(),
// a local file / parent-dir keeps the original behavior.
const fs = require('fs')
const os = require('os')
const path = require('path')
const readline = require('readline')
const DEFAULT_GLOBAL_URL = 'https://git.tic.cc/opx/sysconfig/raw/branch/main/nixhome/seafile-ignore.global.txt'
// same skip list as: grep -E -v 'node_modules|uni_modules|\.deploy_git|\.git|.svn|\.vscode|\.wrangler|unpackage|_webroot|_logstore|_datasotre|_archive|_filestore|_ssl'
const SKIP_DIR_PATTERN = /node_modules|uni_modules|\.deploy_git|\.git|\.svn|\.vscode|\.wrangler|unpackage|_webroot|_logstore|_datasotre|_archive|_filestore|_ssl/
const MAX_DEPTH = 3 // relative to ROOTPATH, matching find -mindepth 0 -maxdepth 3
function resolveHome (p) {
if (p === '~') return os.homedir()
if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2))
return p
}
async function ask (question) {
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
const answer = await new Promise(resolve => {
let settled = false
const finish = value => { if (!settled) { settled = true; resolve(value) } }
rl.question(question, a => finish(a))
rl.on('close', () => finish('')) // stdin ended without input (EOF): treat as blank, like the .sh's `read`
})
rl.close()
return answer.trim()
}
// find . -mindepth 0 -maxdepth 3 -type d -name '[^.]*' (relative paths, root itself excluded)
function collectCandidateDirs (rootPath) {
const candidates = []
function walk (dir, depth) {
if (depth > MAX_DEPTH) return
let entries
try {
entries = fs.readdirSync(dir, { withFileTypes: true })
} catch {
return
}
for (const entry of entries) {
if (!entry.isDirectory()) continue
if (entry.name.startsWith('.')) continue // -name '[^.]*' excludes hidden dirs
const absDir = path.join(dir, entry.name)
const relDir = path.relative(rootPath, absDir)
if (relDir.split(path.sep).length > MAX_DEPTH) continue
candidates.push(relDir)
walk(absDir, depth + 1)
}
}
walk(rootPath, 1)
return candidates
}
async function loadGlobalContent (globalPathInput) {
const globalPath = resolveHome(globalPathInput)
if (/^https?:\/\//i.test(globalPath)) {
console.log('√√√ GLOBALPATH (url) = [[' + globalPath + ']]')
// git.tic.cc's TCP connect can exceed node's default 10s connect timeout; allow 30s and retry once.
let lastError
for (let attempt = 1; attempt <= 2; attempt++) {
try {
const response = await fetch(globalPath, { signal: AbortSignal.timeout(30000) })
if (!response.ok) throw new Error('HTTP ' + response.status)
return await response.text()
} catch (error) {
lastError = error
console.log('... fetch attempt ' + attempt + ' failed: ' + (error.cause?.code || error.message))
}
}
console.log('××× Fetch failed: ' + (lastError.cause?.code || lastError.message) + ' for [[' + globalPath + ']]. Exit now...')
process.exit(1)
}
let globalFile = globalPath
if (fs.existsSync(globalPath) && fs.statSync(globalPath).isDirectory()) {
globalFile = path.join(globalPath, 'seafile-ignore.global.txt')
}
if (!fs.existsSync(globalFile)) {
console.log('××× Not found [[' + globalFile + ']]. Exit now...')
process.exit(1)
}
console.log('√√√ GLOBALPATH = [[' + globalFile + ']]')
return fs.readFileSync(globalFile, 'utf8')
}
async function main () {
console.log('')
console.log('Search in [ROOTPATH], Merge [GLOBALPATH/seafile-ignore.global.txt] and [ROOTPATH/*/seafile-ignore.local.txt] files to [seafile-ignore.txt]')
console.log('')
// --- ROOTPATH ---
let rootPath = process.argv[2]
if (!rootPath) {
console.log('::*** Enter [root path] or [leave blank] for default [[' + process.cwd() + ']] to start tree search for seafile-ignore.txt files')
rootPath = await ask('***:: ')
}
if (rootPath) rootPath = path.resolve(resolveHome(rootPath))
else rootPath = process.cwd()
if (!fs.existsSync(rootPath) || !fs.statSync(rootPath).isDirectory()) {
console.log('××× [[' + rootPath + ']] not exist! Exit now. ***')
process.exit(1)
}
console.log('√√√ ROOTPATH = [[' + rootPath + ']]')
console.log('')
// --- GLOBALPATH ---
let globalPathInput = process.argv[3]
if (!globalPathInput) {
console.log('::*** Enter [path to seafile-ignore.global.txt] or [leave blank] for default [[' + DEFAULT_GLOBAL_URL + ']]')
globalPathInput = await ask('***:: ')
if (!globalPathInput) globalPathInput = DEFAULT_GLOBAL_URL // blank → default URL, as in the .sh
}
const globalContent = await loadGlobalContent(globalPathInput)
console.log('')
// --- y/N ---
let yesNo = process.argv[4]
if (!yesNo) {
console.log('::*** Enter [y] to start updating, or [anything else] for dry-run')
yesNo = await ask('***:: ')
}
const willWrite = yesNo === 'y'
console.log('*** Starting from [[' + rootPath + ']] ***')
console.log('')
// --- search + merge ---
// The root dir itself is also a candidate: the old .sh's find -name '[^.]*' never matched the root,
// so ROOTPATH's own seafile-ignore.txt was never updated — fixed here (and in the .sh).
const candidateDirs = collectCandidateDirs(rootPath).sort()
if (
fs.existsSync(path.join(rootPath, 'seafile-ignore.txt')) ||
fs.existsSync(path.join(rootPath, 'seafile-ignore.local.txt'))
) candidateDirs.unshift('.')
let updatedCount = 0
for (const relDir of candidateDirs) {
const absDir = relDir === '.' ? rootPath : path.join(rootPath, relDir)
if (relDir !== '.' && SKIP_DIR_PATTERN.test(relDir)) continue
const ignoreFile = path.join(absDir, 'seafile-ignore.txt')
const localFile = path.join(absDir, 'seafile-ignore.local.txt')
if (!fs.existsSync(ignoreFile) && !fs.existsSync(localFile)) continue
console.log('---- updating [[' + relDir + '/seafile-ignore.txt]] ----')
if (willWrite) {
let merged = globalContent
if (fs.existsSync(localFile)) merged += fs.readFileSync(localFile, 'utf8')
fs.writeFileSync(ignoreFile, merged)
updatedCount++
}
console.log('')
}
if (willWrite) console.log('*** done: ' + updatedCount + ' file(s) updated ***')
}
main().catch(err => {
console.error(err)
process.exit(1)
})
+3 -2
View File
@@ -55,9 +55,10 @@ cd $ROOTPATH
echo "*** Starting from [[`pwd`]] ***" echo "*** Starting from [[`pwd`]] ***"
echo "" echo ""
find . -mindepth 0 -maxdepth 3 -type d -name '[^.]*' | grep -E -v 'node_modules|uni_modules|\.deploy_git|\.git|.svn|\.vscode|\.wrangler|unpackage|_webroot|_logstore|_datasotre|_archive|_filestore|_ssl' | while read repo # 根目录自身也是候选(find -name '[^.]*' 匹配不到根目录 .,手动补上)
{ echo "."; find . -mindepth 0 -maxdepth 3 -type d -name '[^.]*'; } | grep -E -v 'node_modules|uni_modules|\.deploy_git|\.git|.svn|\.vscode|\.wrangler|unpackage|_webroot|_logstore|_datasotre|_archive|_filestore|_ssl' | while read repo
do do
if [ -f "$repo/seafile-ignore.txt" ] || [ -d "$repo/.git" ] if [ -f "$repo/seafile-ignore.txt" ] || [ -f "$repo/seafile-ignore.local.txt" ]
then then
echo "---- updating [[$repo/seafile-ignore.txt]] ----" echo "---- updating [[$repo/seafile-ignore.txt]] ----"
if [ "$YESNO" = 'y' ] if [ "$YESNO" = 'y' ]
+5
View File
@@ -0,0 +1,5 @@
speed() { curl -sS -o /dev/null -w "http=%{http_code} dns=%{time_namelookup}
conn=%{time_connect} tls=%{time_appconnect} ttfb=%{time_starttransfer}
total=%{time_total}\n" "$1"; }
speed "$1"
+22 -9
View File
@@ -19,6 +19,10 @@ if [[ ! -f "$SETTINGS_FILE" ]]; then
fi fi
# Parse the JSON to get labels and corresponding details # Parse the JSON to get labels and corresponding details
# 临时把 IFS 设为换行,让 =($(...)) 只按行拆分、保留 label 内部空格,
# 避免默认 IFS(空格) 拆散含空格的 label,导致每台显示两行、labels/hosts 错位
oldIFS=$IFS
IFS=$'\n'
labels=($(jq -r '.["sshfs.configs"][] | .label' "$SETTINGS_FILE")) labels=($(jq -r '.["sshfs.configs"][] | .label' "$SETTINGS_FILE"))
hosts=($(jq -r '.["sshfs.configs"][] | .host' "$SETTINGS_FILE")) hosts=($(jq -r '.["sshfs.configs"][] | .host' "$SETTINGS_FILE"))
ports=($(jq -r '.["sshfs.configs"][] | .port' "$SETTINGS_FILE")) ports=($(jq -r '.["sshfs.configs"][] | .port' "$SETTINGS_FILE"))
@@ -26,13 +30,12 @@ names=($(jq -r '.["sshfs.configs"][] | .name' "$SETTINGS_FILE"))
usernames=($(jq -r '.["sshfs.configs"][] | .username' "$SETTINGS_FILE")) usernames=($(jq -r '.["sshfs.configs"][] | .username' "$SETTINGS_FILE"))
passwords=($(jq -r '.["sshfs.configs"][] | .password' "$SETTINGS_FILE")) passwords=($(jq -r '.["sshfs.configs"][] | .password' "$SETTINGS_FILE"))
privateKeyPaths=($(jq -r '.["sshfs.configs"][] | .privateKeyPath' "$SETTINGS_FILE")) privateKeyPaths=($(jq -r '.["sshfs.configs"][] | .privateKeyPath' "$SETTINGS_FILE"))
IFS=$oldIFS
options=()
prefixes=() prefixes=()
for i in "${!labels[@]}"; do for i in "${!labels[@]}"; do
option=$(echo "${labels[$i]}" | sed 's/[^[:alnum:]].*//') # 提取前几个字母和数字 option=$(echo "${labels[$i]}" | sed 's/[^[:alnum:]].*//') # 提取前缀(前导字母数字)
options+=("$option: ${labels[$i]}") # 去掉序号,保留前缀和完整label prefixes+=("$option")
prefixes+=("$option") # 保存前缀
done done
# Check if a command-line argument is provided # Check if a command-line argument is provided
@@ -40,16 +43,26 @@ if [ "$1" != "" ]; then
# 如果提供了参数,则直接使用这个前缀 # 如果提供了参数,则直接使用这个前缀
host_to_ssh=$1 host_to_ssh=$1
else else
echo "Select a target to connect via SSH, or press 0 to exit:" echo "Select a target to connect via SSH (Enter to exit):"
echo echo
# Display options without numbers # 对齐前缀列:按最长前缀宽度填充
for opt in "${options[@]}"; do maxlen=0
echo "$opt" for p in "${prefixes[@]}"; do
[ ${#p} -gt $maxlen ] && maxlen=${#p}
done
for i in "${!labels[@]}"; do
printf "%-*s: %s\n" "$maxlen" "${prefixes[$i]}" "${labels[$i]}"
done done
echo echo
read -p "请输入要连接的主机前缀: " host_to_ssh read -p "请输入要连接的主机前缀 (直接回车退出): " host_to_ssh
# 空输入直接退出(空前缀会误匹配到错乱的数组项,直接拦截)
if [ -z "$host_to_ssh" ]; then
echo "已退出。"
exit 0
fi
fi fi
echo echo
+1
View File
@@ -0,0 +1 @@
fetch('https://git.tic.cc/opx/sysconfig/raw/branch/main/agents/syncAgentRule.mjs').then(r=>{if(!r.ok)throw new Error('HTTP '+r.status);return r.text()}).then(async t=>{await import('data:text/javascript;base64,'+Buffer.from(t).toString('base64'))})