@ia-ccun/code-agent-cli 0.0.54-beta.1 → 0.0.54-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/bin/cli.js +97 -69
  2. package/config/agent/extensions/aicode-advisor.ts +187 -64
  3. package/config/agent/extensions/aicode-banner.ts +101 -0
  4. package/config/agent/extensions/aicode-btw.ts +619 -182
  5. package/config/agent/extensions/aicode-cd.ts +246 -63
  6. package/config/agent/extensions/aicode-pets.ts +0 -11
  7. package/config/agent/extensions/aicode-plan.ts +2 -0
  8. package/config/agent/extensions/aicode-safety-guard.ts +3 -0
  9. package/config/agent/extensions/aicode-tips.ts +1 -16
  10. package/config/agent/extensions/context-monitor.ts +30 -15
  11. package/config/agent/extensions/context.ts +132 -60
  12. package/config/agent/extensions/custom-footer.ts +0 -5
  13. package/config/agent/extensions/custom.ts +41 -29
  14. package/config/agent/extensions/error-monitor.ts +37 -33
  15. package/config/agent/extensions/goal.ts +8 -6
  16. package/config/agent/extensions/msg-debug.ts +103 -112
  17. package/config/agent/extensions/review.ts +365 -164
  18. package/config/agent/extensions/working-msg.ts +2 -4
  19. package/config/agent/settings.json +15 -14
  20. package/config/agent/skills/hello/SKILL.md +1 -1
  21. package/config/agent/skills/refactor/SKILL.md +40 -0
  22. package/dist/banner.d.ts.map +1 -1
  23. package/dist/banner.js +8 -1
  24. package/dist/banner.js.map +1 -1
  25. package/dist/config-loader.d.ts.map +1 -1
  26. package/dist/config-loader.js +9 -2
  27. package/dist/config-loader.js.map +1 -1
  28. package/package.json +6 -7
  29. package/scripts/regression/README.md +224 -0
  30. package/scripts/regression/cases/01-version-and-deps.sh +94 -0
  31. package/scripts/regression/cases/02-extension-loading.sh +130 -0
  32. package/scripts/regression/cases/03-rpc-protocol.sh +259 -0
  33. package/scripts/regression/cases/04-prompt-stream.sh +130 -0
  34. package/scripts/regression/cases/05-slash-commands-and-skills.sh +119 -0
  35. package/scripts/regression/cases/06-tool-execution.sh +115 -0
  36. package/scripts/regression/cases/07-pi-new-features.sh +141 -0
  37. package/scripts/regression/lib/common.sh +287 -0
  38. package/scripts/regression/results/.last_pid +1 -0
  39. package/scripts/regression/results/.last_workdir +1 -0
  40. package/scripts/regression/results/02-extension-loading.log +21 -0
  41. package/scripts/regression/results/03-prompt-stream.log +16 -0
  42. package/scripts/regression/results/03-rpc-protocol.log +27 -0
  43. package/scripts/regression/results/04-slash-commands-and-skills.log +11 -0
  44. package/scripts/regression/results/05-tool-execution.log +8 -0
  45. package/scripts/regression/results/06-pi-new-features.log +27 -0
  46. package/scripts/regression/run-all.sh +187 -0
  47. package/config/agent/bin/fd.exe +0 -0
  48. package/config/agent/bin/rg.exe +0 -0
  49. package/config/agent/extensions/aicode-filter-outputs.ts +0 -131
  50. package/config/agent/extensions/aicode-snake.ts +0 -354
  51. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/SKILL.md +0 -0
  52. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/analyze.py +0 -0
  53. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/examples/showcase_financial_pl_data.csv +0 -0
  54. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/requirements.txt +0 -0
  55. /package/config/agent/{skills_bak → skills}/csv-data-summarizer/resources/sample.csv +0 -0
  56. /package/config/agent/{skills_bak → skills}/dispatching-parallel-agents/SKILL.md +0 -0
  57. /package/config/agent/{skills_bak → skills}/find-skills/SKILL.md +0 -0
  58. /package/config/agent/{skills_bak → skills}/finishing-a-development-branch/SKILL.md +0 -0
  59. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/LICENSE +0 -0
  60. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.md +0 -0
  61. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/README.zh.md +0 -0
  62. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/SKILL.md +0 -0
  63. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agentloop-core.svg +0 -0
  64. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/agents/openai.yaml +0 -0
  65. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style1-flat.png +0 -0
  66. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style2-dark.png +0 -0
  67. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
  68. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style4-notion.png +0 -0
  69. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style5-glass.png +0 -0
  70. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style6-claude.png +0 -0
  71. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/assets/samples/sample-style7-openai.png +0 -0
  72. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/agent-memory-types-style4.json +0 -0
  73. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/api-flow-style7.json +0 -0
  74. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/mem0-style1.json +0 -0
  75. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/microservices-style3.json +0 -0
  76. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/multi-agent-style5.json +0 -0
  77. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/system-architecture-style6.json +0 -0
  78. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/fixtures/tool-call-style2.json +0 -0
  79. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/package.json +0 -0
  80. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/icons.md +0 -0
  81. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-1-flat-icon.md +0 -0
  82. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-2-dark-terminal.md +0 -0
  83. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-3-blueprint.md +0 -0
  84. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-4-notion-clean.md +0 -0
  85. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-5-glassmorphism.md +0 -0
  86. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-6-claude-official.md +0 -0
  87. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-7-openai.md +0 -0
  88. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/style-diagram-matrix.md +0 -0
  89. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/references/svg-layout-best-practices.md +0 -0
  90. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/README.md +0 -0
  91. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-diagram.sh +0 -0
  92. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/generate-from-template.py +0 -0
  93. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/test-all-styles.sh +0 -0
  94. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/scripts/validate-svg.sh +0 -0
  95. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/agent-architecture.svg +0 -0
  96. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/architecture.svg +0 -0
  97. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/comparison-matrix.svg +0 -0
  98. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/data-flow.svg +0 -0
  99. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/er-diagram.svg +0 -0
  100. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/flowchart.svg +0 -0
  101. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/sequence.svg +0 -0
  102. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/state-machine.svg +0 -0
  103. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/timeline.svg +0 -0
  104. /package/config/agent/{skills_bak → skills}/fireworks-tech-graph/templates/use-case.svg +0 -0
  105. /package/config/agent/{skills_bak → skills}/librarian/SKILL.md +0 -0
  106. /package/config/agent/{skills_bak → skills}/markdown-to-html/SKILL.md +0 -0
  107. /package/config/agent/{skills_bak → skills}/milvus-skill/LICENSE +0 -0
  108. /package/config/agent/{skills_bak → skills}/milvus-skill/README.md +0 -0
  109. /package/config/agent/{skills_bak → skills}/milvus-skill/SKILL.md +0 -0
  110. /package/config/agent/{skills_bak → skills}/milvus-skill/references/collection.md +0 -0
  111. /package/config/agent/{skills_bak → skills}/milvus-skill/references/database.md +0 -0
  112. /package/config/agent/{skills_bak → skills}/milvus-skill/references/index.md +0 -0
  113. /package/config/agent/{skills_bak → skills}/milvus-skill/references/partition.md +0 -0
  114. /package/config/agent/{skills_bak → skills}/milvus-skill/references/patterns.md +0 -0
  115. /package/config/agent/{skills_bak → skills}/milvus-skill/references/user-role.md +0 -0
  116. /package/config/agent/{skills_bak → skills}/milvus-skill/references/vector.md +0 -0
  117. /package/config/agent/{skills_bak → skills}/pr/SKILL.md +0 -0
  118. /package/config/agent/{skills_bak → skills}/receiving-code-review/SKILL.md +0 -0
  119. /package/config/agent/{skills_bak → skills}/requesting-code-review/SKILL.md +0 -0
  120. /package/config/agent/{skills_bak → skills}/requesting-code-review/code-reviewer.md +0 -0
  121. /package/config/agent/{skills_bak → skills}/subagent-driven-development/SKILL.md +0 -0
  122. /package/config/agent/{skills_bak → skills}/subagent-driven-development/code-quality-reviewer-prompt.md +0 -0
  123. /package/config/agent/{skills_bak → skills}/subagent-driven-development/implementer-prompt.md +0 -0
  124. /package/config/agent/{skills_bak → skills}/subagent-driven-development/spec-reviewer-prompt.md +0 -0
  125. /package/config/agent/{skills_bak → skills}/systematic-debugging/CREATION-LOG.md +0 -0
  126. /package/config/agent/{skills_bak → skills}/systematic-debugging/SKILL.md +0 -0
  127. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting-example.ts +0 -0
  128. /package/config/agent/{skills_bak → skills}/systematic-debugging/condition-based-waiting.md +0 -0
  129. /package/config/agent/{skills_bak → skills}/systematic-debugging/defense-in-depth.md +0 -0
  130. /package/config/agent/{skills_bak → skills}/systematic-debugging/find-polluter.sh +0 -0
  131. /package/config/agent/{skills_bak → skills}/systematic-debugging/root-cause-tracing.md +0 -0
  132. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-academic.md +0 -0
  133. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-1.md +0 -0
  134. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-2.md +0 -0
  135. /package/config/agent/{skills_bak → skills}/systematic-debugging/test-pressure-3.md +0 -0
  136. /package/config/agent/{skills_bak → skills}/test-driven-development/SKILL.md +0 -0
  137. /package/config/agent/{skills_bak → skills}/test-driven-development/testing-anti-patterns.md +0 -0
  138. /package/config/agent/{skills_bak → skills}/unit-test/SKILL.md +0 -0
  139. /package/config/agent/{skills_bak → skills}/using-git-worktrees/SKILL.md +0 -0
  140. /package/config/agent/{skills_bak → skills}/zread/README.md +0 -0
  141. /package/config/agent/{skills_bak → skills}/zread/SKILL.md +0 -0
  142. /package/config/agent/{skills_bak → skills}/zread/references/stdio-protocol.md +0 -0
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env bash
2
+ # run-all.sh — 串联所有回归测试用例,输出汇总报告
3
+ # 用法:
4
+ # ./run-all.sh # 跑全部 6 个 case
5
+ # ./run-all.sh --only 01,03,05 # 只跑指定 case
6
+ # ./run-all.sh --quiet # 减少输出
7
+ # ./run-all.sh --aicode /path/to/aicode # 指定二进制路径
8
+ # AICODE_BIN=/path/to/aicode ./run-all.sh
9
+ #
10
+ # 退出码:0 全部通过 / 1 有失败 / 2 环境问题(缺 jq 等)
11
+
12
+ set -uo pipefail
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+
15
+ # 参数解析
16
+ ONLY_CASES=""
17
+ QUIET=0
18
+ while [[ $# -gt 0 ]]; do
19
+ case "$1" in
20
+ --only) ONLY_CASES="$2"; shift 2 ;;
21
+ --quiet|-q) QUIET=1; shift ;;
22
+ --aicode) export AICODE_BIN="$2"; shift 2 ;;
23
+ --help|-h)
24
+ cat <<EOF
25
+ Usage: $0 [--only CASES] [--quiet] [--aicode PATH]
26
+
27
+ Options:
28
+ --only CASES 只跑指定 case(逗号分隔,如 01,03,05)
29
+ --quiet, -q 减少输出
30
+ --aicode PATH 指定 aicode 二进制路径(默认:PATH 中的 aicode)
31
+ --help, -h 显示帮助
32
+
33
+ Environment:
34
+ AICODE_BIN aicode 二进制路径(同 --aicode)
35
+ AICODE_CONFIG 配置目录(同 --config)
36
+ EOF
37
+ exit 0
38
+ ;;
39
+ *) echo "Unknown arg: $1"; exit 2 ;;
40
+ esac
41
+ done
42
+
43
+ [[ $QUIET -eq 1 ]] && export REGRESSION_QUIET=1
44
+
45
+ # 前置依赖检查
46
+ echo "═══ aicode-cli 回归测试 ═══"
47
+ echo ""
48
+ echo "AICODE_BIN: ${AICODE_BIN:-aicode}"
49
+ echo "AICODE_CONFIG: ${AICODE_CONFIG:-$HOME/.aicode-cli}"
50
+ echo "SCRIPT_DIR: $SCRIPT_DIR"
51
+
52
+ # jq 必须
53
+ if ! command -v jq >/dev/null 2>&1; then
54
+ echo ""
55
+ echo "ERROR: jq 未安装。请:brew install jq"
56
+ exit 2
57
+ fi
58
+
59
+ # aicode 必须
60
+ if ! command -v "${AICODE_BIN:-aicode}" >/dev/null 2>&1; then
61
+ echo ""
62
+ echo "ERROR: ${AICODE_BIN:-aicode} 未找到。设 AICODE_BIN=/path/to/aicode 或 --aicode PATH"
63
+ exit 2
64
+ fi
65
+
66
+ echo ""
67
+ echo "─── 预检 ───"
68
+ "$AICODE_BIN" -v 2>&1 | head -1 | sed 's/^/ version: /'
69
+ echo ""
70
+
71
+ # 收集 case
72
+ ALL_CASES=($(ls "$SCRIPT_DIR/cases/"*.sh 2>/dev/null | sort | xargs -n1 basename))
73
+ if [[ -n "$ONLY_CASES" ]]; then
74
+ IFS=',' read -ra WANTED <<<"$ONLY_CASES"
75
+ FILTERED=()
76
+ for c in "${ALL_CASES[@]}"; do
77
+ for w in "${WANTED[@]}"; do
78
+ # 支持短前缀匹配:"03" 匹配 "03-rpc-protocol.sh"
79
+ if [[ "$c" == "${w}.sh" || "$c" == "${w}-"* ]]; then
80
+ FILTERED+=("$c")
81
+ break
82
+ fi
83
+ done
84
+ done
85
+ if [[ ${#FILTERED[@]} -gt 0 ]]; then
86
+ CASES_TO_RUN=("${FILTERED[@]}")
87
+ else
88
+ CASES_TO_RUN=()
89
+ fi
90
+ else
91
+ CASES_TO_RUN=("${ALL_CASES[@]}")
92
+ fi
93
+
94
+ if [[ ${#CASES_TO_RUN[@]} -eq 0 ]]; then
95
+ echo "ERROR: 没有匹配的 case (--only=$ONLY_CASES)"
96
+ exit 2
97
+ fi
98
+
99
+ echo "将运行 ${#CASES_TO_RUN[@]} 个 case: ${CASES_TO_RUN[*]}"
100
+ echo ""
101
+
102
+ # 跑每个 case
103
+ TOTAL_PASS=0
104
+ TOTAL_FAIL=0
105
+ TOTAL_SKIP=0
106
+ FAILED_CASES=()
107
+
108
+ for case_file in "${CASES_TO_RUN[@]}"; do
109
+ echo "━━━ $case_file ━━━"
110
+ chmod +x "$SCRIPT_DIR/cases/$case_file" 2>/dev/null
111
+
112
+ if "$SCRIPT_DIR/cases/$case_file"; then
113
+ # 从 case 输出里提取 pass/fail 数(不太可靠,但能汇总)
114
+ :
115
+ else
116
+ FAILED_CASES+=("$case_file")
117
+ fi
118
+
119
+ # 统计本 case 的 pass/fail/skip
120
+ last_log="$SCRIPT_DIR/results/${case_file%.sh}.log"
121
+ if [[ -f "$last_log" ]]; then
122
+ # 失败标记
123
+ if grep -q "✗" "$last_log" 2>/dev/null; then
124
+ :
125
+ fi
126
+ fi
127
+ done
128
+
129
+ # 解析每个 case 的日志
130
+ echo ""
131
+ echo "═══ 汇总 ═══"
132
+
133
+ for case_file in "${CASES_TO_RUN[@]}"; do
134
+ case_name="${case_file%.sh}"
135
+ log="$SCRIPT_DIR/results/${case_name}.log"
136
+ result="?"
137
+ pass_n=0
138
+ fail_n=0
139
+
140
+ if [[ -f "$log" ]]; then
141
+ # 通过 case 退出码判断(case 脚本内部已有 summary + exit)
142
+ # 同时作为备份,使用 grep 统计 stdout 输出
143
+ case_exit_file="$SCRIPT_DIR/results/.case_exit_${case_name}"
144
+ if [[ -f "$case_exit_file" ]]; then
145
+ case_exit=$(cat "$case_exit_file")
146
+ if [[ "$case_exit" -ne 0 ]]; then
147
+ # 整个 case 失败,用 FAIL 计数(不具体到哪个检查)
148
+ fail_n=1
149
+ pass_n=0
150
+ skip_n=0
151
+ else
152
+ pass_n=1
153
+ fail_n=0
154
+ skip_n=0
155
+ fi
156
+ else
157
+ pass_n=0; fail_n=0; skip_n=0
158
+ fi
159
+ else
160
+ pass_n=0; fail_n=0; skip_n=0
161
+ fi
162
+
163
+ TOTAL_PASS=$((TOTAL_PASS + pass_n))
164
+ TOTAL_FAIL=$((TOTAL_FAIL + fail_n))
165
+ TOTAL_SKIP=$((TOTAL_SKIP + skip_n))
166
+
167
+ if [[ $fail_n -gt 0 ]]; then
168
+ printf " %-40s ✗ %d fails\n" "$case_name" "$fail_n"
169
+ else
170
+ printf " %-40s ✓ pass\n" "$case_name"
171
+ fi
172
+ done
173
+
174
+ echo ""
175
+ echo "─── 总计 ───"
176
+ echo " PASS: $TOTAL_PASS"
177
+ echo " FAIL: $TOTAL_FAIL"
178
+ echo " SKIP: $TOTAL_SKIP"
179
+ echo ""
180
+
181
+ if [[ $TOTAL_FAIL -eq 0 ]]; then
182
+ echo "✓ ALL CASES PASSED"
183
+ exit 0
184
+ else
185
+ echo "✗ $TOTAL_FAIL FAILURES — 详见 results/*.log"
186
+ exit 1
187
+ fi
Binary file
Binary file
@@ -1,131 +0,0 @@
1
- /**
2
- * @author: xujianjiang
3
- * AICode 输出脱敏扩展 — 在 LLM 看到工具结果之前,对内容进行过滤和敏感信息替换
4
- *
5
- * 功能说明:
6
- * - 内容级脱敏:API Key、Token、密码、JWT、数据库连接串、私钥等
7
- * - 文件级拦截:.env、secrets、credentials 等敏感文件的读取结果整体替换
8
- * - 错误透传:工具执行失败时不拦截,保留调试线索
9
- * - UI 友好:TUI 模式下弹出 AICode 风格的通知提示
10
- *
11
- * 触发时机:tool_result 钩子(在 Pi 注入到 LLM 上下文之前生效)
12
- */
13
-
14
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
-
16
- type SensitivePattern = {
17
- pattern: RegExp;
18
- replacement: string;
19
- };
20
-
21
- function redactText(
22
- text: string,
23
- patterns: SensitivePattern[],
24
- ): { text: string; modified: boolean } {
25
- let result = text;
26
- let modified = false;
27
-
28
- for (const { pattern, replacement } of patterns) {
29
- const redacted = result.replace(pattern, replacement);
30
- if (redacted !== result) {
31
- modified = true;
32
- result = redacted;
33
- }
34
- }
35
-
36
- return { text: result, modified };
37
- }
38
-
39
- /**
40
- * 在 LLM 看到工具结果之前,对内容进行过滤或转换。
41
- * 对 API Key、Token、密码等敏感数据进行脱敏。
42
- */
43
- export default function (pi: ExtensionAPI) {
44
- const sensitivePatterns: SensitivePattern[] = [
45
- { pattern: /\b(sk-[a-zA-Z0-9]{20,})\b/g, replacement: "[OPENAI_KEY_REDACTED]" },
46
- { pattern: /\b(sk-ant-[a-zA-Z0-9_-]{20,})\b/g, replacement: "[ANTHROPIC_KEY_REDACTED]" },
47
- { pattern: /\b(sk-or-v1-[a-zA-Z0-9_-]{20,})\b/g, replacement: "[OPENROUTER_KEY_REDACTED]" },
48
- { pattern: /\b(AIza[a-zA-Z0-9_-]{30,})\b/g, replacement: "[GOOGLE_KEY_REDACTED]" },
49
- {
50
- pattern: /\b(cf(?:k|ut|at)_[a-zA-Z0-9_-]{41,})\b/g,
51
- replacement: "[CLOUDFLARE_TOKEN_REDACTED]",
52
- },
53
- {
54
- pattern: /\b(CLOUDFLARE_API_TOKEN|CF_API_TOKEN)\s*=\s*['"]?[a-zA-Z0-9_-]{40,}['"]?/gi,
55
- replacement: "$1=[CLOUDFLARE_TOKEN_REDACTED]",
56
- },
57
- {
58
- pattern: /\b(CLOUDFLARE_API_KEY|CF_API_KEY)\s*=\s*['"]?[a-f0-9]{37,45}['"]?/gi,
59
- replacement: "$1=[CLOUDFLARE_KEY_REDACTED]",
60
- },
61
- { pattern: /\b(npm_[a-zA-Z0-9]{20,})\b/g, replacement: "[NPM_TOKEN_REDACTED]" },
62
- { pattern: /\b(glpat-[a-zA-Z0-9_-]{20,})\b/g, replacement: "[GITLAB_TOKEN_REDACTED]" },
63
- { pattern: /\b(gh[pousr]_[a-zA-Z0-9]{36,})\b/g, replacement: "[GITHUB_TOKEN_REDACTED]" },
64
- { pattern: /\b(xox[baprs]-[a-zA-Z0-9-]{10,})\b/g, replacement: "[SLACK_TOKEN_REDACTED]" },
65
- { pattern: /\b(AKIA[A-Z0-9]{16})\b/g, replacement: "[AWS_KEY_REDACTED]" },
66
- {
67
- pattern: /\b(api[_-]?key|apikey)\s*[=:]\s*['"]?([a-zA-Z0-9_-]{20,})['"]?/gi,
68
- replacement: "$1=[REDACTED]",
69
- },
70
- {
71
- pattern: /\b(secret|token|password|passwd|pwd)\s*[=:]\s*['"]?([^\s'"]{8,})['"]?/gi,
72
- replacement: "$1=[REDACTED]",
73
- },
74
- { pattern: /\b(bearer)\s+([a-zA-Z0-9._-]{20,})\b/gi, replacement: "Bearer [REDACTED]" },
75
- {
76
- pattern: /\beyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\b/g,
77
- replacement: "[JWT_REDACTED]",
78
- },
79
- { pattern: /(mongodb(\+srv)?:\/\/[^:]+:)[^@]+(@)/gi, replacement: "$1[REDACTED]$3" },
80
- { pattern: /(postgres(ql)?:\/\/[^:]+:)[^@]+(@)/gi, replacement: "$1[REDACTED]$3" },
81
- { pattern: /(mysql:\/\/[^:]+:)[^@]+(@)/gi, replacement: "$1[REDACTED]$3" },
82
- { pattern: /(redis:\/\/[^:]+:)[^@]+(@)/gi, replacement: "$1[REDACTED]$3" },
83
- {
84
- pattern:
85
- /-----BEGIN (RSA |EC |OPENSSH |)PRIVATE KEY-----[\s\S]*?-----END \1PRIVATE KEY-----/g,
86
- replacement: "[PRIVATE_KEY_REDACTED]",
87
- },
88
- ];
89
-
90
- const sensitiveFiles = [
91
- /(^|\/)\.env$/,
92
- /(^|\/)\.env\.(?!example$)[^/]+$/,
93
- /(^|\/)\.dev\.vars($|\.[^/]+$)/,
94
- /(^|\/)secrets?\.(json|ya?ml|toml)$/i,
95
- /(^|\/)credentials/i,
96
- ];
97
-
98
- pi.on("tool_result", async (event, ctx) => {
99
- if (event.isError) return undefined;
100
-
101
- if (event.toolName === "read" && typeof event.input.path === "string") {
102
- const filePath = event.input.path;
103
- if (/(^|\/)\.env\.example$/i.test(filePath)) {
104
- return undefined;
105
- }
106
- for (const pattern of sensitiveFiles) {
107
- if (pattern.test(filePath)) {
108
- if (ctx.hasUI) ctx.ui.notify(`[AICode] 已脱敏敏感文件内容:${filePath}`, "info");
109
- return {
110
- content: [{ type: "text", text: `[AICode] 文件 ${filePath} 内容已脱敏,出于安全考虑不予展示` }],
111
- };
112
- }
113
- }
114
- }
115
-
116
- let wasModified = false;
117
- const content = event.content.map((item) => {
118
- if (item.type !== "text") return item;
119
- const redacted = redactText(item.text, sensitivePatterns);
120
- if (redacted.modified) wasModified = true;
121
- return redacted.modified ? { ...item, text: redacted.text } : item;
122
- });
123
-
124
- if (wasModified) {
125
- if (ctx.hasUI) ctx.ui.notify("[AICode] 已对输出中的敏感信息进行脱敏", "info");
126
- return { content };
127
- }
128
-
129
- return undefined;
130
- });
131
- }
@@ -1,354 +0,0 @@
1
- /**
2
- * @author: xujianjiang
3
- * AICode 贪吃蛇游戏扩展 — 通过 /snake 命令在终端内玩经典贪吃蛇
4
- *
5
- * 功能说明:
6
- * - 经典玩法:方向键或 WASD 控制方向,吃到食物加分
7
- * - 状态持久化:游戏可暂停,下次执行 /snake 自动恢复上次进度
8
- * - 最高分记录:跨会话保留历史最高分(High Score)
9
- * - 自适应渲染:根据终端宽度自动调整游戏区域尺寸
10
- * - 简洁控制:ESC 暂停并保存,Q 退出不保存,R/空格 重开
11
- *
12
- * 使用方法:
13
- * /snake 启动贪吃蛇游戏(无参数则新建游戏或恢复上次进度)
14
- */
15
-
16
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
17
- import { matchesKey, visibleWidth } from "@earendil-works/pi-tui";
18
-
19
- const GAME_WIDTH = 40;
20
- const GAME_HEIGHT = 15;
21
- const TICK_MS = 100;
22
-
23
- type Direction = "up" | "down" | "left" | "right";
24
- type Point = { x: number; y: number };
25
-
26
- interface GameState {
27
- snake: Point[];
28
- food: Point;
29
- direction: Direction;
30
- nextDirection: Direction;
31
- score: number;
32
- gameOver: boolean;
33
- highScore: number;
34
- }
35
-
36
- function createInitialState(): GameState {
37
- const startX = Math.floor(GAME_WIDTH / 2);
38
- const startY = Math.floor(GAME_HEIGHT / 2);
39
- return {
40
- snake: [
41
- { x: startX, y: startY },
42
- { x: startX - 1, y: startY },
43
- { x: startX - 2, y: startY },
44
- ],
45
- food: spawnFood([{ x: startX, y: startY }]),
46
- direction: "right",
47
- nextDirection: "right",
48
- score: 0,
49
- gameOver: false,
50
- highScore: 0,
51
- };
52
- }
53
-
54
- function spawnFood(snake: Point[]): Point {
55
- let food: Point;
56
- do {
57
- food = {
58
- x: Math.floor(Math.random() * GAME_WIDTH),
59
- y: Math.floor(Math.random() * GAME_HEIGHT),
60
- };
61
- } while (snake.some((s) => s.x === food.x && s.y === food.y));
62
- return food;
63
- }
64
-
65
- class SnakeComponent {
66
- private state: GameState;
67
- private interval: ReturnType<typeof setInterval> | null = null;
68
- private onClose: () => void;
69
- private onSave: (state: GameState | null) => void;
70
- private tui: { requestRender: () => void };
71
- private cachedLines: string[] = [];
72
- private cachedWidth = 0;
73
- private version = 0;
74
- private cachedVersion = -1;
75
- private paused: boolean;
76
-
77
- constructor(
78
- tui: { requestRender: () => void },
79
- onClose: () => void,
80
- onSave: (state: GameState | null) => void,
81
- savedState?: GameState,
82
- ) {
83
- this.tui = tui;
84
- if (savedState && !savedState.gameOver) {
85
- // Resume from saved state, start paused
86
- this.state = savedState;
87
- this.paused = true;
88
- } else {
89
- // New game or saved game was over
90
- this.state = createInitialState();
91
- if (savedState) {
92
- this.state.highScore = savedState.highScore;
93
- }
94
- this.paused = false;
95
- this.startGame();
96
- }
97
- this.onClose = onClose;
98
- this.onSave = onSave;
99
- }
100
-
101
- private startGame(): void {
102
- this.interval = setInterval(() => {
103
- if (!this.state.gameOver) {
104
- this.tick();
105
- this.version++;
106
- this.tui.requestRender();
107
- }
108
- }, TICK_MS);
109
- }
110
-
111
- private tick(): void {
112
- // Apply queued direction change
113
- this.state.direction = this.state.nextDirection;
114
-
115
- // Calculate new head position
116
- const head = this.state.snake[0];
117
- let newHead: Point;
118
-
119
- switch (this.state.direction) {
120
- case "up":
121
- newHead = { x: head.x, y: head.y - 1 };
122
- break;
123
- case "down":
124
- newHead = { x: head.x, y: head.y + 1 };
125
- break;
126
- case "left":
127
- newHead = { x: head.x - 1, y: head.y };
128
- break;
129
- case "right":
130
- newHead = { x: head.x + 1, y: head.y };
131
- break;
132
- }
133
-
134
- // Check wall collision
135
- if (newHead.x < 0 || newHead.x >= GAME_WIDTH || newHead.y < 0 || newHead.y >= GAME_HEIGHT) {
136
- this.state.gameOver = true;
137
- return;
138
- }
139
-
140
- // Check self collision
141
- if (this.state.snake.some((s) => s.x === newHead.x && s.y === newHead.y)) {
142
- this.state.gameOver = true;
143
- return;
144
- }
145
-
146
- // Move snake
147
- this.state.snake.unshift(newHead);
148
-
149
- // Check food collision
150
- if (newHead.x === this.state.food.x && newHead.y === this.state.food.y) {
151
- this.state.score += 10;
152
- if (this.state.score > this.state.highScore) {
153
- this.state.highScore = this.state.score;
154
- }
155
- this.state.food = spawnFood(this.state.snake);
156
- } else {
157
- this.state.snake.pop();
158
- }
159
- }
160
-
161
- handleInput(data: string): void {
162
- // If paused (resuming), wait for any key
163
- if (this.paused) {
164
- if (matchesKey(data, "escape") || data === "q" || data === "Q") {
165
- // Quit without clearing save
166
- this.dispose();
167
- this.onClose();
168
- return;
169
- }
170
- // Any other key resumes
171
- this.paused = false;
172
- this.startGame();
173
- return;
174
- }
175
-
176
- // ESC to pause and save
177
- if (matchesKey(data, "escape")) {
178
- this.dispose();
179
- this.onSave(this.state);
180
- this.onClose();
181
- return;
182
- }
183
-
184
- // Q to quit without saving (clears saved state)
185
- if (data === "q" || data === "Q") {
186
- this.dispose();
187
- this.onSave(null); // Clear saved state
188
- this.onClose();
189
- return;
190
- }
191
-
192
- // Arrow keys or WASD
193
- if (matchesKey(data, "up") || data === "w" || data === "W") {
194
- if (this.state.direction !== "down") this.state.nextDirection = "up";
195
- } else if (matchesKey(data, "down") || data === "s" || data === "S") {
196
- if (this.state.direction !== "up") this.state.nextDirection = "down";
197
- } else if (matchesKey(data, "right") || data === "d" || data === "D") {
198
- if (this.state.direction !== "left") this.state.nextDirection = "right";
199
- } else if (matchesKey(data, "left") || data === "a" || data === "A") {
200
- if (this.state.direction !== "right") this.state.nextDirection = "left";
201
- }
202
-
203
- // Restart on game over
204
- if (this.state.gameOver && (data === "r" || data === "R" || data === " ")) {
205
- const highScore = this.state.highScore;
206
- this.state = createInitialState();
207
- this.state.highScore = highScore;
208
- this.onSave(null); // Clear saved state on restart
209
- this.version++;
210
- this.tui.requestRender();
211
- }
212
- }
213
-
214
- invalidate(): void {
215
- this.cachedWidth = 0;
216
- }
217
-
218
- render(width: number): string[] {
219
- if (width === this.cachedWidth && this.cachedVersion === this.version) {
220
- return this.cachedLines;
221
- }
222
-
223
- const lines: string[] = [];
224
-
225
- // Each game cell is 2 chars wide to appear square (terminal cells are ~2:1 aspect)
226
- const cellWidth = 2;
227
- const effectiveWidth = Math.min(GAME_WIDTH, Math.floor((width - 4) / cellWidth));
228
- const effectiveHeight = GAME_HEIGHT;
229
-
230
- // Colors
231
- const dim = (s: string) => `\x1b[2m${s}\x1b[22m`;
232
- const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
233
- const red = (s: string) => `\x1b[31m${s}\x1b[0m`;
234
- const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`;
235
- const bold = (s: string) => `\x1b[1m${s}\x1b[22m`;
236
-
237
- const boxWidth = effectiveWidth * cellWidth;
238
-
239
- // Helper to pad content inside box
240
- const boxLine = (content: string) => {
241
- const contentLen = visibleWidth(content);
242
- const padding = Math.max(0, boxWidth - contentLen);
243
- return dim(" │") + content + " ".repeat(padding) + dim("│");
244
- };
245
-
246
- // Top border
247
- lines.push(this.padLine(dim(` ╭${"─".repeat(boxWidth)}╮`), width));
248
-
249
- // Header with score
250
- const scoreText = `Score: ${bold(yellow(String(this.state.score)))}`;
251
- const highText = `High: ${bold(yellow(String(this.state.highScore)))}`;
252
- const title = `${bold(green("SNAKE"))} │ ${scoreText} │ ${highText}`;
253
- lines.push(this.padLine(boxLine(title), width));
254
-
255
- // Separator
256
- lines.push(this.padLine(dim(` ├${"─".repeat(boxWidth)}┤`), width));
257
-
258
- // Game grid
259
- for (let y = 0; y < effectiveHeight; y++) {
260
- let row = "";
261
- for (let x = 0; x < effectiveWidth; x++) {
262
- const isHead = this.state.snake[0].x === x && this.state.snake[0].y === y;
263
- const isBody = this.state.snake.slice(1).some((s) => s.x === x && s.y === y);
264
- const isFood = this.state.food.x === x && this.state.food.y === y;
265
-
266
- if (isHead) {
267
- row += green("██"); // Snake head (2 chars)
268
- } else if (isBody) {
269
- row += green("▓▓"); // Snake body (2 chars)
270
- } else if (isFood) {
271
- row += red("◆ "); // Food (2 chars)
272
- } else {
273
- row += " "; // Empty cell (2 spaces)
274
- }
275
- }
276
- lines.push(this.padLine(dim(" │") + row + dim("│"), width));
277
- }
278
-
279
- // Separator
280
- lines.push(this.padLine(dim(` ├${"─".repeat(boxWidth)}┤`), width));
281
-
282
- // Footer
283
- let footer: string;
284
- if (this.paused) {
285
- footer = `${yellow(bold("PAUSED"))} Press any key to continue, ${bold("Q")} to quit`;
286
- } else if (this.state.gameOver) {
287
- footer = `${red(bold("GAME OVER!"))} Press ${bold("R")} to restart, ${bold("Q")} to quit`;
288
- } else {
289
- footer = `↑↓←→ or WASD to move, ${bold("ESC")} pause, ${bold("Q")} quit`;
290
- }
291
- lines.push(this.padLine(boxLine(footer), width));
292
-
293
- // Bottom border
294
- lines.push(this.padLine(dim(` ╰${"─".repeat(boxWidth)}╯`), width));
295
-
296
- this.cachedLines = lines;
297
- this.cachedWidth = width;
298
- this.cachedVersion = this.version;
299
-
300
- return lines;
301
- }
302
-
303
- private padLine(line: string, width: number): string {
304
- // Calculate visible length (strip ANSI codes)
305
- const visibleLen = line.replace(/\x1b\[[0-9;]*m/g, "").length;
306
- const padding = Math.max(0, width - visibleLen);
307
- return line + " ".repeat(padding);
308
- }
309
-
310
- dispose(): void {
311
- if (this.interval) {
312
- clearInterval(this.interval);
313
- this.interval = null;
314
- }
315
- }
316
- }
317
-
318
- const SNAKE_SAVE_TYPE = "snake-save";
319
-
320
- export default function (pi: ExtensionAPI) {
321
- pi.registerCommand("snake", {
322
- description: "AICode 贪吃蛇游戏",
323
-
324
- handler: async (_args, ctx) => {
325
- if (!ctx.hasUI) {
326
- ctx.ui.notify("[AICode] 贪吃蛇需要交互模式才能运行", "error");
327
- return;
328
- }
329
-
330
- // Load saved state from session
331
- const entries = ctx.sessionManager.getEntries();
332
- let savedState: GameState | undefined;
333
- for (let i = entries.length - 1; i >= 0; i--) {
334
- const entry = entries[i];
335
- if (entry.type === "custom" && entry.customType === SNAKE_SAVE_TYPE) {
336
- savedState = entry.data as GameState;
337
- break;
338
- }
339
- }
340
-
341
- await ctx.ui.custom((tui, _theme, _kb, done) => {
342
- return new SnakeComponent(
343
- tui,
344
- () => done(undefined),
345
- (state) => {
346
- // Save or clear state
347
- pi.appendEntry(SNAKE_SAVE_TYPE, state);
348
- },
349
- savedState,
350
- );
351
- });
352
- },
353
- });
354
- }