@jackwener/opencli 1.6.1 ā 1.6.2
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.
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -45
- package/README.zh-CN.md +32 -34
- package/autoresearch/browse-tasks.json +18 -20
- package/autoresearch/commands/debug.ts +163 -0
- package/autoresearch/commands/fix.ts +145 -0
- package/autoresearch/commands/plan.ts +88 -0
- package/autoresearch/commands/run.ts +138 -0
- package/autoresearch/config.ts +82 -0
- package/autoresearch/engine.ts +359 -0
- package/autoresearch/eval-all.ts +127 -0
- package/autoresearch/eval-browse.ts +1 -1
- package/autoresearch/eval-publish.ts +238 -0
- package/autoresearch/eval-save.ts +249 -0
- package/autoresearch/eval-skill.ts +14 -8
- package/autoresearch/eval-v2ex.ts +220 -0
- package/autoresearch/eval-zhihu.ts +230 -0
- package/autoresearch/logger.ts +69 -0
- package/autoresearch/presets/combined-reliability.ts +27 -0
- package/autoresearch/presets/index.ts +23 -0
- package/autoresearch/presets/operate-reliability.ts +24 -0
- package/autoresearch/presets/save-reliability.ts +26 -0
- package/autoresearch/presets/skill-quality.ts +20 -0
- package/autoresearch/presets/v2ex-reliability.ts +24 -0
- package/autoresearch/presets/zhihu-reliability.ts +25 -0
- package/autoresearch/publish-tasks.json +345 -0
- package/autoresearch/run-save.sh +11 -0
- package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
- package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
- package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
- package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
- package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
- package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
- package/autoresearch/save-tasks.json +281 -0
- package/autoresearch/v2ex-tasks.json +899 -0
- package/autoresearch/zhihu-tasks.json +848 -0
- package/dist/browser/base-page.d.ts +4 -2
- package/dist/browser/base-page.js +37 -4
- package/dist/browser/bridge.js +10 -8
- package/dist/browser/cdp.js +2 -6
- package/dist/browser/daemon-client.d.ts +11 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.d.ts +4 -2
- package/dist/browser/dom-helpers.js +42 -31
- package/dist/browser/dom-snapshot.js +23 -1
- package/dist/browser/page.d.ts +7 -2
- package/dist/browser/page.js +112 -30
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +1 -0
- package/dist/cli-manifest.json +1135 -184
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +48 -7
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +88 -0
- package/dist/clis/1688/item.d.ts +70 -0
- package/dist/clis/1688/item.js +187 -0
- package/dist/clis/1688/item.test.d.ts +1 -0
- package/dist/clis/1688/item.test.js +67 -0
- package/dist/clis/1688/search.d.ts +56 -0
- package/dist/clis/1688/search.js +309 -0
- package/dist/clis/1688/search.test.d.ts +1 -0
- package/dist/clis/1688/search.test.js +75 -0
- package/dist/clis/1688/shared.d.ts +112 -0
- package/dist/clis/1688/shared.js +514 -0
- package/dist/clis/1688/shared.test.d.ts +1 -0
- package/dist/clis/1688/shared.test.js +57 -0
- package/dist/clis/1688/store.d.ts +45 -0
- package/dist/clis/1688/store.js +226 -0
- package/dist/clis/1688/store.test.d.ts +1 -0
- package/dist/clis/1688/store.test.js +62 -0
- package/dist/clis/amazon/bestsellers.d.ts +0 -20
- package/dist/clis/amazon/bestsellers.js +6 -129
- package/dist/clis/amazon/bestsellers.test.js +12 -3
- package/dist/clis/amazon/movers-shakers.d.ts +1 -0
- package/dist/clis/amazon/movers-shakers.js +7 -0
- package/dist/clis/amazon/new-releases.d.ts +1 -0
- package/dist/clis/amazon/new-releases.js +7 -0
- package/dist/clis/amazon/rankings.d.ts +59 -0
- package/dist/clis/amazon/rankings.js +226 -0
- package/dist/clis/amazon/rankings.test.d.ts +1 -0
- package/dist/clis/amazon/rankings.test.js +41 -0
- package/dist/clis/amazon/shared.d.ts +11 -0
- package/dist/clis/amazon/shared.js +121 -11
- package/dist/clis/amazon/shared.test.js +11 -0
- package/dist/clis/bilibili/comments.js +2 -2
- package/dist/clis/bilibili/comments.test.js +3 -2
- package/dist/clis/bilibili/download.js +2 -1
- package/dist/clis/bilibili/subtitle.js +4 -3
- package/dist/clis/bilibili/subtitle.test.js +2 -1
- package/dist/clis/bilibili/utils.d.ts +5 -0
- package/dist/clis/bilibili/utils.js +30 -0
- package/dist/clis/bilibili/utils.test.d.ts +1 -0
- package/dist/clis/bilibili/utils.test.js +17 -0
- package/dist/clis/douban/marks.js +1 -1
- package/dist/clis/douban/subject.yaml +50 -19
- package/dist/clis/doubao/utils.js +32 -12
- package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
- package/dist/clis/douyin/_shared/transcode.test.js +0 -2
- package/dist/clis/douyin/draft.test.js +0 -2
- package/dist/clis/facebook/search.test.js +0 -2
- package/dist/clis/gemini/ask.js +9 -3
- package/dist/clis/gemini/ask.test.d.ts +1 -0
- package/dist/clis/gemini/ask.test.js +100 -0
- package/dist/clis/gemini/reply-state.test.d.ts +1 -0
- package/dist/clis/gemini/reply-state.test.js +641 -0
- package/dist/clis/gemini/utils.d.ts +44 -1
- package/dist/clis/gemini/utils.js +528 -61
- package/dist/clis/gemini/utils.test.js +149 -2
- package/dist/clis/hupu/detail.d.ts +1 -0
- package/dist/clis/hupu/detail.js +72 -0
- package/dist/clis/hupu/hot.yaml +43 -0
- package/dist/clis/hupu/like.d.ts +1 -0
- package/dist/clis/hupu/like.js +75 -0
- package/dist/clis/hupu/reply.d.ts +1 -0
- package/dist/clis/hupu/reply.js +71 -0
- package/dist/clis/hupu/search.d.ts +1 -0
- package/dist/clis/hupu/search.js +59 -0
- package/dist/clis/hupu/unlike.d.ts +1 -0
- package/dist/clis/hupu/unlike.js +75 -0
- package/dist/clis/hupu/utils.d.ts +20 -0
- package/dist/clis/hupu/utils.js +319 -0
- package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
- package/dist/clis/instagram/_shared/private-publish.js +1030 -0
- package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
- package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
- package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
- package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
- package/dist/clis/instagram/_shared/runtime-info.js +81 -0
- package/dist/clis/instagram/note.d.ts +1 -0
- package/dist/clis/instagram/note.js +222 -0
- package/dist/clis/instagram/note.test.d.ts +1 -0
- package/dist/clis/instagram/note.test.js +81 -0
- package/dist/clis/instagram/post.d.ts +4 -0
- package/dist/clis/instagram/post.js +1496 -0
- package/dist/clis/instagram/post.test.d.ts +1 -0
- package/dist/clis/instagram/post.test.js +1647 -0
- package/dist/clis/instagram/reel.d.ts +1 -0
- package/dist/clis/instagram/reel.js +826 -0
- package/dist/clis/instagram/reel.test.d.ts +1 -0
- package/dist/clis/instagram/reel.test.js +167 -0
- package/dist/clis/instagram/story.d.ts +1 -0
- package/dist/clis/instagram/story.js +115 -0
- package/dist/clis/instagram/story.test.d.ts +1 -0
- package/dist/clis/instagram/story.test.js +167 -0
- package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
- package/dist/clis/sinafinance/stock-rank.js +65 -0
- package/dist/clis/substack/utils.test.js +0 -2
- package/dist/clis/twitter/post.js +72 -45
- package/dist/clis/twitter/post.test.d.ts +1 -0
- package/dist/clis/twitter/post.test.js +116 -0
- package/dist/clis/twitter/reply.d.ts +12 -0
- package/dist/clis/twitter/reply.js +257 -35
- package/dist/clis/twitter/reply.test.d.ts +1 -0
- package/dist/clis/twitter/reply.test.js +151 -0
- package/dist/clis/xianyu/chat.d.ts +7 -0
- package/dist/clis/xianyu/chat.js +146 -0
- package/dist/clis/xianyu/chat.test.d.ts +1 -0
- package/dist/clis/xianyu/chat.test.js +15 -0
- package/dist/clis/xianyu/item.d.ts +7 -0
- package/dist/clis/xianyu/item.js +152 -0
- package/dist/clis/xianyu/item.test.d.ts +1 -0
- package/dist/clis/xianyu/item.test.js +56 -0
- package/dist/clis/xianyu/search.d.ts +10 -0
- package/dist/clis/xianyu/search.js +134 -0
- package/dist/clis/xianyu/search.test.d.ts +1 -0
- package/dist/clis/xianyu/search.test.js +17 -0
- package/dist/clis/xianyu/utils.d.ts +1 -0
- package/dist/clis/xianyu/utils.js +8 -0
- package/dist/clis/xiaoe/catalog.yaml +129 -0
- package/dist/clis/xiaoe/content.yaml +43 -0
- package/dist/clis/xiaoe/courses.yaml +73 -0
- package/dist/clis/xiaoe/detail.yaml +39 -0
- package/dist/clis/xiaoe/play-url.yaml +124 -0
- package/dist/clis/xiaohongshu/comments.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
- package/dist/clis/xiaohongshu/download.test.js +0 -2
- package/dist/clis/xiaohongshu/note.test.js +0 -2
- package/dist/clis/xiaohongshu/publish.test.js +0 -2
- package/dist/clis/xiaohongshu/search.js +29 -20
- package/dist/clis/xiaohongshu/search.test.js +56 -48
- package/dist/clis/yuanbao/ask.d.ts +21 -0
- package/dist/clis/yuanbao/ask.js +427 -0
- package/dist/clis/yuanbao/ask.test.d.ts +1 -0
- package/dist/clis/yuanbao/ask.test.js +124 -0
- package/dist/clis/yuanbao/new.d.ts +1 -0
- package/dist/clis/yuanbao/new.js +70 -0
- package/dist/clis/yuanbao/new.test.d.ts +1 -0
- package/dist/clis/yuanbao/new.test.js +30 -0
- package/dist/clis/yuanbao/shared.d.ts +13 -0
- package/dist/clis/yuanbao/shared.js +49 -0
- package/dist/clis/zhihu/question.js +30 -19
- package/dist/clis/zhihu/question.test.js +34 -16
- package/dist/commanderAdapter.js +8 -4
- package/dist/commanderAdapter.test.js +42 -0
- package/dist/completion.js +3 -1
- package/dist/completion.test.d.ts +1 -0
- package/dist/completion.test.js +23 -0
- package/dist/doctor.js +1 -1
- package/dist/electron-apps.d.ts +2 -0
- package/dist/electron-apps.js +7 -1
- package/dist/errors.js +1 -1
- package/dist/execution.js +25 -35
- package/dist/explore.js +1 -1
- package/dist/launcher.d.ts +4 -0
- package/dist/launcher.js +64 -8
- package/dist/launcher.test.js +88 -7
- package/dist/output.d.ts +2 -0
- package/dist/output.js +10 -1
- package/dist/output.test.d.ts +0 -3
- package/dist/output.test.js +59 -92
- package/dist/pipeline/executor.test.js +0 -2
- package/dist/pipeline/steps/download.test.js +0 -2
- package/dist/registry.d.ts +2 -0
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +1 -0
- package/dist/types.d.ts +9 -2
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/1688.md +52 -0
- package/docs/adapters/browser/36kr.md +2 -1
- package/docs/adapters/browser/doubao.md +5 -1
- package/docs/adapters/browser/hupu.md +53 -0
- package/docs/adapters/browser/sinafinance.md +32 -2
- package/docs/adapters/browser/weibo.md +6 -1
- package/docs/adapters/browser/wikipedia.md +2 -0
- package/docs/adapters/browser/xianyu.md +42 -0
- package/docs/adapters/browser/xiaoe.md +44 -0
- package/docs/adapters/browser/yuanbao.md +64 -0
- package/docs/adapters/index.md +14 -5
- package/docs/comparison.md +1 -1
- package/docs/developer/ai-workflow.md +2 -2
- package/docs/developer/contributing.md +1 -1
- package/docs/developer/testing.md +2 -0
- package/docs/guide/plugins.md +1 -0
- package/docs/guide/troubleshooting.md +11 -0
- package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
- package/docs/zh/guide/plugins.md +1 -0
- package/extension/dist/background.js +1127 -0
- package/extension/src/background.test.ts +39 -0
- package/extension/src/background.ts +223 -34
- package/extension/src/cdp.ts +194 -4
- package/extension/src/protocol.ts +22 -1
- package/package.json +3 -2
- package/scripts/postinstall.js +1 -1
- package/skills/opencli-explorer/SKILL.md +1 -1
- package/skills/opencli-oneshot/SKILL.md +2 -2
- package/skills/opencli-operate/SKILL.md +120 -27
- package/skills/opencli-usage/SKILL.md +31 -20
- package/skills/opencli-usage/browser.md +114 -16
- package/skills/opencli-usage/public-api.md +32 -3
- package/skills/smart-search/SKILL.md +156 -0
- package/skills/smart-search/references/sources-ai.md +74 -0
- package/skills/smart-search/references/sources-info.md +43 -0
- package/skills/smart-search/references/sources-media.md +50 -0
- package/skills/smart-search/references/sources-other.md +42 -0
- package/skills/smart-search/references/sources-shopping.md +31 -0
- package/skills/smart-search/references/sources-social.md +51 -0
- package/skills/smart-search/references/sources-tech.md +42 -0
- package/skills/smart-search/references/sources-travel.md +20 -0
- package/src/browser/base-page.ts +41 -6
- package/src/browser/bridge.ts +11 -8
- package/src/browser/cdp.ts +1 -8
- package/src/browser/daemon-client.ts +11 -1
- package/src/browser/dom-helpers.ts +43 -31
- package/src/browser/dom-snapshot.ts +23 -1
- package/src/browser/page.ts +115 -31
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.ts +2 -0
- package/src/cli.test.ts +133 -0
- package/src/cli.ts +73 -11
- package/src/clis/1688/item.test.ts +69 -0
- package/src/clis/1688/item.ts +282 -0
- package/src/clis/1688/search.test.ts +81 -0
- package/src/clis/1688/search.ts +402 -0
- package/src/clis/1688/shared.test.ts +75 -0
- package/src/clis/1688/shared.ts +623 -0
- package/src/clis/1688/store.test.ts +69 -0
- package/src/clis/1688/store.ts +300 -0
- package/src/clis/amazon/bestsellers.test.ts +12 -3
- package/src/clis/amazon/bestsellers.ts +6 -178
- package/src/clis/amazon/movers-shakers.ts +8 -0
- package/src/clis/amazon/new-releases.ts +8 -0
- package/src/clis/amazon/rankings.test.ts +47 -0
- package/src/clis/amazon/rankings.ts +312 -0
- package/src/clis/amazon/shared.test.ts +16 -0
- package/src/clis/amazon/shared.ts +134 -12
- package/src/clis/bilibili/comments.test.ts +4 -3
- package/src/clis/bilibili/comments.ts +2 -2
- package/src/clis/bilibili/download.ts +2 -1
- package/src/clis/bilibili/subtitle.test.ts +2 -1
- package/src/clis/bilibili/subtitle.ts +4 -3
- package/src/clis/bilibili/utils.test.ts +21 -0
- package/src/clis/bilibili/utils.ts +27 -0
- package/src/clis/douban/marks.ts +1 -1
- package/src/clis/douban/subject.yaml +50 -19
- package/src/clis/doubao/utils.ts +32 -12
- package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
- package/src/clis/douyin/_shared/transcode.test.ts +0 -2
- package/src/clis/douyin/draft.test.ts +0 -2
- package/src/clis/facebook/search.test.ts +0 -2
- package/src/clis/gemini/ask.test.ts +116 -0
- package/src/clis/gemini/ask.ts +10 -3
- package/src/clis/gemini/reply-state.test.ts +708 -0
- package/src/clis/gemini/utils.test.ts +184 -2
- package/src/clis/gemini/utils.ts +588 -60
- package/src/clis/hupu/detail.ts +126 -0
- package/src/clis/hupu/hot.yaml +43 -0
- package/src/clis/hupu/like.ts +76 -0
- package/src/clis/hupu/reply.ts +76 -0
- package/src/clis/hupu/search.ts +95 -0
- package/src/clis/hupu/unlike.ts +76 -0
- package/src/clis/hupu/utils.ts +381 -0
- package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
- package/src/clis/instagram/_shared/private-publish.ts +1303 -0
- package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
- package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
- package/src/clis/instagram/_shared/runtime-info.ts +91 -0
- package/src/clis/instagram/note.test.ts +96 -0
- package/src/clis/instagram/note.ts +254 -0
- package/src/clis/instagram/post.test.ts +1716 -0
- package/src/clis/instagram/post.ts +1620 -0
- package/src/clis/instagram/reel.test.ts +191 -0
- package/src/clis/instagram/reel.ts +886 -0
- package/src/clis/instagram/story.test.ts +191 -0
- package/src/clis/instagram/story.ts +151 -0
- package/src/clis/sinafinance/stock-rank.ts +68 -0
- package/src/clis/substack/utils.test.ts +0 -2
- package/src/clis/twitter/post.test.ts +157 -0
- package/src/clis/twitter/post.ts +82 -48
- package/src/clis/twitter/reply.test.ts +177 -0
- package/src/clis/twitter/reply.ts +285 -39
- package/src/clis/xianyu/chat.test.ts +20 -0
- package/src/clis/xianyu/chat.ts +175 -0
- package/src/clis/xianyu/item.test.ts +67 -0
- package/src/clis/xianyu/item.ts +172 -0
- package/src/clis/xianyu/search.test.ts +22 -0
- package/src/clis/xianyu/search.ts +151 -0
- package/src/clis/xianyu/utils.ts +9 -0
- package/src/clis/xiaoe/catalog.yaml +129 -0
- package/src/clis/xiaoe/content.yaml +43 -0
- package/src/clis/xiaoe/courses.yaml +73 -0
- package/src/clis/xiaoe/detail.yaml +39 -0
- package/src/clis/xiaoe/play-url.yaml +124 -0
- package/src/clis/xiaohongshu/comments.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
- package/src/clis/xiaohongshu/download.test.ts +0 -2
- package/src/clis/xiaohongshu/note.test.ts +0 -2
- package/src/clis/xiaohongshu/publish.test.ts +0 -2
- package/src/clis/xiaohongshu/search.test.ts +59 -48
- package/src/clis/xiaohongshu/search.ts +31 -21
- package/src/clis/yuanbao/ask.test.ts +156 -0
- package/src/clis/yuanbao/ask.ts +522 -0
- package/src/clis/yuanbao/new.test.ts +36 -0
- package/src/clis/yuanbao/new.ts +81 -0
- package/src/clis/yuanbao/shared.ts +57 -0
- package/src/clis/zhihu/question.test.ts +42 -17
- package/src/clis/zhihu/question.ts +31 -26
- package/src/commanderAdapter.test.ts +51 -0
- package/src/commanderAdapter.ts +8 -4
- package/src/completion.test.ts +30 -0
- package/src/completion.ts +3 -1
- package/src/doctor.ts +1 -1
- package/src/electron-apps.ts +9 -1
- package/src/errors.ts +1 -1
- package/src/execution.ts +26 -30
- package/src/explore.ts +1 -1
- package/src/launcher.test.ts +121 -7
- package/src/launcher.ts +87 -9
- package/src/output.test.ts +50 -90
- package/src/output.ts +10 -1
- package/src/pipeline/executor.test.ts +0 -2
- package/src/pipeline/steps/download.test.ts +0 -2
- package/src/registry.ts +2 -0
- package/src/serialization.ts +2 -0
- package/src/types.ts +9 -2
- package/tests/e2e/browser-auth.test.ts +9 -0
- package/CLI-EXPLORER.md +0 -724
- package/CLI-ONESHOT.md +0 -216
- package/SKILL.md +0 -59
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* /autoresearch:fix ā Iterative error elimination.
|
|
4
|
+
*
|
|
5
|
+
* Auto-detects broken state (build ā test ā browse tests) and iteratively
|
|
6
|
+
* fixes errors one at a time. Stops when error count reaches 0.
|
|
7
|
+
*
|
|
8
|
+
* Priority: build errors ā test failures ā browse task failures
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* npx tsx autoresearch/commands/fix.ts
|
|
12
|
+
* npx tsx autoresearch/commands/fix.ts --iterations 10
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
import { join, dirname } from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { parseArgs } from '../config.js';
|
|
19
|
+
import { Engine, type ModifyContext } from '../engine.js';
|
|
20
|
+
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const ROOT = join(__dirname, '..', '..');
|
|
23
|
+
|
|
24
|
+
function exec(cmd: string): { ok: boolean; output: string } {
|
|
25
|
+
try {
|
|
26
|
+
const output = execSync(cmd, {
|
|
27
|
+
cwd: ROOT, timeout: 120_000, encoding: 'utf-8',
|
|
28
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
29
|
+
}).trim();
|
|
30
|
+
return { ok: true, output };
|
|
31
|
+
} catch (err: any) {
|
|
32
|
+
return { ok: false, output: (err.stdout ?? '') + '\n' + (err.stderr ?? '') };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Detect current broken state and return verify command + error count */
|
|
37
|
+
function detectBrokenState(): { verify: string; errors: number; description: string } | null {
|
|
38
|
+
// 1. Build
|
|
39
|
+
const build = exec('npm run build 2>&1');
|
|
40
|
+
if (!build.ok) {
|
|
41
|
+
const errorCount = (build.output.match(/error TS/g) || []).length || 1;
|
|
42
|
+
return {
|
|
43
|
+
verify: 'npm run build 2>&1 | grep -c "error TS" || echo 0',
|
|
44
|
+
errors: errorCount,
|
|
45
|
+
description: `${errorCount} TypeScript build error(s)`,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 2. Tests
|
|
50
|
+
const test = exec('npm test 2>&1');
|
|
51
|
+
if (!test.ok) {
|
|
52
|
+
const failMatch = test.output.match(/(\d+)\s+fail/i);
|
|
53
|
+
const errorCount = failMatch ? parseInt(failMatch[1], 10) : 1;
|
|
54
|
+
return {
|
|
55
|
+
verify: 'npm test 2>&1 | grep -oP "\\d+(?= fail)" || echo 0',
|
|
56
|
+
errors: errorCount,
|
|
57
|
+
description: `${errorCount} test failure(s)`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 3. Browse tests
|
|
62
|
+
const browse = exec('npx tsx autoresearch/eval-browse.ts 2>&1');
|
|
63
|
+
const scoreMatch = browse.output.match(/SCORE=(\d+)\/(\d+)/);
|
|
64
|
+
if (scoreMatch) {
|
|
65
|
+
const passed = parseInt(scoreMatch[1], 10);
|
|
66
|
+
const total = parseInt(scoreMatch[2], 10);
|
|
67
|
+
const failures = total - passed;
|
|
68
|
+
if (failures > 0) {
|
|
69
|
+
return {
|
|
70
|
+
verify: 'npx tsx autoresearch/eval-browse.ts 2>&1 | tail -1',
|
|
71
|
+
errors: failures,
|
|
72
|
+
description: `${failures} browse task failure(s) (${passed}/${total})`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return null; // all clean
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function main() {
|
|
81
|
+
const args = parseArgs(process.argv.slice(2));
|
|
82
|
+
const maxIterations = args.iterations ?? 20;
|
|
83
|
+
|
|
84
|
+
console.log('\nš§ AutoResearch Fix ā Detecting broken state...\n');
|
|
85
|
+
|
|
86
|
+
const broken = detectBrokenState();
|
|
87
|
+
if (!broken) {
|
|
88
|
+
console.log(' ā All clean ā nothing to fix!\n');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(` Found: ${broken.description}`);
|
|
93
|
+
console.log(` Verify: ${broken.verify}\n`);
|
|
94
|
+
|
|
95
|
+
const config = {
|
|
96
|
+
goal: `Fix all errors: ${broken.description}`,
|
|
97
|
+
scope: ['src/**/*.ts', 'extension/src/**/*.ts'],
|
|
98
|
+
metric: 'error_count',
|
|
99
|
+
direction: 'lower' as const,
|
|
100
|
+
verify: broken.verify,
|
|
101
|
+
guard: 'npm run build',
|
|
102
|
+
iterations: maxIterations,
|
|
103
|
+
minDelta: 1,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const logPath = join(ROOT, 'autoresearch-results.tsv');
|
|
107
|
+
const engine = new Engine(config, logPath, {
|
|
108
|
+
modify: async (ctx: ModifyContext) => {
|
|
109
|
+
const prompt = `Fix ONE error. Current error count: ${ctx.currentMetric}. Goal: 0 errors.
|
|
110
|
+
|
|
111
|
+
Read the error output, understand the root cause, and make ONE focused fix.
|
|
112
|
+
Do NOT fix multiple unrelated errors at once.
|
|
113
|
+
Do NOT modify test files.
|
|
114
|
+
|
|
115
|
+
${ctx.stuckHint ? `STUCK HINT: ${ctx.stuckHint}` : ''}`;
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const result = execSync(
|
|
119
|
+
`claude -p --dangerously-skip-permissions --allowedTools "Bash(npm:*),Bash(npx:*),Read,Edit,Write,Glob,Grep" --output-format text --no-session-persistence "${prompt.replace(/"/g, '\\"')}"`,
|
|
120
|
+
{ cwd: ROOT, timeout: 180_000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
121
|
+
).trim();
|
|
122
|
+
const lines = result.split('\n').filter(l => l.trim());
|
|
123
|
+
return lines[lines.length - 1]?.trim()?.slice(0, 120) || 'fix attempt';
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
onStatus: (msg) => console.log(msg),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const results = await engine.run();
|
|
133
|
+
const finalMetric = results[results.length - 1]?.metric ?? broken.errors;
|
|
134
|
+
if (finalMetric === 0) {
|
|
135
|
+
console.log('\nā
All errors fixed!\n');
|
|
136
|
+
} else {
|
|
137
|
+
console.log(`\nā ${finalMetric} error(s) remaining after ${maxIterations} iterations.\n`);
|
|
138
|
+
}
|
|
139
|
+
} catch (err: any) {
|
|
140
|
+
console.error(`\nā ${err.message}`);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
main();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* /autoresearch:plan ā Interactive configuration wizard.
|
|
4
|
+
*
|
|
5
|
+
* Walks through goal, scope, metric, verify, guard settings
|
|
6
|
+
* and outputs a ready-to-paste run command.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx tsx autoresearch/commands/plan.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
import { createInterface } from 'node:readline';
|
|
14
|
+
import { join, dirname } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { PRESETS } from '../presets/index.js';
|
|
17
|
+
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const ROOT = join(__dirname, '..', '..');
|
|
20
|
+
|
|
21
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
+
const ask = (q: string): Promise<string> => new Promise(r => rl.question(q, r));
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
console.log('\nš¬ AutoResearch ā Configuration Wizard\n');
|
|
26
|
+
|
|
27
|
+
// Offer presets first
|
|
28
|
+
const presetNames = Object.keys(PRESETS);
|
|
29
|
+
console.log('Available presets:');
|
|
30
|
+
presetNames.forEach((name, i) => {
|
|
31
|
+
console.log(` [${i + 1}] ${name} ā ${PRESETS[name].goal}`);
|
|
32
|
+
});
|
|
33
|
+
console.log(` [0] Custom config\n`);
|
|
34
|
+
|
|
35
|
+
const choice = await ask('Choose preset or 0 for custom: ');
|
|
36
|
+
const idx = parseInt(choice, 10);
|
|
37
|
+
|
|
38
|
+
if (idx > 0 && idx <= presetNames.length) {
|
|
39
|
+
const name = presetNames[idx - 1];
|
|
40
|
+
const iterations = await ask('Iterations (empty = unbounded): ');
|
|
41
|
+
const iterFlag = iterations ? ` --iterations ${iterations}` : '';
|
|
42
|
+
console.log(`\nā
Ready to run:\n`);
|
|
43
|
+
console.log(` npx tsx autoresearch/commands/run.ts --preset ${name}${iterFlag}\n`);
|
|
44
|
+
rl.close();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Custom config
|
|
49
|
+
const goal = await ask('Goal (what to improve): ');
|
|
50
|
+
const scope = await ask('Scope (file globs, comma-separated): ');
|
|
51
|
+
const metric = await ask('Metric name (e.g. pass_count, coverage): ');
|
|
52
|
+
const direction = await ask('Direction (higher/lower): ') as 'higher' | 'lower';
|
|
53
|
+
const verify = await ask('Verify command (must output a number): ');
|
|
54
|
+
|
|
55
|
+
// Dry-run verify
|
|
56
|
+
console.log('\n Dry-running verify command...');
|
|
57
|
+
try {
|
|
58
|
+
const output = execSync(verify, { cwd: ROOT, timeout: 120_000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
59
|
+
const { extractMetric } = await import('../config.js');
|
|
60
|
+
const value = extractMetric(output);
|
|
61
|
+
if (value != null) {
|
|
62
|
+
console.log(` ā Verify works ā current ${metric}: ${value}`);
|
|
63
|
+
} else {
|
|
64
|
+
console.log(` ā Verify ran but no number extracted from output:\n ${output.slice(0, 200)}`);
|
|
65
|
+
}
|
|
66
|
+
} catch (err: any) {
|
|
67
|
+
console.log(` ā Verify failed: ${err.message?.slice(0, 100)}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const guard = await ask('Guard command (optional, press Enter to skip): ');
|
|
71
|
+
const iterations = await ask('Iterations (empty = unbounded): ');
|
|
72
|
+
|
|
73
|
+
const parts = ['npx tsx autoresearch/commands/run.ts'];
|
|
74
|
+
parts.push(`--goal "${goal}"`);
|
|
75
|
+
parts.push(`--scope "${scope}"`);
|
|
76
|
+
parts.push(`--metric "${metric}"`);
|
|
77
|
+
parts.push(`--direction ${direction}`);
|
|
78
|
+
parts.push(`--verify "${verify}"`);
|
|
79
|
+
if (guard) parts.push(`--guard "${guard}"`);
|
|
80
|
+
if (iterations) parts.push(`--iterations ${iterations}`);
|
|
81
|
+
|
|
82
|
+
console.log(`\nā
Ready to run:\n`);
|
|
83
|
+
console.log(` ${parts.join(' \\\n ')}\n`);
|
|
84
|
+
|
|
85
|
+
rl.close();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
main();
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* /autoresearch ā Main autonomous iteration loop.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx tsx autoresearch/commands/run.ts --preset operate-reliability
|
|
7
|
+
* npx tsx autoresearch/commands/run.ts --preset operate-reliability --iterations 5
|
|
8
|
+
* npx tsx autoresearch/commands/run.ts --goal "..." --scope "src/*.ts" --verify "..." --iterations 10
|
|
9
|
+
*
|
|
10
|
+
* The modify callback spawns Claude Code to make ONE atomic change per iteration.
|
|
11
|
+
* Engine handles commit, verify, guard, keep/discard, and logging.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { execSync } from 'node:child_process';
|
|
15
|
+
import { join, dirname } from 'node:path';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { parseArgs, type AutoResearchConfig } from '../config.js';
|
|
18
|
+
import { Engine, type ModifyContext } from '../engine.js';
|
|
19
|
+
import { PRESETS } from '../presets/index.js';
|
|
20
|
+
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const ROOT = join(__dirname, '..', '..');
|
|
23
|
+
|
|
24
|
+
function buildModifyPrompt(ctx: ModifyContext, config: AutoResearchConfig): string {
|
|
25
|
+
const recent = ctx.recentLog.slice(-10).map(r =>
|
|
26
|
+
` ${r.status.padEnd(12)} ${r.description}`
|
|
27
|
+
).join('\n');
|
|
28
|
+
|
|
29
|
+
return `You are an autonomous improvement agent. Make ONE atomic change to improve this metric.
|
|
30
|
+
|
|
31
|
+
## Goal
|
|
32
|
+
${config.goal}
|
|
33
|
+
|
|
34
|
+
## Current State
|
|
35
|
+
- Metric (${config.metric}): ${ctx.currentMetric} (best: ${ctx.bestMetric})
|
|
36
|
+
- Iteration: ${ctx.iteration}
|
|
37
|
+
- Consecutive discards: ${ctx.consecutiveDiscards}
|
|
38
|
+
${ctx.stuckHint ? `\n## STUCK ā Try a Different Approach\n${ctx.stuckHint}` : ''}
|
|
39
|
+
|
|
40
|
+
## Recent History
|
|
41
|
+
${recent || ' (no history yet)'}
|
|
42
|
+
|
|
43
|
+
## Git Log (recent experiments)
|
|
44
|
+
${ctx.gitLog.split('\n').slice(0, 10).join('\n')}
|
|
45
|
+
|
|
46
|
+
## Scope (files you can modify)
|
|
47
|
+
${ctx.scopeFiles.join('\n')}
|
|
48
|
+
|
|
49
|
+
## Rules
|
|
50
|
+
1. Make ONE atomic change (one logical intent, even if multiple files)
|
|
51
|
+
2. Read the failing test output or code BEFORE modifying
|
|
52
|
+
3. DO NOT modify test files or the verify command
|
|
53
|
+
4. Describe what you changed in one sentence (no "and" linking unrelated actions)
|
|
54
|
+
5. If previous approach was discarded, try something DIFFERENT
|
|
55
|
+
6. Focus on the specific failures ā read error messages carefully`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function modify(ctx: ModifyContext, config: AutoResearchConfig): Promise<string | null> {
|
|
59
|
+
const prompt = buildModifyPrompt(ctx, config);
|
|
60
|
+
|
|
61
|
+
console.log(' Claude Code making a change...');
|
|
62
|
+
try {
|
|
63
|
+
const result = execSync(
|
|
64
|
+
`claude -p --dangerously-skip-permissions --allowedTools "Bash(npm:*),Bash(npx:*),Bash(git:*),Read,Edit,Write,Glob,Grep" --output-format text --no-session-persistence "${prompt.replace(/"/g, '\\"')}"`,
|
|
65
|
+
{
|
|
66
|
+
cwd: ROOT,
|
|
67
|
+
timeout: 300_000,
|
|
68
|
+
encoding: 'utf-8',
|
|
69
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
70
|
+
env: process.env,
|
|
71
|
+
}
|
|
72
|
+
).trim();
|
|
73
|
+
|
|
74
|
+
// Extract description from Claude's response (last non-empty line or summary)
|
|
75
|
+
const lines = result.split('\n').filter(l => l.trim());
|
|
76
|
+
const desc = lines[lines.length - 1]?.trim() || 'change made by Claude Code';
|
|
77
|
+
return desc.slice(0, 120);
|
|
78
|
+
} catch (err: any) {
|
|
79
|
+
console.error(' Claude Code failed:', err.message?.slice(0, 100));
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function main() {
|
|
85
|
+
const args = parseArgs(process.argv.slice(2));
|
|
86
|
+
|
|
87
|
+
// Resolve config from preset or CLI args
|
|
88
|
+
let config: AutoResearchConfig;
|
|
89
|
+
if (args.preset) {
|
|
90
|
+
config = PRESETS[args.preset];
|
|
91
|
+
if (!config) {
|
|
92
|
+
console.error(`Unknown preset: ${args.preset}`);
|
|
93
|
+
console.error(`Available: ${Object.keys(PRESETS).join(', ')}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
// Allow CLI overrides
|
|
97
|
+
if (args.iterations != null) config = { ...config, iterations: args.iterations };
|
|
98
|
+
if (args.guard != null) config = { ...config, guard: args.guard };
|
|
99
|
+
} else if (args.goal && args.verify) {
|
|
100
|
+
config = {
|
|
101
|
+
goal: args.goal,
|
|
102
|
+
scope: args.scope ?? ['src/**/*.ts'],
|
|
103
|
+
metric: args.metric ?? 'score',
|
|
104
|
+
direction: args.direction ?? 'higher',
|
|
105
|
+
verify: args.verify,
|
|
106
|
+
guard: args.guard,
|
|
107
|
+
iterations: args.iterations,
|
|
108
|
+
minDelta: args.minDelta,
|
|
109
|
+
};
|
|
110
|
+
} else {
|
|
111
|
+
console.error('Usage: npx tsx autoresearch/commands/run.ts --preset <name> [--iterations N]');
|
|
112
|
+
console.error(' or: npx tsx autoresearch/commands/run.ts --goal "..." --verify "..." --scope "..."');
|
|
113
|
+
console.error(`\nAvailable presets: ${Object.keys(PRESETS).join(', ')}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(`\nš¬ AutoResearch: ${config.goal}`);
|
|
118
|
+
console.log(` Metric: ${config.metric} (${config.direction})`);
|
|
119
|
+
console.log(` Verify: ${config.verify}`);
|
|
120
|
+
console.log(` Guard: ${config.guard ?? '(none)'}`);
|
|
121
|
+
console.log(` Iterations: ${config.iterations ?? 'ā'}`);
|
|
122
|
+
console.log('');
|
|
123
|
+
|
|
124
|
+
const logPath = join(ROOT, 'autoresearch-results.tsv');
|
|
125
|
+
const engine = new Engine(config, logPath, {
|
|
126
|
+
modify: (ctx) => modify(ctx, config),
|
|
127
|
+
onStatus: (msg) => console.log(msg),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
await engine.run();
|
|
132
|
+
} catch (err: any) {
|
|
133
|
+
console.error(`\nā ${err.message}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
main();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AutoResearch Configuration ā type definitions and CLI parsing.
|
|
3
|
+
*
|
|
4
|
+
* Based on Karpathy's autoresearch: constraint + mechanical metric + unbounded loop.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface AutoResearchConfig {
|
|
8
|
+
/** Plain-language goal, e.g. "Increase operate pass rate to 59/59" */
|
|
9
|
+
goal: string;
|
|
10
|
+
/** Glob patterns for files the agent can modify */
|
|
11
|
+
scope: string[];
|
|
12
|
+
/** What the metric measures, e.g. "pass_count" */
|
|
13
|
+
metric: string;
|
|
14
|
+
/** Whether improvement means the number goes up or down */
|
|
15
|
+
direction: 'higher' | 'lower';
|
|
16
|
+
/** Shell command that outputs a number (the metric value) */
|
|
17
|
+
verify: string;
|
|
18
|
+
/** Optional guard command ā must pass for a keep decision */
|
|
19
|
+
guard?: string;
|
|
20
|
+
/** Max iterations (undefined = unbounded) */
|
|
21
|
+
iterations?: number;
|
|
22
|
+
/** Minimum delta to count as real improvement (noise filter) */
|
|
23
|
+
minDelta?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type IterationStatus =
|
|
27
|
+
| 'baseline'
|
|
28
|
+
| 'keep'
|
|
29
|
+
| 'keep (reworked)'
|
|
30
|
+
| 'discard'
|
|
31
|
+
| 'crash'
|
|
32
|
+
| 'no-op'
|
|
33
|
+
| 'hook-blocked';
|
|
34
|
+
|
|
35
|
+
export interface IterationResult {
|
|
36
|
+
iteration: number;
|
|
37
|
+
commit: string;
|
|
38
|
+
metric: number;
|
|
39
|
+
delta: number;
|
|
40
|
+
guard: 'pass' | 'fail' | '-';
|
|
41
|
+
status: IterationStatus;
|
|
42
|
+
description: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Parse CLI args into a partial config (missing fields filled by preset or prompts) */
|
|
46
|
+
export function parseArgs(argv: string[]): Partial<AutoResearchConfig> & { preset?: string; task?: string } {
|
|
47
|
+
const config: Partial<AutoResearchConfig> & { preset?: string; task?: string } = {};
|
|
48
|
+
for (let i = 0; i < argv.length; i++) {
|
|
49
|
+
const arg = argv[i];
|
|
50
|
+
const next = argv[i + 1];
|
|
51
|
+
switch (arg) {
|
|
52
|
+
case '--preset': config.preset = next; i++; break;
|
|
53
|
+
case '--goal': config.goal = next; i++; break;
|
|
54
|
+
case '--scope': config.scope = next?.split(','); i++; break;
|
|
55
|
+
case '--metric': config.metric = next; i++; break;
|
|
56
|
+
case '--direction': config.direction = next as 'higher' | 'lower'; i++; break;
|
|
57
|
+
case '--verify': config.verify = next; i++; break;
|
|
58
|
+
case '--guard': config.guard = next; i++; break;
|
|
59
|
+
case '--iterations': config.iterations = parseInt(next, 10); i++; break;
|
|
60
|
+
case '--min-delta': config.minDelta = parseFloat(next); i++; break;
|
|
61
|
+
case '--task': config.task = next; i++; break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return config;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Extract a number from command output using common patterns */
|
|
68
|
+
export function extractMetric(output: string): number | null {
|
|
69
|
+
// Try: last line that looks like a number
|
|
70
|
+
const lines = output.trim().split('\n');
|
|
71
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
72
|
+
const line = lines[i].trim();
|
|
73
|
+
// Match standalone numbers: "56", "95.2", "SCORE=56/59" ā 56
|
|
74
|
+
const scoreMatch = line.match(/SCORE[=:]\s*(\d+)/i);
|
|
75
|
+
if (scoreMatch) return parseFloat(scoreMatch[1]);
|
|
76
|
+
const numMatch = line.match(/^[\d.]+$/);
|
|
77
|
+
if (numMatch) return parseFloat(numMatch[0]);
|
|
78
|
+
}
|
|
79
|
+
// Fallback: first number in output
|
|
80
|
+
const fallback = output.match(/(\d+(?:\.\d+)?)/);
|
|
81
|
+
return fallback ? parseFloat(fallback[1]) : null;
|
|
82
|
+
}
|