@ozerohax/assistagents 0.1.1
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/README.md +90 -0
- package/dist/cli.js +442 -0
- package/dist/cli.js.map +1 -0
- package/package.json +39 -0
- package/templates/agents/ask/ask.md +126 -0
- package/templates/agents/assist/research/code-research.md +116 -0
- package/templates/agents/assist/research/web-research.md +105 -0
- package/templates/agents/build/dev.md +131 -0
- package/templates/agents/planning/plan.md +103 -0
- package/templates/agents/planning/project.md +122 -0
- package/templates/agents/review/reviewer.md +121 -0
- package/templates/agents/test/tester.md +170 -0
- package/templates/skills/coder/csharp/coder-csharp-aspnetcore-api/SKILL.md +352 -0
- package/templates/skills/coder/csharp/coder-csharp-async-concurrency/SKILL.md +63 -0
- package/templates/skills/coder/csharp/coder-csharp-conventions/SKILL.md +405 -0
- package/templates/skills/coder/csharp/coder-csharp-efcore-config/SKILL.md +384 -0
- package/templates/skills/coder/csharp/coder-csharp-efcore-queries/SKILL.md +434 -0
- package/templates/skills/coder/csharp/coder-csharp-error-handling/SKILL.md +354 -0
- package/templates/skills/coder/csharp/coder-csharp-logging/SKILL.md +362 -0
- package/templates/skills/coder/csharp/coder-csharp-performance/SKILL.md +71 -0
- package/templates/skills/coder/csharp/coder-csharp-security/SKILL.md +57 -0
- package/templates/skills/coder/csharp/coder-csharp-testing/SKILL.md +398 -0
- package/templates/skills/coder/rust/coder-rust-async-concurrency/SKILL.md +63 -0
- package/templates/skills/coder/rust/coder-rust-axum-api/SKILL.md +104 -0
- package/templates/skills/coder/rust/coder-rust-conventions/SKILL.md +57 -0
- package/templates/skills/coder/rust/coder-rust-error-handling/SKILL.md +56 -0
- package/templates/skills/coder/rust/coder-rust-logging/SKILL.md +69 -0
- package/templates/skills/coder/rust/coder-rust-performance/SKILL.md +51 -0
- package/templates/skills/coder/rust/coder-rust-security/SKILL.md +49 -0
- package/templates/skills/coder/rust/coder-rust-serde/SKILL.md +61 -0
- package/templates/skills/coder/rust/coder-rust-sqlx-config/SKILL.md +54 -0
- package/templates/skills/coder/rust/coder-rust-sqlx-queries/SKILL.md +66 -0
- package/templates/skills/coder/rust/coder-rust-testing/SKILL.md +52 -0
- package/templates/skills/coder/rust/coder-rust-tokio/SKILL.md +69 -0
- package/templates/skills/coder/rust/coder-rust-tower-http/SKILL.md +56 -0
- package/templates/skills/coder/typescript/coder-typescript-async-concurrency/SKILL.md +73 -0
- package/templates/skills/coder/typescript/coder-typescript-conventions/SKILL.md +156 -0
- package/templates/skills/coder/typescript/coder-typescript-error-handling/SKILL.md +126 -0
- package/templates/skills/coder/typescript/coder-typescript-logging/SKILL.md +107 -0
- package/templates/skills/coder/typescript/coder-typescript-performance/SKILL.md +83 -0
- package/templates/skills/coder/typescript/coder-typescript-security/SKILL.md +78 -0
- package/templates/skills/coder/typescript/coder-typescript-testing/SKILL.md +111 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-composables/SKILL.md +57 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-core/SKILL.md +65 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-performance/SKILL.md +48 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-primevue/SKILL.md +140 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-primevue/scripts/primevue-docs.js +502 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-reactivity/SKILL.md +61 -0
- package/templates/skills/coder/typescript/coder-typescript-vuejs/coder-typescript-vuejs-testing/SKILL.md +63 -0
- package/templates/skills/planning/code/planning-code-feature/SKILL.md +148 -0
- package/templates/skills/planning/code/planning-code-fix/SKILL.md +207 -0
- package/templates/skills/planning/project/fast/planning-project-fast-delivery-deploy/SKILL.md +33 -0
- package/templates/skills/planning/project/fast/planning-project-fast-intake-questions/SKILL.md +82 -0
- package/templates/skills/planning/project/fast/planning-project-fast-mvp-slicing/SKILL.md +36 -0
- package/templates/skills/planning/project/fast/planning-project-fast-output-template/SKILL.md +79 -0
- package/templates/skills/planning/project/fast/planning-project-fast-session-plan/SKILL.md +30 -0
- package/templates/skills/planning/project/shared/planning-project-acceptance-criteria/SKILL.md +46 -0
- package/templates/skills/planning/project/shared/planning-project-artifact-storage/SKILL.md +68 -0
- package/templates/skills/planning/project/shared/planning-project-checkpoints-approval/SKILL.md +24 -0
- package/templates/skills/planning/project/shared/planning-project-save-revision/SKILL.md +33 -0
- package/templates/skills/planning/project/shared/planning-project-scope-control/SKILL.md +30 -0
- package/templates/skills/planning/project/shared/planning-project-success-metric-scope/SKILL.md +41 -0
- package/templates/skills/planning/project/shared/planning-project-task-decomposition/SKILL.md +47 -0
- package/templates/skills/planning/project/shared/planning-project-testing-validation/SKILL.md +31 -0
- package/templates/skills/planning/project/standard/planning-project-standard-architecture-adr-lite/SKILL.md +68 -0
- package/templates/skills/planning/project/standard/planning-project-standard-epics-stories/SKILL.md +41 -0
- package/templates/skills/planning/project/standard/planning-project-standard-implementation-readiness-lite/SKILL.md +32 -0
- package/templates/skills/planning/project/standard/planning-project-standard-prd-fr-nfr/SKILL.md +60 -0
- package/templates/skills/planning/project/standard/planning-project-standard-product-brief/SKILL.md +46 -0
- package/templates/skills/planning/project/standard/planning-project-standard-story-to-tasks/SKILL.md +52 -0
- package/templates/skills/research-strategy/research-strategy-code/SKILL.md +80 -0
- package/templates/skills/research-strategy/research-strategy-web/SKILL.md +63 -0
- package/templates/skills/review/review-checklists/SKILL.md +57 -0
- package/templates/skills/review/review-requirements/SKILL.md +42 -0
- package/templates/skills/review/review-strategy/SKILL.md +120 -0
- package/templates/skills/testing/testing-api-manual/SKILL.md +535 -0
- package/templates/skills/testing/testing-automation-web/SKILL.md +81 -0
- package/templates/skills/testing/testing-browser-manual/SKILL.md +528 -0
- package/templates/skills/testing/testing-checklists/SKILL.md +434 -0
- package/templates/skills/testing/testing-strategy/SKILL.md +471 -0
- package/templates/skills/testing/testing-test-cases/SKILL.md +362 -0
- package/templates/skills/testing/testing-triage-bugs/SKILL.md +457 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
|
|
2
|
+
Набор готовых агентов и skills для AI‑ассистированного кодинга в OpenCode: от быстрого “вайб‑прототипа” до аккуратной реализации с планированием, тестированием и ревью.
|
|
3
|
+
Пак ставит в `~/.opencode/`:
|
|
4
|
+
- `agents/` — роли (build/dev, planning, review, test, ask + research сабагенты)
|
|
5
|
+
- `skills/` — плейбуки (planning/review/testing/research + coder skills по языкам)
|
|
6
|
+
- `keys/` и `opencode.jsonc` — хранение ключей и глобальная конфигурация
|
|
7
|
+
Главная идея: ты говоришь “что хочу получить”, агент ведёт процесс и соблюдает правила (skills/Context7/LSP), а ты управляешь итерациями “сгенерировал → запустил → исправил → проверил”.
|
|
8
|
+
|
|
9
|
+
ВАЖНО! Это первая тестовая версия, если что не так - welcome to issue/pr
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Quickstart
|
|
13
|
+
1) Скажи цель одной фразой: что сделать + ограничения + критерий готовности.
|
|
14
|
+
2) Запусти `planning/plan`: попроси краткий план, список файлов, риски, что тестировать.
|
|
15
|
+
3) Запусти `build/dev`: реализуй по плану, минимальными правками, без "улучшим всё рядом".
|
|
16
|
+
4) Запусти проект/сборку локально, собери ошибку/лог.
|
|
17
|
+
5) Вставь ошибку в `build/dev` (как есть) и попроси минимальный фикс + объяснение причины.
|
|
18
|
+
6) Запусти `test/tester`: попроси покрыть критерии готовности тестами/ручной проверкой.
|
|
19
|
+
7) Запусти `review/reviewer`: пусть проверит correctness/security/tests и точки риска.
|
|
20
|
+
8) Повтори короткий круг `build/dev → test → review`, пока не станет скучно.
|
|
21
|
+
|
|
22
|
+
### Как выбирать агента (в двух словах)
|
|
23
|
+
- `build/dev` — когда нужно менять код (фича/фикс/рефактор).
|
|
24
|
+
- `planning/plan` — когда задача мутная или хочется минимизировать риск до правок.
|
|
25
|
+
- `review/reviewer` — когда уже есть изменения и нужно понять, не наделали ли бед.
|
|
26
|
+
- `test/tester` — когда нужно проверить руками/через API/браузер, особенно после правок.
|
|
27
|
+
- `ask/ask` — когда вопрос “где/что/почему” и хочется быстрый ответ или делегацию в research.
|
|
28
|
+
|
|
29
|
+
### Важно: чего не делать
|
|
30
|
+
- Не вставляй ключи/токены/PII в промпты и в репозиторий; ключи храни в `~/.opencode/keys/`.
|
|
31
|
+
- Не тащи "Accept All" в прод: для high-risk зон (auth/payments/migrations/public APIs/deploy) всегда `planning → build/dev → test → review`.
|
|
32
|
+
- Context7 используй для публичной документации/примеров, а не для секретов.
|
|
33
|
+
- LSP/диагностику не игнорируй: тип-ошибки/линт/тесты должны давать чистый сигнал.
|
|
34
|
+
|
|
35
|
+
## Установка / обновление
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx @ozerohax/assistagents
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
В TUI ты:
|
|
42
|
+
|
|
43
|
+
- выбираешь языки, для которых поставить `skills/coder/*` (сейчас: TypeScript, Rust, C#)
|
|
44
|
+
- решаешь, делать ли zip-бэкап и куда его класть
|
|
45
|
+
- вводишь/обновляешь ключи (если файл ключа пустой — спросит; если заполнен — спросит, оставить или перезаписать)
|
|
46
|
+
|
|
47
|
+
## Что именно устанавливается
|
|
48
|
+
|
|
49
|
+
Целевая структура:
|
|
50
|
+
|
|
51
|
+
- `~/.opencode/agents/` — описания агентов (YAML frontmatter + инструкции)
|
|
52
|
+
- `~/.opencode/skills/` — skills (структурированные инструкции/плейбуки)
|
|
53
|
+
- `~/.opencode/keys/` — ключи провайдеров (файлы)
|
|
54
|
+
- `~/.opencode/opencode.jsonc` — глобальный конфиг OpenCode, который ссылается на ключи
|
|
55
|
+
|
|
56
|
+
## Агенты (templates/agents)
|
|
57
|
+
|
|
58
|
+
В пакете есть несколько «ролей», которые подхватываются OpenCode:
|
|
59
|
+
|
|
60
|
+
- `build/dev` — основной агент для имплементации/рефакторинга/фиксов. Перед правками обязан подгружать релевантные skills, при необходимости пользоваться Context7 (внешние либы) и LSP (переходы/диагностика). Для неоднозначностей — вопросы только через `question` tool.
|
|
61
|
+
- `planning/plan` — планировщик (read-only): исследует репозиторий и пишет план, но не редактирует файлы. План может сохранять только в `/ai-docs/plan/` и только после явного подтверждения.
|
|
62
|
+
- `planning/project` — планировщик проекта (read-only): делает быстрый/стандартный план по продукту/проекту; если режим не задан — обязан спросить через `question` tool.
|
|
63
|
+
- `review/reviewer` — агент код-ревью: работает по review skills (стратегия + чеклисты), фокус на correctness/security/tests, формат «Conventional Comments».
|
|
64
|
+
- `test/tester` — агент ручного тестирования API/браузера: собирает сценарии, использует curl/DevTools (в зависимости от окружения) и документирует результаты; опасные запросы/действия подтверждает через `question` tool.
|
|
65
|
+
- `ask/ask` — «спроси о чём угодно»: отвечает на вопросы, выбирая режим (свои инструменты vs сабагенты). Для сложного анализа может делегировать в `assist/research/*`.
|
|
66
|
+
- `assist/research/code-research` — сабагент для локального code research (read-only, с обязательной доказательной базой: файлы/строки).
|
|
67
|
+
- `assist/research/web-research` — сабагент для web research (поиск/выгрузка страниц; подходит для актуальной документации/примеров).
|
|
68
|
+
|
|
69
|
+
## Skills (templates/skills)
|
|
70
|
+
|
|
71
|
+
Skills — это «плейбуки», которые агенты обязаны подгружать перед работой (в зависимости от задачи).
|
|
72
|
+
|
|
73
|
+
Основные группы:
|
|
74
|
+
|
|
75
|
+
- `planning/code/*` — планирование изменений в коде:
|
|
76
|
+
- `planning-code-feature` — как планировать фичи (scope → декомпозиция → план реализации/тестирования)
|
|
77
|
+
- `planning-code-fix` — как планировать фиксы/баги (repro → root cause → безопасный fix + тест-план)
|
|
78
|
+
- `planning/project/*` — планирование проекта/продукта (fast/standard + общие shared навыки)
|
|
79
|
+
- `research-strategy/*` — как правильно формулировать запросы для сабагентов:
|
|
80
|
+
- `research-strategy-code`
|
|
81
|
+
- `research-strategy-web`
|
|
82
|
+
- `review/*` — стратегия ревью и чеклисты:
|
|
83
|
+
- `review-strategy`, `review-checklists`, `review-requirements`
|
|
84
|
+
- `testing/*` — стратегия ручного тестирования, чеклисты, тест-кейсы, triage багов, web-автоматизация
|
|
85
|
+
- `coder/<language>/*` — языковые best practices (ставятся по выбору в TUI):
|
|
86
|
+
- TypeScript: conventions, error-handling, logging, performance, security, async, testing + Vue 3 skills (core/reactivity/composables/performance/testing/PrimeVue)
|
|
87
|
+
- Rust: conventions, error-handling, logging, performance, security, async, testing + Axum/Tokio/Serde/SQLx/Tower
|
|
88
|
+
- C#: conventions, error-handling, logging, performance, security, async, testing + ASP.NET Core API + EF Core
|
|
89
|
+
|
|
90
|
+
Примечание: если под конкретную технологию skill отсутствует, агент должен явно это отметить и действовать осторожно.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { checkbox, confirm, input, password } from "@inquirer/prompts";
|
|
5
|
+
import path4 from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import fs3 from "fs/promises";
|
|
8
|
+
import fsSync from "fs";
|
|
9
|
+
|
|
10
|
+
// src/config-template.ts
|
|
11
|
+
function renderGlobalConfigJsonc(keyFiles) {
|
|
12
|
+
return `{
|
|
13
|
+
"$schema": "https://opencode.ai/config.json",
|
|
14
|
+
"keybinds": {
|
|
15
|
+
"messages_undo": "ctrl+alt+z",
|
|
16
|
+
"session_new": "ctrl+alt+n",
|
|
17
|
+
"command_list": "ctrl+alt+p"
|
|
18
|
+
},
|
|
19
|
+
"watcher": {
|
|
20
|
+
"ignore": [
|
|
21
|
+
"node_modules/**",
|
|
22
|
+
"dist/**",
|
|
23
|
+
".git/**",
|
|
24
|
+
".venv/**",
|
|
25
|
+
"bin/**"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"permission": {
|
|
29
|
+
"bash": "deny",
|
|
30
|
+
"codesearch": "deny",
|
|
31
|
+
"doom_loop": "deny",
|
|
32
|
+
"edit": "deny",
|
|
33
|
+
"external_directory": "deny",
|
|
34
|
+
"glob": "deny",
|
|
35
|
+
"grep": "deny",
|
|
36
|
+
"list": "deny",
|
|
37
|
+
"lsp": "deny",
|
|
38
|
+
"question": "deny",
|
|
39
|
+
"read": "deny",
|
|
40
|
+
"task": "deny",
|
|
41
|
+
"todoread": "deny",
|
|
42
|
+
"todowrite": "deny",
|
|
43
|
+
"webfetch": "deny",
|
|
44
|
+
"websearch": "deny",
|
|
45
|
+
"skill": "deny",
|
|
46
|
+
// MCP
|
|
47
|
+
"tavily-search*": "deny",
|
|
48
|
+
"ddg-search*": "deny",
|
|
49
|
+
"zai-web-search*": "deny",
|
|
50
|
+
"zai-web-reader*": "deny",
|
|
51
|
+
"context7*": "deny",
|
|
52
|
+
"github-grep*": "deny",
|
|
53
|
+
"pencil": "deny"
|
|
54
|
+
},
|
|
55
|
+
"agent": {
|
|
56
|
+
"explore": { "disable": true },
|
|
57
|
+
"build": { "disable": true },
|
|
58
|
+
"plan": { "disable": true }
|
|
59
|
+
},
|
|
60
|
+
"mcp": {
|
|
61
|
+
"tavily-search": {
|
|
62
|
+
"type": "remote",
|
|
63
|
+
"url": "https://mcp.tavily.com/mcp/?tavilyApiKey={file:${keyFiles.tavily}}"
|
|
64
|
+
},
|
|
65
|
+
"ddg-search": {
|
|
66
|
+
"type": "local",
|
|
67
|
+
"command": ["uvx", "duckduckgo-mcp-server"]
|
|
68
|
+
},
|
|
69
|
+
"zai-web-search": {
|
|
70
|
+
"type": "remote",
|
|
71
|
+
"url": "https://api.z.ai/api/mcp/web_search_prime/mcp",
|
|
72
|
+
"headers": {
|
|
73
|
+
"Authorization": "Bearer {file:${keyFiles.zaiApi}}"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"zai-web-reader": {
|
|
77
|
+
"type": "remote",
|
|
78
|
+
"url": "https://api.z.ai/api/mcp/web_reader/mcp",
|
|
79
|
+
"headers": {
|
|
80
|
+
"Authorization": "Bearer {file:${keyFiles.zaiApi}}"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"context7": {
|
|
84
|
+
"type": "remote",
|
|
85
|
+
"url": "https://mcp.context7.com/mcp",
|
|
86
|
+
"headers": {
|
|
87
|
+
"CONTEXT7_API_KEY": "{file:${keyFiles.context7}}"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"github-grep": {
|
|
91
|
+
"type": "remote",
|
|
92
|
+
"url": "https://mcp.grep.app"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/backup.ts
|
|
100
|
+
import fs2 from "fs";
|
|
101
|
+
import fsp from "fs/promises";
|
|
102
|
+
import path2 from "path";
|
|
103
|
+
import archiver from "archiver";
|
|
104
|
+
|
|
105
|
+
// src/fs-utils.ts
|
|
106
|
+
import fs from "fs/promises";
|
|
107
|
+
import path from "path";
|
|
108
|
+
async function pathExists(p) {
|
|
109
|
+
try {
|
|
110
|
+
await fs.stat(p);
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function ensureDir(dir) {
|
|
117
|
+
await fs.mkdir(dir, { recursive: true });
|
|
118
|
+
}
|
|
119
|
+
async function isNonEmptyDir(dir) {
|
|
120
|
+
try {
|
|
121
|
+
const entries = await fs.readdir(dir);
|
|
122
|
+
return entries.length > 0;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function removeIfExists(p) {
|
|
128
|
+
if (!await pathExists(p)) return;
|
|
129
|
+
await fs.rm(p, { recursive: true, force: true });
|
|
130
|
+
}
|
|
131
|
+
async function copyDir(src, dest) {
|
|
132
|
+
await ensureDir(path.dirname(dest));
|
|
133
|
+
await fs.cp(src, dest, {
|
|
134
|
+
recursive: true,
|
|
135
|
+
force: true,
|
|
136
|
+
errorOnExist: false,
|
|
137
|
+
dereference: true
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async function writeFileAtomic(filePath, content) {
|
|
141
|
+
const dir = path.dirname(filePath);
|
|
142
|
+
await ensureDir(dir);
|
|
143
|
+
const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
144
|
+
await fs.writeFile(tmpPath, content, { encoding: "utf8" });
|
|
145
|
+
await fs.rename(tmpPath, filePath);
|
|
146
|
+
}
|
|
147
|
+
async function chmod600IfPossible(filePath) {
|
|
148
|
+
try {
|
|
149
|
+
await fs.chmod(filePath, 384);
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/backup.ts
|
|
155
|
+
function timestampForFilename(date = /* @__PURE__ */ new Date()) {
|
|
156
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
157
|
+
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
|
158
|
+
}
|
|
159
|
+
async function zipDirectory(opts) {
|
|
160
|
+
const { sourceDir, backupDir } = opts;
|
|
161
|
+
const prefix = opts.prefix ?? "opencode-backup";
|
|
162
|
+
await ensureDir(backupDir);
|
|
163
|
+
const zipPath = path2.join(backupDir, `${prefix}-${timestampForFilename()}.zip`);
|
|
164
|
+
await new Promise((resolve, reject) => {
|
|
165
|
+
const output = fs2.createWriteStream(zipPath);
|
|
166
|
+
const archive = archiver("zip", { zlib: { level: 9 } });
|
|
167
|
+
output.on("close", () => resolve());
|
|
168
|
+
output.on("error", (err) => reject(err));
|
|
169
|
+
archive.on("warning", (err) => {
|
|
170
|
+
if (err.code === "ENOENT") return;
|
|
171
|
+
reject(err);
|
|
172
|
+
});
|
|
173
|
+
archive.on("error", (err) => reject(err));
|
|
174
|
+
archive.pipe(output);
|
|
175
|
+
archive.directory(sourceDir, false);
|
|
176
|
+
void archive.finalize();
|
|
177
|
+
});
|
|
178
|
+
await fsp.stat(zipPath);
|
|
179
|
+
return zipPath;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/paths.ts
|
|
183
|
+
import os from "os";
|
|
184
|
+
import path3 from "path";
|
|
185
|
+
function getInstallPaths(backupDirFlag) {
|
|
186
|
+
const home = os.homedir();
|
|
187
|
+
const targetRoot = path3.join(home, ".opencode");
|
|
188
|
+
const expandTilde = (p) => {
|
|
189
|
+
if (p === "~") return home;
|
|
190
|
+
if (p.startsWith("~/") || p.startsWith("~\\")) return path3.join(home, p.slice(2));
|
|
191
|
+
return p;
|
|
192
|
+
};
|
|
193
|
+
const backupDir = backupDirFlag ? expandTilde(backupDirFlag) : path3.join(home, ".opencode-backups");
|
|
194
|
+
return {
|
|
195
|
+
targetRoot,
|
|
196
|
+
targetAgents: path3.join(targetRoot, "agents"),
|
|
197
|
+
targetSkills: path3.join(targetRoot, "skills"),
|
|
198
|
+
targetKeys: path3.join(targetRoot, "keys"),
|
|
199
|
+
globalConfig: path3.join(targetRoot, "opencode.jsonc"),
|
|
200
|
+
backupDir
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
function getKeyFileRefs() {
|
|
204
|
+
return {
|
|
205
|
+
zaiApi: "~/.opencode/keys/zai_api.txt",
|
|
206
|
+
context7: "~/.opencode/keys/context7.txt",
|
|
207
|
+
tavily: "~/.opencode/keys/tavily_search.txt"
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/skill-selection.ts
|
|
212
|
+
var ALL_LANGUAGES = ["typescript", "rust", "csharp"];
|
|
213
|
+
function buildSkillCopyPlan(languages) {
|
|
214
|
+
const relDirs = [];
|
|
215
|
+
relDirs.push("planning");
|
|
216
|
+
relDirs.push("review");
|
|
217
|
+
relDirs.push("testing");
|
|
218
|
+
relDirs.push("research-strategy");
|
|
219
|
+
const uniq = new Set(languages);
|
|
220
|
+
for (const lang of ALL_LANGUAGES) {
|
|
221
|
+
if (uniq.has(lang)) {
|
|
222
|
+
relDirs.push(`coder/${lang}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { relDirs };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/cli.ts
|
|
229
|
+
function getTemplatesRoot() {
|
|
230
|
+
const here = path4.dirname(fileURLToPath(import.meta.url));
|
|
231
|
+
return path4.resolve(here, "..", "templates");
|
|
232
|
+
}
|
|
233
|
+
async function writeKeyFile(filePath, value, dryRun) {
|
|
234
|
+
const exists = await pathExists(filePath);
|
|
235
|
+
if (value !== void 0) {
|
|
236
|
+
if (!dryRun) {
|
|
237
|
+
await writeFileAtomic(filePath, `${value}
|
|
238
|
+
`);
|
|
239
|
+
await chmod600IfPossible(filePath);
|
|
240
|
+
}
|
|
241
|
+
return { action: "written" };
|
|
242
|
+
}
|
|
243
|
+
if (exists) return { action: "kept" };
|
|
244
|
+
if (!dryRun) {
|
|
245
|
+
await writeFileAtomic(filePath, "");
|
|
246
|
+
await chmod600IfPossible(filePath);
|
|
247
|
+
}
|
|
248
|
+
return { action: "created-empty" };
|
|
249
|
+
}
|
|
250
|
+
function tryGetPromptContext() {
|
|
251
|
+
const ttyOk = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
252
|
+
if (ttyOk) return void 0;
|
|
253
|
+
if (process.platform !== "win32") {
|
|
254
|
+
try {
|
|
255
|
+
const inFd = fsSync.openSync("/dev/tty", "r");
|
|
256
|
+
const outFd = fsSync.openSync("/dev/tty", "w");
|
|
257
|
+
const input2 = fsSync.createReadStream("", { fd: inFd, autoClose: true });
|
|
258
|
+
const output = fsSync.createWriteStream("", { fd: outFd, autoClose: true });
|
|
259
|
+
input2.on("error", () => {
|
|
260
|
+
});
|
|
261
|
+
output.on("error", () => {
|
|
262
|
+
});
|
|
263
|
+
return { input: input2, output };
|
|
264
|
+
} catch {
|
|
265
|
+
return void 0;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
async function isKeyFileFilled(filePath) {
|
|
271
|
+
try {
|
|
272
|
+
const content = (await fs3.readFile(filePath, "utf8")).trim();
|
|
273
|
+
return content.length > 0;
|
|
274
|
+
} catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async function promptForMissingKeys(opts) {
|
|
279
|
+
const promptCtx = tryGetPromptContext();
|
|
280
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) || Boolean(promptCtx);
|
|
281
|
+
if (!interactive) {
|
|
282
|
+
throw new Error("\u041D\u0435\u0442 TTY \u0434\u043B\u044F \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0433\u043E \u0440\u0435\u0436\u0438\u043C\u0430 (\u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u0438\u0437 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0430).");
|
|
283
|
+
}
|
|
284
|
+
const askOne = async (label, filePath) => {
|
|
285
|
+
const filled = await isKeyFileFilled(filePath);
|
|
286
|
+
if (filled) {
|
|
287
|
+
const keep = await confirm(
|
|
288
|
+
{ message: `${label}: \u0444\u0430\u0439\u043B \u0443\u0436\u0435 \u0437\u0430\u043F\u043E\u043B\u043D\u0435\u043D (${filePath}). \u041E\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u043A\u0430\u043A \u0435\u0441\u0442\u044C?`, default: true },
|
|
289
|
+
promptCtx
|
|
290
|
+
);
|
|
291
|
+
if (keep) return void 0;
|
|
292
|
+
}
|
|
293
|
+
const value = await password(
|
|
294
|
+
{
|
|
295
|
+
message: `\u0412\u0432\u0435\u0434\u0438\u0442\u0435 ${label} (\u0441\u043E\u0445\u0440\u0430\u043D\u0438\u043C \u0432 ${filePath})`,
|
|
296
|
+
mask: "*",
|
|
297
|
+
validate: (v) => v.trim().length > 0 ? true : "\u041D\u0443\u0436\u043D\u043E \u043D\u0435\u043F\u0443\u0441\u0442\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435"
|
|
298
|
+
},
|
|
299
|
+
promptCtx
|
|
300
|
+
);
|
|
301
|
+
return value;
|
|
302
|
+
};
|
|
303
|
+
const out = {};
|
|
304
|
+
const zaiApi = await askOne("ZAI_API_KEY", opts.keyFiles.zaiApi);
|
|
305
|
+
if (zaiApi !== void 0) out.zaiApi = zaiApi;
|
|
306
|
+
const context7 = await askOne("CONTEXT7_API_KEY", opts.keyFiles.context7);
|
|
307
|
+
if (context7 !== void 0) out.context7 = context7;
|
|
308
|
+
const tavily = await askOne("TAVILY_API_KEY", opts.keyFiles.tavily);
|
|
309
|
+
if (tavily !== void 0) out.tavily = tavily;
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
async function promptLanguages() {
|
|
313
|
+
const promptCtx = tryGetPromptContext();
|
|
314
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) || Boolean(promptCtx);
|
|
315
|
+
if (!interactive) {
|
|
316
|
+
throw new Error("\u041D\u0435\u0442 TTY \u0434\u043B\u044F \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0433\u043E \u0440\u0435\u0436\u0438\u043C\u0430 (\u0437\u0430\u043F\u0443\u0441\u0442\u0438 \u0438\u0437 \u0442\u0435\u0440\u043C\u0438\u043D\u0430\u043B\u0430).");
|
|
317
|
+
}
|
|
318
|
+
const selected = await checkbox(
|
|
319
|
+
{
|
|
320
|
+
message: "\u041A\u0430\u043A\u0438\u0435 \u044F\u0437\u044B\u043A\u0438 \u0434\u043E\u0431\u0430\u0432\u0438\u0442\u044C?",
|
|
321
|
+
choices: [
|
|
322
|
+
{ name: "TypeScript", value: "typescript", checked: true },
|
|
323
|
+
{ name: "Rust", value: "rust", checked: true },
|
|
324
|
+
{ name: "C#", value: "csharp", checked: true }
|
|
325
|
+
],
|
|
326
|
+
validate: (values) => values.length > 0 ? true : "\u0412\u044B\u0431\u0435\u0440\u0438 \u0445\u043E\u0442\u044F \u0431\u044B \u043E\u0434\u0438\u043D \u044F\u0437\u044B\u043A"
|
|
327
|
+
},
|
|
328
|
+
promptCtx
|
|
329
|
+
);
|
|
330
|
+
return selected;
|
|
331
|
+
}
|
|
332
|
+
async function backupExistingOpencode(paths, dryRun) {
|
|
333
|
+
const nonEmpty = await isNonEmptyDir(paths.targetRoot);
|
|
334
|
+
if (!nonEmpty) return void 0;
|
|
335
|
+
if (dryRun) return path4.join(paths.backupDir, "opencode-backup-YYYYMMDD-HHmmss.zip");
|
|
336
|
+
return await zipDirectory({ sourceDir: paths.targetRoot, backupDir: paths.backupDir });
|
|
337
|
+
}
|
|
338
|
+
function timestampForBak(date = /* @__PURE__ */ new Date()) {
|
|
339
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
340
|
+
return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
|
|
341
|
+
}
|
|
342
|
+
async function maybeBackupConfig(globalConfigPath, dryRun) {
|
|
343
|
+
if (!await pathExists(globalConfigPath)) return void 0;
|
|
344
|
+
const bakPath = `${globalConfigPath}.bak-${timestampForBak()}`;
|
|
345
|
+
if (!dryRun) {
|
|
346
|
+
await fs3.copyFile(globalConfigPath, bakPath);
|
|
347
|
+
}
|
|
348
|
+
return bakPath;
|
|
349
|
+
}
|
|
350
|
+
async function main() {
|
|
351
|
+
const promptCtx = tryGetPromptContext();
|
|
352
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) || Boolean(promptCtx);
|
|
353
|
+
if (!interactive) {
|
|
354
|
+
throw new Error("\u042D\u0442\u043E\u0442 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u0449\u0438\u043A \u0440\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0432 \u0438\u043D\u0442\u0435\u0440\u0430\u043A\u0442\u0438\u0432\u043D\u043E\u043C TUI \u0440\u0435\u0436\u0438\u043C\u0435 (TTY \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D).");
|
|
355
|
+
}
|
|
356
|
+
const doBackup = await confirm({ message: "\u0421\u0434\u0435\u043B\u0430\u0442\u044C zip-\u0431\u044D\u043A\u0430\u043F \u0442\u0435\u043A\u0443\u0449\u0435\u0433\u043E ~/.opencode \u043F\u0435\u0440\u0435\u0434 \u0437\u0430\u043C\u0435\u043D\u043E\u0439?", default: true }, promptCtx);
|
|
357
|
+
const backupDir = doBackup ? await input(
|
|
358
|
+
{ message: "\u041A\u0443\u0434\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C \u0431\u044D\u043A\u0430\u043F\u044B?", default: "~/.opencode-backups", required: true },
|
|
359
|
+
promptCtx
|
|
360
|
+
) : void 0;
|
|
361
|
+
const paths = getInstallPaths(backupDir);
|
|
362
|
+
const templatesRoot = getTemplatesRoot();
|
|
363
|
+
const templatesAgents = path4.join(templatesRoot, "agents");
|
|
364
|
+
const templatesSkills = path4.join(templatesRoot, "skills");
|
|
365
|
+
if (!await pathExists(templatesAgents) || !await pathExists(templatesSkills)) {
|
|
366
|
+
throw new Error(`templates not found at ${templatesRoot}. Is the package built correctly?`);
|
|
367
|
+
}
|
|
368
|
+
const keyRefs = getKeyFileRefs();
|
|
369
|
+
const report = [];
|
|
370
|
+
const missingKeys = [];
|
|
371
|
+
report.push(`Target: ${paths.targetRoot}`);
|
|
372
|
+
report.push("Mode: apply");
|
|
373
|
+
let zipBackupPath;
|
|
374
|
+
if (doBackup) {
|
|
375
|
+
zipBackupPath = await backupExistingOpencode({ targetRoot: paths.targetRoot, backupDir: paths.backupDir }, false);
|
|
376
|
+
if (zipBackupPath) report.push(`Backup zip: ${zipBackupPath}`);
|
|
377
|
+
} else {
|
|
378
|
+
report.push("Backup: skipped");
|
|
379
|
+
}
|
|
380
|
+
await removeIfExists(paths.targetAgents);
|
|
381
|
+
report.push(`Replace: ${paths.targetAgents} <= ${templatesAgents}`);
|
|
382
|
+
await copyDir(templatesAgents, paths.targetAgents);
|
|
383
|
+
const languages = await promptLanguages();
|
|
384
|
+
const skillPlan = buildSkillCopyPlan(languages);
|
|
385
|
+
await removeIfExists(paths.targetSkills);
|
|
386
|
+
report.push(`Replace: ${paths.targetSkills} <= ${templatesSkills} (selected: ${languages.join(",")})`);
|
|
387
|
+
for (const rel of skillPlan.relDirs) {
|
|
388
|
+
const src = path4.join(templatesSkills, rel);
|
|
389
|
+
const dst = path4.join(paths.targetSkills, rel);
|
|
390
|
+
report.push(` - copy ${src} -> ${dst}`);
|
|
391
|
+
if (await pathExists(src)) await copyDir(src, dst);
|
|
392
|
+
}
|
|
393
|
+
report.push(`Keys dir: ${paths.targetKeys}`);
|
|
394
|
+
await ensureDir(paths.targetKeys);
|
|
395
|
+
const keyFiles = {
|
|
396
|
+
zaiApi: path4.join(paths.targetKeys, "zai_api.txt"),
|
|
397
|
+
context7: path4.join(paths.targetKeys, "context7.txt"),
|
|
398
|
+
tavily: path4.join(paths.targetKeys, "tavily_search.txt")
|
|
399
|
+
};
|
|
400
|
+
const keyInput = await promptForMissingKeys({ keyFiles });
|
|
401
|
+
const keyWrites = [
|
|
402
|
+
{ name: "zai_api", filePath: keyFiles.zaiApi, action: "", inputProvided: keyInput.zaiApi !== void 0 },
|
|
403
|
+
{ name: "context7", filePath: keyFiles.context7, action: "", inputProvided: keyInput.context7 !== void 0 },
|
|
404
|
+
{ name: "tavily_search", filePath: keyFiles.tavily, action: "", inputProvided: keyInput.tavily !== void 0 }
|
|
405
|
+
];
|
|
406
|
+
{
|
|
407
|
+
const r1 = await writeKeyFile(keyWrites[0].filePath, keyInput.zaiApi, false);
|
|
408
|
+
keyWrites[0].action = r1.action;
|
|
409
|
+
const r2 = await writeKeyFile(keyWrites[1].filePath, keyInput.context7, false);
|
|
410
|
+
keyWrites[1].action = r2.action;
|
|
411
|
+
const r3 = await writeKeyFile(keyWrites[2].filePath, keyInput.tavily, false);
|
|
412
|
+
keyWrites[2].action = r3.action;
|
|
413
|
+
}
|
|
414
|
+
for (const k of keyWrites) {
|
|
415
|
+
report.push(`Key file: ${k.filePath} (${k.action}${k.inputProvided ? ", provided" : ""})`);
|
|
416
|
+
if (!await isKeyFileFilled(k.filePath)) missingKeys.push(k.filePath);
|
|
417
|
+
}
|
|
418
|
+
const configBak = await maybeBackupConfig(paths.globalConfig, false);
|
|
419
|
+
if (configBak) report.push(`Config backup: ${configBak}`);
|
|
420
|
+
const configContent = renderGlobalConfigJsonc({
|
|
421
|
+
zaiApi: keyRefs.zaiApi,
|
|
422
|
+
context7: keyRefs.context7,
|
|
423
|
+
tavily: keyRefs.tavily
|
|
424
|
+
});
|
|
425
|
+
report.push(`Write: ${paths.globalConfig}`);
|
|
426
|
+
await writeFileAtomic(paths.globalConfig, configContent);
|
|
427
|
+
process.stdout.write(`${report.join("\n")}
|
|
428
|
+
`);
|
|
429
|
+
if (missingKeys.length > 0) {
|
|
430
|
+
process.stdout.write(`
|
|
431
|
+
Missing keys (fill these files):
|
|
432
|
+
${missingKeys.map((p) => `- ${p}`).join("\n")}
|
|
433
|
+
`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
main().catch((error) => {
|
|
437
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
438
|
+
process.stderr.write(`assistagents failed: ${message}
|
|
439
|
+
`);
|
|
440
|
+
process.exitCode = 1;
|
|
441
|
+
});
|
|
442
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/config-template.ts","../src/backup.ts","../src/fs-utils.ts","../src/paths.ts","../src/skill-selection.ts"],"sourcesContent":["import { checkbox, confirm, input, password } from '@inquirer/prompts';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport fs from 'node:fs/promises';\nimport fsSync from 'node:fs';\nimport { Readable, Writable } from 'node:stream';\n\nimport { renderGlobalConfigJsonc } from './config-template.js';\nimport { zipDirectory } from './backup.js';\nimport { chmod600IfPossible, copyDir, ensureDir, isNonEmptyDir, pathExists, removeIfExists, writeFileAtomic } from './fs-utils.js';\nimport { getInstallPaths, getKeyFileRefs } from './paths.js';\nimport { ALL_LANGUAGES, buildSkillCopyPlan, type LanguageKey } from './skill-selection.js';\n\nfunction getTemplatesRoot(): string {\n const here = path.dirname(fileURLToPath(import.meta.url));\n // dist/cli.js -> packageRoot/templates\n return path.resolve(here, '..', 'templates');\n}\n\nasync function writeKeyFile(filePath: string, value: string | undefined, dryRun: boolean): Promise<{ action: 'written' | 'created-empty' | 'kept' }>{\n const exists = await pathExists(filePath);\n\n if (value !== undefined) {\n if (!dryRun) {\n await writeFileAtomic(filePath, `${value}\\n`);\n await chmod600IfPossible(filePath);\n }\n return { action: 'written' };\n }\n\n if (exists) return { action: 'kept' };\n\n if (!dryRun) {\n await writeFileAtomic(filePath, '');\n await chmod600IfPossible(filePath);\n }\n return { action: 'created-empty' };\n}\n\ntype PromptContext = { input: Readable; output: Writable } | undefined;\n\nfunction tryGetPromptContext(): PromptContext {\n const ttyOk = Boolean(process.stdin.isTTY && process.stdout.isTTY);\n if (ttyOk) return undefined;\n\n // Some runners (npm exec/npx) may not provide a TTY stdin even in a terminal.\n // Try to attach to /dev/tty on Unix.\n if (process.platform !== 'win32') {\n try {\n // createReadStream('/dev/tty') can emit async errors that bypass try/catch.\n // Use openSync so we can fail fast and safely.\n const inFd = fsSync.openSync('/dev/tty', 'r');\n const outFd = fsSync.openSync('/dev/tty', 'w');\n\n const input = fsSync.createReadStream('', { fd: inFd, autoClose: true });\n const output = fsSync.createWriteStream('', { fd: outFd, autoClose: true });\n\n input.on('error', () => {});\n output.on('error', () => {});\n\n return { input, output };\n } catch {\n return undefined;\n }\n }\n\n return undefined;\n}\n\nasync function isKeyFileFilled(filePath: string): Promise<boolean> {\n try {\n const content = (await fs.readFile(filePath, 'utf8')).trim();\n return content.length > 0;\n } catch {\n return false;\n }\n}\n\nasync function promptForMissingKeys(opts: {\n keyFiles: { zaiApi: string; context7: string; tavily: string };\n}): Promise<{ zaiApi?: string; context7?: string; tavily?: string }> {\n const promptCtx = tryGetPromptContext();\n const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) || Boolean(promptCtx);\n if (!interactive) {\n throw new Error('Нет TTY для интерактивного режима (запусти из терминала).');\n }\n\n const askOne = async (label: string, filePath: string): Promise<string | undefined> => {\n const filled = await isKeyFileFilled(filePath);\n if (filled) {\n const keep = await confirm(\n { message: `${label}: файл уже заполнен (${filePath}). Оставить как есть?`, default: true },\n promptCtx\n );\n if (keep) return undefined;\n }\n\n const value = await password(\n {\n message: `Введите ${label} (сохраним в ${filePath})`,\n mask: '*',\n validate: (v) => (v.trim().length > 0 ? true : 'Нужно непустое значение'),\n },\n promptCtx\n );\n return value;\n };\n\n const out: { zaiApi?: string; context7?: string; tavily?: string } = {};\n const zaiApi = await askOne('ZAI_API_KEY', opts.keyFiles.zaiApi);\n if (zaiApi !== undefined) out.zaiApi = zaiApi;\n const context7 = await askOne('CONTEXT7_API_KEY', opts.keyFiles.context7);\n if (context7 !== undefined) out.context7 = context7;\n const tavily = await askOne('TAVILY_API_KEY', opts.keyFiles.tavily);\n if (tavily !== undefined) out.tavily = tavily;\n\n return out;\n}\n\nasync function promptLanguages(): Promise<LanguageKey[]> {\n const promptCtx = tryGetPromptContext();\n const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) || Boolean(promptCtx);\n if (!interactive) {\n throw new Error('Нет TTY для интерактивного режима (запусти из терминала).');\n }\n\n const selected = await checkbox<LanguageKey>(\n {\n message: 'Какие языки добавить?',\n choices: [\n { name: 'TypeScript', value: 'typescript', checked: true },\n { name: 'Rust', value: 'rust', checked: true },\n { name: 'C#', value: 'csharp', checked: true },\n ],\n validate: (values) => (values.length > 0 ? true : 'Выбери хотя бы один язык'),\n },\n promptCtx\n );\n return selected;\n}\n\nasync function backupExistingOpencode(paths: { targetRoot: string; backupDir: string }, dryRun: boolean): Promise<string | undefined> {\n const nonEmpty = await isNonEmptyDir(paths.targetRoot);\n if (!nonEmpty) return undefined;\n if (dryRun) return path.join(paths.backupDir, 'opencode-backup-YYYYMMDD-HHmmss.zip');\n return await zipDirectory({ sourceDir: paths.targetRoot, backupDir: paths.backupDir });\n}\n\nfunction timestampForBak(date = new Date()): string {\n const pad = (n: number) => n.toString().padStart(2, '0');\n return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;\n}\n\nasync function maybeBackupConfig(globalConfigPath: string, dryRun: boolean): Promise<string | undefined> {\n if (!(await pathExists(globalConfigPath))) return undefined;\n const bakPath = `${globalConfigPath}.bak-${timestampForBak()}`;\n if (!dryRun) {\n await fs.copyFile(globalConfigPath, bakPath);\n }\n return bakPath;\n}\n\nasync function main(): Promise<void> {\n const promptCtx = tryGetPromptContext();\n const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) || Boolean(promptCtx);\n if (!interactive) {\n throw new Error('Этот установщик работает только в интерактивном TUI режиме (TTY не найден).');\n }\n\n const doBackup = await confirm({ message: 'Сделать zip-бэкап текущего ~/.opencode перед заменой?', default: true }, promptCtx);\n const backupDir = doBackup\n ? await input(\n { message: 'Куда сохранить бэкапы?', default: '~/.opencode-backups', required: true },\n promptCtx\n )\n : undefined;\n\n const paths = getInstallPaths(backupDir);\n const templatesRoot = getTemplatesRoot();\n const templatesAgents = path.join(templatesRoot, 'agents');\n const templatesSkills = path.join(templatesRoot, 'skills');\n\n if (!(await pathExists(templatesAgents)) || !(await pathExists(templatesSkills))) {\n throw new Error(`templates not found at ${templatesRoot}. Is the package built correctly?`);\n }\n\n const keyRefs = getKeyFileRefs();\n const report: string[] = [];\n const missingKeys: string[] = [];\n\n report.push(`Target: ${paths.targetRoot}`);\n report.push('Mode: apply');\n\n let zipBackupPath: string | undefined;\n if (doBackup) {\n zipBackupPath = await backupExistingOpencode({ targetRoot: paths.targetRoot, backupDir: paths.backupDir }, false);\n if (zipBackupPath) report.push(`Backup zip: ${zipBackupPath}`);\n } else {\n report.push('Backup: skipped');\n }\n\n // Replace agents\n await removeIfExists(paths.targetAgents);\n report.push(`Replace: ${paths.targetAgents} <= ${templatesAgents}`);\n await copyDir(templatesAgents, paths.targetAgents);\n\n // Replace skills (selective)\n const languages = await promptLanguages();\n const skillPlan = buildSkillCopyPlan(languages);\n await removeIfExists(paths.targetSkills);\n report.push(`Replace: ${paths.targetSkills} <= ${templatesSkills} (selected: ${languages.join(',')})`);\n for (const rel of skillPlan.relDirs) {\n const src = path.join(templatesSkills, rel);\n const dst = path.join(paths.targetSkills, rel);\n report.push(` - copy ${src} -> ${dst}`);\n if (await pathExists(src)) await copyDir(src, dst);\n }\n\n // Keys\n report.push(`Keys dir: ${paths.targetKeys}`);\n await ensureDir(paths.targetKeys);\n\n const keyFiles = {\n zaiApi: path.join(paths.targetKeys, 'zai_api.txt'),\n context7: path.join(paths.targetKeys, 'context7.txt'),\n tavily: path.join(paths.targetKeys, 'tavily_search.txt'),\n };\n\n const keyInput = await promptForMissingKeys({ keyFiles });\n\n const keyWrites: Array<{ name: string; filePath: string; action: string; inputProvided: boolean }> = [\n { name: 'zai_api', filePath: keyFiles.zaiApi, action: '', inputProvided: keyInput.zaiApi !== undefined },\n { name: 'context7', filePath: keyFiles.context7, action: '', inputProvided: keyInput.context7 !== undefined },\n { name: 'tavily_search', filePath: keyFiles.tavily, action: '', inputProvided: keyInput.tavily !== undefined },\n ];\n\n {\n const r1 = await writeKeyFile(keyWrites[0]!.filePath, keyInput.zaiApi, false);\n keyWrites[0]!.action = r1.action;\n const r2 = await writeKeyFile(keyWrites[1]!.filePath, keyInput.context7, false);\n keyWrites[1]!.action = r2.action;\n const r3 = await writeKeyFile(keyWrites[2]!.filePath, keyInput.tavily, false);\n keyWrites[2]!.action = r3.action;\n }\n\n for (const k of keyWrites) {\n report.push(`Key file: ${k.filePath} (${k.action}${k.inputProvided ? ', provided' : ''})`);\n if (!(await isKeyFileFilled(k.filePath))) missingKeys.push(k.filePath);\n }\n\n // Global config\n const configBak = await maybeBackupConfig(paths.globalConfig, false);\n if (configBak) report.push(`Config backup: ${configBak}`);\n\n const configContent = renderGlobalConfigJsonc({\n zaiApi: keyRefs.zaiApi,\n context7: keyRefs.context7,\n tavily: keyRefs.tavily,\n });\n report.push(`Write: ${paths.globalConfig}`);\n await writeFileAtomic(paths.globalConfig, configContent);\n\n // Output\n process.stdout.write(`${report.join('\\n')}\\n`);\n\n if (missingKeys.length > 0) {\n process.stdout.write(`\\nMissing keys (fill these files):\\n${missingKeys.map((p) => `- ${p}`).join('\\n')}\\n`);\n }\n}\n\nmain().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`assistagents failed: ${message}\\n`);\n process.exitCode = 1;\n});\n","export type KeyFiles = {\n zaiApi: string;\n context7: string;\n tavily: string;\n};\n\nexport function renderGlobalConfigJsonc(keyFiles: KeyFiles): string {\n // Keep this close to the repo's existing opencode.jsonc layout.\n return `{\n \"$schema\": \"https://opencode.ai/config.json\",\n \"keybinds\": {\n \"messages_undo\": \"ctrl+alt+z\",\n \"session_new\": \"ctrl+alt+n\",\n \"command_list\": \"ctrl+alt+p\"\n },\n \"watcher\": {\n \"ignore\": [\n \"node_modules/**\",\n \"dist/**\",\n \".git/**\",\n \".venv/**\",\n \"bin/**\"\n ]\n },\n \"permission\": {\n \"bash\": \"deny\",\n \"codesearch\": \"deny\",\n \"doom_loop\": \"deny\",\n \"edit\": \"deny\",\n \"external_directory\": \"deny\",\n \"glob\": \"deny\",\n \"grep\": \"deny\",\n \"list\": \"deny\",\n \"lsp\": \"deny\",\n \"question\": \"deny\",\n \"read\": \"deny\",\n \"task\": \"deny\",\n \"todoread\": \"deny\",\n \"todowrite\": \"deny\",\n \"webfetch\": \"deny\",\n \"websearch\": \"deny\",\n \"skill\": \"deny\",\n // MCP\n \"tavily-search*\": \"deny\",\n \"ddg-search*\": \"deny\",\n \"zai-web-search*\": \"deny\",\n \"zai-web-reader*\": \"deny\",\n \"context7*\": \"deny\",\n \"github-grep*\": \"deny\",\n \"pencil\": \"deny\"\n },\n \"agent\": {\n \"explore\": { \"disable\": true },\n \"build\": { \"disable\": true },\n \"plan\": { \"disable\": true }\n },\n \"mcp\": {\n \"tavily-search\": {\n \"type\": \"remote\",\n \"url\": \"https://mcp.tavily.com/mcp/?tavilyApiKey={file:${keyFiles.tavily}}\"\n },\n \"ddg-search\": {\n \"type\": \"local\",\n \"command\": [\"uvx\", \"duckduckgo-mcp-server\"]\n },\n \"zai-web-search\": {\n \"type\": \"remote\",\n \"url\": \"https://api.z.ai/api/mcp/web_search_prime/mcp\",\n \"headers\": {\n \"Authorization\": \"Bearer {file:${keyFiles.zaiApi}}\"\n }\n },\n \"zai-web-reader\": {\n \"type\": \"remote\",\n \"url\": \"https://api.z.ai/api/mcp/web_reader/mcp\",\n \"headers\": {\n \"Authorization\": \"Bearer {file:${keyFiles.zaiApi}}\"\n }\n },\n \"context7\": {\n \"type\": \"remote\",\n \"url\": \"https://mcp.context7.com/mcp\",\n \"headers\": {\n \"CONTEXT7_API_KEY\": \"{file:${keyFiles.context7}}\"\n }\n },\n \"github-grep\": {\n \"type\": \"remote\",\n \"url\": \"https://mcp.grep.app\"\n }\n }\n}\n`;\n}\n","import fs from 'node:fs';\nimport fsp from 'node:fs/promises';\nimport path from 'node:path';\nimport archiver from 'archiver';\nimport { ensureDir } from './fs-utils.js';\n\nexport type BackupResult = {\n created: boolean;\n zipPath?: string;\n};\n\nfunction timestampForFilename(date = new Date()): string {\n const pad = (n: number) => n.toString().padStart(2, '0');\n return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}-${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;\n}\n\nexport async function zipDirectory(opts: {\n sourceDir: string;\n backupDir: string;\n prefix?: string;\n}): Promise<string> {\n const { sourceDir, backupDir } = opts;\n const prefix = opts.prefix ?? 'opencode-backup';\n\n await ensureDir(backupDir);\n const zipPath = path.join(backupDir, `${prefix}-${timestampForFilename()}.zip`);\n\n await new Promise<void>((resolve, reject) => {\n const output = fs.createWriteStream(zipPath);\n const archive = archiver('zip', { zlib: { level: 9 } });\n\n output.on('close', () => resolve());\n output.on('error', (err) => reject(err));\n\n archive.on('warning', (err: NodeJS.ErrnoException) => {\n if (err.code === 'ENOENT') return;\n reject(err);\n });\n archive.on('error', (err: Error) => reject(err));\n\n archive.pipe(output);\n // Put directory contents at archive root.\n archive.directory(sourceDir, false);\n void archive.finalize();\n });\n\n // Ensure file exists.\n await fsp.stat(zipPath);\n return zipPath;\n}\n","import fs from 'node:fs/promises';\nimport path from 'node:path';\n\nexport async function pathExists(p: string): Promise<boolean> {\n try {\n await fs.stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureDir(dir: string): Promise<void> {\n await fs.mkdir(dir, { recursive: true });\n}\n\nexport async function isNonEmptyDir(dir: string): Promise<boolean> {\n try {\n const entries = await fs.readdir(dir);\n return entries.length > 0;\n } catch {\n return false;\n }\n}\n\nexport async function removeIfExists(p: string): Promise<void> {\n if (!(await pathExists(p))) return;\n await fs.rm(p, { recursive: true, force: true });\n}\n\nexport async function copyDir(src: string, dest: string): Promise<void> {\n await ensureDir(path.dirname(dest));\n await fs.cp(src, dest, {\n recursive: true,\n force: true,\n errorOnExist: false,\n dereference: true,\n });\n}\n\nexport async function writeFileAtomic(filePath: string, content: string): Promise<void> {\n const dir = path.dirname(filePath);\n await ensureDir(dir);\n\n const tmpPath = `${filePath}.tmp-${Date.now()}-${Math.random().toString(16).slice(2)}`;\n await fs.writeFile(tmpPath, content, { encoding: 'utf8' });\n await fs.rename(tmpPath, filePath);\n}\n\nexport async function chmod600IfPossible(filePath: string): Promise<void> {\n // Windows may not support chmod in the same way; ignore failures.\n try {\n await fs.chmod(filePath, 0o600);\n } catch {\n // ignore\n }\n}\n","import os from 'node:os';\nimport path from 'node:path';\n\nexport type InstallPaths = {\n targetRoot: string;\n targetAgents: string;\n targetSkills: string;\n targetKeys: string;\n globalConfig: string;\n backupDir: string;\n};\n\nexport function getInstallPaths(backupDirFlag?: string): InstallPaths {\n const home = os.homedir();\n const targetRoot = path.join(home, '.opencode');\n\n const expandTilde = (p: string): string => {\n if (p === '~') return home;\n if (p.startsWith('~/') || p.startsWith('~\\\\')) return path.join(home, p.slice(2));\n return p;\n };\n\n const backupDir = backupDirFlag ? expandTilde(backupDirFlag) : path.join(home, '.opencode-backups');\n\n return {\n targetRoot,\n targetAgents: path.join(targetRoot, 'agents'),\n targetSkills: path.join(targetRoot, 'skills'),\n targetKeys: path.join(targetRoot, 'keys'),\n globalConfig: path.join(targetRoot, 'opencode.jsonc'),\n backupDir,\n };\n}\n\nexport function getKeyFileRefs(): { zaiApi: string; context7: string; tavily: string } {\n // Important: OpenCode supports ~ inside {file:...} references.\n return {\n zaiApi: '~/.opencode/keys/zai_api.txt',\n context7: '~/.opencode/keys/context7.txt',\n tavily: '~/.opencode/keys/tavily_search.txt',\n };\n}\n","export type LanguageKey = 'typescript' | 'rust' | 'csharp';\n\nexport const ALL_LANGUAGES: ReadonlyArray<LanguageKey> = ['typescript', 'rust', 'csharp'];\n\nexport type SkillCopyPlan = {\n // Paths relative to templates/skills\n relDirs: string[];\n};\n\nexport function buildSkillCopyPlan(languages: ReadonlyArray<LanguageKey>): SkillCopyPlan {\n const relDirs: string[] = [];\n\n // Always install these.\n relDirs.push('planning');\n relDirs.push('review');\n relDirs.push('testing');\n relDirs.push('research-strategy');\n\n // Language-specific coder skills.\n const uniq = new Set(languages);\n for (const lang of ALL_LANGUAGES) {\n if (uniq.has(lang)) {\n relDirs.push(`coder/${lang}`);\n }\n }\n\n return { relDirs };\n}\n"],"mappings":"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","names":["path","fs","fs","path","path","fs","path","path","input","fs"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ozerohax/assistagents",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "npx installer for OpenCode agents/skills and key files",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"assistagents": "dist/cli.js",
|
|
9
|
+
"assistagents-setup": "dist/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"templates"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"dev": "tsx src/cli.ts",
|
|
24
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
25
|
+
"test": "tsx --test",
|
|
26
|
+
"prepack": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@inquirer/prompts": "^7.4.1",
|
|
30
|
+
"archiver": "^7.0.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/archiver": "^6.0.3",
|
|
34
|
+
"@types/node": "^20.11.30",
|
|
35
|
+
"tsx": "^4.19.2",
|
|
36
|
+
"tsup": "^8.0.2",
|
|
37
|
+
"typescript": "^5.5.4"
|
|
38
|
+
}
|
|
39
|
+
}
|