@zhihaohong52/sonata 0.4.0
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/LICENSE +21 -0
- package/README.md +437 -0
- package/dist/adapters/claude.js +83 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/codex.js +382 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/index.js +21 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/opencode.js +104 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/pi.js +152 -0
- package/dist/adapters/pi.js.map +1 -0
- package/dist/adapters/reasonix.js +358 -0
- package/dist/adapters/reasonix.js.map +1 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/agent-markers.js +3 -0
- package/dist/agent-markers.js.map +1 -0
- package/dist/aipricing.js +109 -0
- package/dist/aipricing.js.map +1 -0
- package/dist/catalog.js +274 -0
- package/dist/catalog.js.map +1 -0
- package/dist/cli.js +766 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/approve.js +23 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/auth.js +41 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/catalog.js +172 -0
- package/dist/commands/catalog.js.map +1 -0
- package/dist/commands/code.js +113 -0
- package/dist/commands/code.js.map +1 -0
- package/dist/commands/dispatch.js +148 -0
- package/dist/commands/dispatch.js.map +1 -0
- package/dist/commands/doctor.js +545 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/gc.js +25 -0
- package/dist/commands/gc.js.map +1 -0
- package/dist/commands/init-log.js +83 -0
- package/dist/commands/init-log.js.map +1 -0
- package/dist/commands/init.js +157 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/log.js +24 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/route.js +660 -0
- package/dist/commands/route.js.map +1 -0
- package/dist/commands/run.js +199 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/runs.js +37 -0
- package/dist/commands/runs.js.map +1 -0
- package/dist/commands/serve.js +818 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/status.js +14 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.js +258 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/tail.js +229 -0
- package/dist/commands/tail.js.map +1 -0
- package/dist/commands/usage.js +85 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/commands/verify.js +69 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/wait.js +38 -0
- package/dist/commands/wait.js.map +1 -0
- package/dist/config.js +588 -0
- package/dist/config.js.map +1 -0
- package/dist/detect.js +445 -0
- package/dist/detect.js.map +1 -0
- package/dist/filelock.js +83 -0
- package/dist/filelock.js.map +1 -0
- package/dist/init/apply.js +96 -0
- package/dist/init/apply.js.map +1 -0
- package/dist/init/candidates.js +115 -0
- package/dist/init/candidates.js.map +1 -0
- package/dist/init/discover.js +216 -0
- package/dist/init/discover.js.map +1 -0
- package/dist/init/helpers.js +429 -0
- package/dist/init/helpers.js.map +1 -0
- package/dist/init/interactive-state.js +128 -0
- package/dist/init/interactive-state.js.map +1 -0
- package/dist/init/plan.js +225 -0
- package/dist/init/plan.js.map +1 -0
- package/dist/init/scripted-state.js +130 -0
- package/dist/init/scripted-state.js.map +1 -0
- package/dist/init/toml.js +83 -0
- package/dist/init/toml.js.map +1 -0
- package/dist/init/validate.js +149 -0
- package/dist/init/validate.js.map +1 -0
- package/dist/ledger.js +123 -0
- package/dist/ledger.js.map +1 -0
- package/dist/mcp/protocol.js +64 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/server.js +96 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.js +210 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mode.js +64 -0
- package/dist/mode.js.map +1 -0
- package/dist/native/claude-shim.js +228 -0
- package/dist/native/claude-shim.js.map +1 -0
- package/dist/native/codex-auth.js +177 -0
- package/dist/native/codex-auth.js.map +1 -0
- package/dist/native/copilot-auth.js +105 -0
- package/dist/native/copilot-auth.js.map +1 -0
- package/dist/native/credentials.js +83 -0
- package/dist/native/credentials.js.map +1 -0
- package/dist/native/hosts.js +60 -0
- package/dist/native/hosts.js.map +1 -0
- package/dist/native/litellm.js +103 -0
- package/dist/native/litellm.js.map +1 -0
- package/dist/native/models.js +96 -0
- package/dist/native/models.js.map +1 -0
- package/dist/native/oauth-login.js +112 -0
- package/dist/native/oauth-login.js.map +1 -0
- package/dist/native/router.js +469 -0
- package/dist/native/router.js.map +1 -0
- package/dist/native/tls.js +73 -0
- package/dist/native/tls.js.map +1 -0
- package/dist/native/usage.js +119 -0
- package/dist/native/usage.js.map +1 -0
- package/dist/normalize.js +98 -0
- package/dist/normalize.js.map +1 -0
- package/dist/pricing.js +86 -0
- package/dist/pricing.js.map +1 -0
- package/dist/roles.js +49 -0
- package/dist/roles.js.map +1 -0
- package/dist/sessions.js +62 -0
- package/dist/sessions.js.map +1 -0
- package/dist/settings.js +131 -0
- package/dist/settings.js.map +1 -0
- package/dist/store.js +70 -0
- package/dist/store.js.map +1 -0
- package/dist/tmux.js +89 -0
- package/dist/tmux.js.map +1 -0
- package/dist/tui-ink/app-state.js +340 -0
- package/dist/tui-ink/app-state.js.map +1 -0
- package/dist/tui-ink/app.js +156 -0
- package/dist/tui-ink/app.js.map +1 -0
- package/dist/tui-ink/components/byok-step.js +102 -0
- package/dist/tui-ink/components/byok-step.js.map +1 -0
- package/dist/tui-ink/components/login-screen.js +80 -0
- package/dist/tui-ink/components/login-screen.js.map +1 -0
- package/dist/tui-ink/components/models-step.js +92 -0
- package/dist/tui-ink/components/models-step.js.map +1 -0
- package/dist/tui-ink/components/multi-select-state.js +56 -0
- package/dist/tui-ink/components/multi-select-state.js.map +1 -0
- package/dist/tui-ink/components/multi-select.js +69 -0
- package/dist/tui-ink/components/multi-select.js.map +1 -0
- package/dist/tui-ink/components/providers-step.js +285 -0
- package/dist/tui-ink/components/providers-step.js.map +1 -0
- package/dist/tui-ink/components/ranked-select-state.js +41 -0
- package/dist/tui-ink/components/ranked-select-state.js.map +1 -0
- package/dist/tui-ink/components/ranked-select.js +61 -0
- package/dist/tui-ink/components/ranked-select.js.map +1 -0
- package/dist/tui-ink/components/search-select.js +47 -0
- package/dist/tui-ink/components/search-select.js.map +1 -0
- package/dist/tui-ink/components/text-input-state.js +42 -0
- package/dist/tui-ink/components/text-input-state.js.map +1 -0
- package/dist/tui-ink/components/text-input.js +48 -0
- package/dist/tui-ink/components/text-input.js.map +1 -0
- package/dist/tui-ink/run.js +38 -0
- package/dist/tui-ink/run.js.map +1 -0
- package/dist/tui-ink/test-compile.js +4 -0
- package/dist/tui-ink/test-compile.js.map +1 -0
- package/dist/tui-ink/types.js +2 -0
- package/dist/tui-ink/types.js.map +1 -0
- package/dist/tui.js +330 -0
- package/dist/tui.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/watchdog.js +85 -0
- package/dist/watchdog.js.map +1 -0
- package/hooks/capture-mode.mjs +56 -0
- package/hooks/ensure-serve.mjs +140 -0
- package/hooks/hooks.json +12 -0
- package/hooks/route-session.mjs +47 -0
- package/hooks/route-subagent.mjs +54 -0
- package/package.json +64 -0
- package/roles/code.md +13 -0
- package/roles/explore.md +9 -0
- package/roles/plan.md +10 -0
- package/roles/review.md +11 -0
- package/skills/loop/SKILL.md +43 -0
package/dist/detect.js
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment detection for `sonata init` and `sonata doctor`.
|
|
3
|
+
*
|
|
4
|
+
* Parsing is separated from process execution so the interesting logic can be
|
|
5
|
+
* tested against real fixture files rather than a live machine.
|
|
6
|
+
*/
|
|
7
|
+
import { execFile } from 'node:child_process';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import { existsSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { checkVersion } from './commands/doctor.js';
|
|
12
|
+
import { TIER_AGENT_MARKER } from './agent-markers.js';
|
|
13
|
+
import { parsePiRefs } from './adapters/pi.js';
|
|
14
|
+
import { parseCodexModels, codexModelList } from './adapters/codex.js';
|
|
15
|
+
import { parseReasonixRefs, reasonixDoctorJson } from './adapters/reasonix.js';
|
|
16
|
+
const run = promisify(execFile);
|
|
17
|
+
export function parseOpenCodeModels(text) {
|
|
18
|
+
let parsed;
|
|
19
|
+
try {
|
|
20
|
+
parsed = JSON.parse(text);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const out = [];
|
|
26
|
+
for (const [provider, def] of Object.entries(parsed?.provider ?? {})) {
|
|
27
|
+
for (const [id, model] of Object.entries(def?.models ?? {})) {
|
|
28
|
+
out.push({ provider, id, name: model?.name });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
// Well-known provider endpoints, merged from:
|
|
34
|
+
// - https://opencode.ai/docs/providers/ (opencode's built-in providers)
|
|
35
|
+
// - litellm.openai_compatible_endpoints (litellm's authoritative list)
|
|
36
|
+
// - https://docs.litellm.ai/docs/providers (litellm docs)
|
|
37
|
+
// Custom/self-hosted provider URLs are discovered from harness configs.
|
|
38
|
+
export const WELL_KNOWN_PROVIDER_URLS = {
|
|
39
|
+
// OpenAI ecosystem
|
|
40
|
+
'openai': 'https://api.openai.com/v1',
|
|
41
|
+
'openai-codex': 'https://api.openai.com/v1',
|
|
42
|
+
'codex': 'https://api.openai.com/v1',
|
|
43
|
+
// OpenCode's own gateways
|
|
44
|
+
'opencode': 'https://api.opencode.ai/v1',
|
|
45
|
+
'opencode-go': 'https://opencode.ai/zen/go/v1',
|
|
46
|
+
// Major providers
|
|
47
|
+
'anthropic': 'https://api.anthropic.com/v1',
|
|
48
|
+
'deepseek': 'https://api.deepseek.com/v1',
|
|
49
|
+
'google': 'https://generativelanguage.googleapis.com/v1beta',
|
|
50
|
+
'mistral': 'https://api.mistral.ai/v1',
|
|
51
|
+
'xai': 'https://api.x.ai/v1',
|
|
52
|
+
'meta_llama': 'https://api.llama.com/compat/v1',
|
|
53
|
+
// Aggregators / routers
|
|
54
|
+
'openrouter': 'https://openrouter.ai/api/v1',
|
|
55
|
+
// Inference providers
|
|
56
|
+
'groq': 'https://api.groq.com/openai/v1',
|
|
57
|
+
'together': 'https://api.together.xyz/v1',
|
|
58
|
+
'together_ai': 'https://api.together.xyz/v1',
|
|
59
|
+
'fireworks': 'https://api.fireworks.ai/inference/v1',
|
|
60
|
+
'fireworks_ai': 'https://api.fireworks.ai/inference/v1',
|
|
61
|
+
'cerebras': 'https://api.cerebras.ai/v1',
|
|
62
|
+
'deep-infra': 'https://api.deepinfra.com/v1',
|
|
63
|
+
'deepinfra': 'https://api.deepinfra.com/v1/openai',
|
|
64
|
+
'sambanova': 'https://api.sambanova.ai/v1',
|
|
65
|
+
'nebius': 'https://api.studio.nebius.ai/v1',
|
|
66
|
+
'lambda_ai': 'https://api.lambda.ai/v1',
|
|
67
|
+
'hyperbolic': 'https://api.hyperbolic.xyz/v1',
|
|
68
|
+
'perplexity': 'https://api.perplexity.ai',
|
|
69
|
+
'nvidia_nim': 'https://integrate.api.nvidia.com/v1',
|
|
70
|
+
'featherless_ai': 'https://api.featherless.ai/v1',
|
|
71
|
+
'novita': 'https://api.novita.ai/v3/openai',
|
|
72
|
+
'nscale': 'https://inference.api.nscale.com/v1',
|
|
73
|
+
// Other known providers
|
|
74
|
+
'moonshot': 'https://api.moonshot.ai/v1',
|
|
75
|
+
'dashscope': 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1',
|
|
76
|
+
'galadriel': 'https://api.galadriel.ai/v1',
|
|
77
|
+
'friendliai': 'https://api.friendli.ai/serverless/v1',
|
|
78
|
+
'chutes': 'https://llm.chutes.ai/v1',
|
|
79
|
+
'empower': 'https://app.empower.dev/api/v1',
|
|
80
|
+
'morph': 'https://api.morphllm.com/v1',
|
|
81
|
+
};
|
|
82
|
+
export function parseOpenCodeProviderBaseUrls(text) {
|
|
83
|
+
// Start with well-known defaults — opencode's built-in providers are not
|
|
84
|
+
// listed in the config file at all, so iterating config entries alone misses
|
|
85
|
+
// every standard provider.
|
|
86
|
+
const out = { ...WELL_KNOWN_PROVIDER_URLS };
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(text);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
for (const [provider, def] of Object.entries(parsed?.provider ?? {})) {
|
|
95
|
+
const baseUrl = def?.options?.baseURL;
|
|
96
|
+
if (typeof baseUrl === 'string' && baseUrl.trim() !== '') {
|
|
97
|
+
out[provider] = baseUrl;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
export function parseAuthedProviders(text) {
|
|
103
|
+
try {
|
|
104
|
+
return Object.keys(JSON.parse(text) ?? {});
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Parses `opencode models`, which prints one `provider/model` ref per line —
|
|
112
|
+
* exactly the string `-m` accepts.
|
|
113
|
+
*/
|
|
114
|
+
export function parseOpenCodeRefs(stdout) {
|
|
115
|
+
const out = [];
|
|
116
|
+
for (const raw of stdout.split('\n')) {
|
|
117
|
+
const line = raw.trim();
|
|
118
|
+
const slash = line.indexOf('/');
|
|
119
|
+
// A ref needs a non-empty provider and a non-empty id either side.
|
|
120
|
+
if (slash <= 0 || slash === line.length - 1)
|
|
121
|
+
continue;
|
|
122
|
+
out.push({
|
|
123
|
+
harness: 'opencode',
|
|
124
|
+
provider: line.slice(0, slash),
|
|
125
|
+
id: line.slice(slash + 1),
|
|
126
|
+
ref: line,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* OpenCode's free tier needs no auth entry, so it is offered alongside the
|
|
133
|
+
* authenticated providers.
|
|
134
|
+
*/
|
|
135
|
+
const FREE_OPENCODE_PROVIDERS = ['opencode'];
|
|
136
|
+
/**
|
|
137
|
+
* Groups refs into picker rows.
|
|
138
|
+
*
|
|
139
|
+
* The auth filter applies to opencode only. `pi --list-models` lists just the
|
|
140
|
+
* models pi can actually run — that is what piHealth relies on when it treats
|
|
141
|
+
* an empty list as "no usable provider" — so filtering pi by an auth file
|
|
142
|
+
* would invent a concept pi does not have.
|
|
143
|
+
*/
|
|
144
|
+
export function offerableProviders(refs, authed) {
|
|
145
|
+
const allowed = new Set([...authed, ...FREE_OPENCODE_PROVIDERS]);
|
|
146
|
+
const counts = new Map();
|
|
147
|
+
for (const ref of refs) {
|
|
148
|
+
if (ref.harness === 'opencode' && !allowed.has(ref.provider))
|
|
149
|
+
continue;
|
|
150
|
+
const key = `${ref.harness}/${ref.provider}`;
|
|
151
|
+
const seen = counts.get(key);
|
|
152
|
+
if (seen)
|
|
153
|
+
seen.count += 1;
|
|
154
|
+
else
|
|
155
|
+
counts.set(key, { harness: ref.harness, provider: ref.provider, count: 1, key });
|
|
156
|
+
}
|
|
157
|
+
return [...counts.values()].sort((a, b) => a.key.localeCompare(b.key));
|
|
158
|
+
}
|
|
159
|
+
/** Agent files that sonata generated but the current config no longer covers. */
|
|
160
|
+
export function staleAgents(agentsDir, expected) {
|
|
161
|
+
if (!existsSync(agentsDir))
|
|
162
|
+
return [];
|
|
163
|
+
const wanted = new Set(expected.map((n) => `${n}.md`));
|
|
164
|
+
return readdirSync(agentsDir)
|
|
165
|
+
.filter((f) => f.endsWith('.md'))
|
|
166
|
+
.filter((f) => isSonataAgent(join(agentsDir, f)))
|
|
167
|
+
.filter((f) => !wanted.has(f))
|
|
168
|
+
.sort();
|
|
169
|
+
}
|
|
170
|
+
export function isSonataAgent(path) {
|
|
171
|
+
try {
|
|
172
|
+
const text = readFileSync(path, 'utf8');
|
|
173
|
+
return text.includes('forwarding wrapper around the sonata runtime')
|
|
174
|
+
|| text.includes(TIER_AGENT_MARKER)
|
|
175
|
+
|| /^name:\s+native-[^\s]+/m.test(text);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Deletes the named agent files, returning those actually removed.
|
|
183
|
+
*
|
|
184
|
+
* Takes an explicit list rather than recomputing, so the files shown to a user
|
|
185
|
+
* are exactly the files deleted — there is no window in which the set changes
|
|
186
|
+
* between the question and the deletion.
|
|
187
|
+
*/
|
|
188
|
+
export function pruneAgents(agentsDir, files) {
|
|
189
|
+
const removed = [];
|
|
190
|
+
for (const f of files) {
|
|
191
|
+
try {
|
|
192
|
+
unlinkSync(join(agentsDir, f));
|
|
193
|
+
removed.push(f);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// Already gone. A concurrent sync is a race, not a failure.
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return removed;
|
|
200
|
+
}
|
|
201
|
+
async function tryRun(cmd, args, env) {
|
|
202
|
+
try {
|
|
203
|
+
const { stdout } = await run(cmd, args, { env: env ?? process.env });
|
|
204
|
+
return stdout.trim();
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Like `tryRun`, but bounded — a hung provider must not stall `init`. */
|
|
211
|
+
async function tryRunLimited(cmd, args, env, ms) {
|
|
212
|
+
try {
|
|
213
|
+
const { stdout } = await run(cmd, args, { env, timeout: ms });
|
|
214
|
+
return stdout.trim();
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
export async function detectTmux() {
|
|
221
|
+
const out = await tryRun('tmux', ['-V']);
|
|
222
|
+
if (out === null) {
|
|
223
|
+
return {
|
|
224
|
+
installed: false,
|
|
225
|
+
problems: [{
|
|
226
|
+
severity: 'error',
|
|
227
|
+
message: 'tmux is not installed — sonata runs every harness inside a tmux session',
|
|
228
|
+
fix: 'brew install tmux',
|
|
229
|
+
}],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return { installed: true, version: out.replace(/^tmux\s+/, ''), problems: [] };
|
|
233
|
+
}
|
|
234
|
+
export async function detectOpenCode(env) {
|
|
235
|
+
const problems = [];
|
|
236
|
+
const localBin = join(env.home, '.opencode', 'bin', 'opencode');
|
|
237
|
+
const path = `${join(env.home, '.opencode', 'bin')}:${process.env.PATH ?? ''}`;
|
|
238
|
+
const version = await tryRun('opencode', ['--version'], { ...process.env, PATH: path });
|
|
239
|
+
if (version === null) {
|
|
240
|
+
return {
|
|
241
|
+
name: 'opencode',
|
|
242
|
+
installed: false,
|
|
243
|
+
supported: false,
|
|
244
|
+
refs: [],
|
|
245
|
+
authedProviders: [],
|
|
246
|
+
problems: [{
|
|
247
|
+
severity: 'error',
|
|
248
|
+
message: 'opencode is not installed',
|
|
249
|
+
fix: 'curl -fsSL https://opencode.ai/install | bash',
|
|
250
|
+
}],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
const supported = checkVersion(version, env.supportedVersions);
|
|
254
|
+
if (!supported) {
|
|
255
|
+
problems.push({
|
|
256
|
+
severity: 'warn',
|
|
257
|
+
message: `opencode ${version} is outside the tested range ${env.supportedVersions}`,
|
|
258
|
+
fix: 'Prompt detection may misbehave; upgrade or pin a tested version.',
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const listing = await tryRun('opencode', ['models'], { ...process.env, PATH: path });
|
|
262
|
+
const refs = listing === null ? [] : parseOpenCodeRefs(listing);
|
|
263
|
+
// The CLI emits no display names; the config has them for custom providers.
|
|
264
|
+
const configPath = join(env.home, '.config', 'opencode', 'opencode.json');
|
|
265
|
+
const configText = existsSync(configPath) ? readFileSync(configPath, 'utf8') : '';
|
|
266
|
+
const named = configText === '' ? [] : parseOpenCodeModels(configText);
|
|
267
|
+
for (const ref of refs) {
|
|
268
|
+
ref.name = named.find((m) => m.provider === ref.provider && m.id === ref.id)?.name;
|
|
269
|
+
}
|
|
270
|
+
const providerBaseUrls = parseOpenCodeProviderBaseUrls(configText);
|
|
271
|
+
const authPath = join(env.home, '.local', 'share', 'opencode', 'auth.json');
|
|
272
|
+
const authedProviders = existsSync(authPath)
|
|
273
|
+
? parseAuthedProviders(readFileSync(authPath, 'utf8'))
|
|
274
|
+
: [];
|
|
275
|
+
if (refs.length === 0) {
|
|
276
|
+
problems.push({
|
|
277
|
+
severity: 'error',
|
|
278
|
+
message: 'opencode reported no models',
|
|
279
|
+
fix: 'opencode auth login',
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
name: 'opencode',
|
|
284
|
+
installed: true,
|
|
285
|
+
version,
|
|
286
|
+
supported,
|
|
287
|
+
binPath: existsSync(localBin) ? localBin : 'opencode',
|
|
288
|
+
refs,
|
|
289
|
+
authedProviders,
|
|
290
|
+
providerBaseUrls,
|
|
291
|
+
problems,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
export async function detectPi(env) {
|
|
295
|
+
const path = `${join(env.home, '.local', 'bin')}:${process.env.PATH ?? ''}`;
|
|
296
|
+
const version = await tryRun('pi', ['--version'], { ...process.env, PATH: path });
|
|
297
|
+
// Absence is not an error. A machine with only opencode is normal.
|
|
298
|
+
if (version === null) {
|
|
299
|
+
return { name: 'pi', installed: false, supported: false, refs: [], authedProviders: [], problems: [] };
|
|
300
|
+
}
|
|
301
|
+
// Pi can block when a provider is unreachable, and doctor must never hang.
|
|
302
|
+
const listing = await tryRunLimited('pi', ['--list-models'], { ...process.env, PATH: path }, 5_000);
|
|
303
|
+
const problems = [];
|
|
304
|
+
if (listing === null) {
|
|
305
|
+
problems.push({
|
|
306
|
+
severity: 'warn',
|
|
307
|
+
message: 'pi is installed but did not list any models',
|
|
308
|
+
fix: 'pi auth check --provider <name>',
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
name: 'pi',
|
|
313
|
+
installed: true,
|
|
314
|
+
version,
|
|
315
|
+
supported: true,
|
|
316
|
+
refs: listing === null ? [] : parsePiRefs(listing),
|
|
317
|
+
authedProviders: [],
|
|
318
|
+
problems,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Codex, discovered through `codex app-server`'s `model/list`.
|
|
323
|
+
*
|
|
324
|
+
* Codex has no provider dimension, so every model lands under the single
|
|
325
|
+
* pseudo-provider `codex`. An install that is present but not logged in lists
|
|
326
|
+
* nothing, which is reported as a warning rather than an error: a machine with
|
|
327
|
+
* only opencode is normal.
|
|
328
|
+
*/
|
|
329
|
+
export async function detectCodex(env) {
|
|
330
|
+
const version = await tryRun('codex', ['--version'], process.env);
|
|
331
|
+
if (version === null) {
|
|
332
|
+
return { name: 'codex', installed: false, supported: false, refs: [], authedProviders: [], problems: [] };
|
|
333
|
+
}
|
|
334
|
+
const listing = await codexModelList();
|
|
335
|
+
const refs = listing === null ? [] : parseCodexModels(listing);
|
|
336
|
+
const problems = [];
|
|
337
|
+
if (refs.length === 0) {
|
|
338
|
+
problems.push({
|
|
339
|
+
severity: 'warn',
|
|
340
|
+
message: 'codex is installed but listed no models',
|
|
341
|
+
fix: 'codex login',
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
name: 'codex',
|
|
346
|
+
installed: true,
|
|
347
|
+
version,
|
|
348
|
+
supported: true,
|
|
349
|
+
refs,
|
|
350
|
+
// Codex has one account rather than per-provider auth; listing models at
|
|
351
|
+
// all is the evidence that it is usable.
|
|
352
|
+
authedProviders: refs.length > 0 ? ['codex'] : [],
|
|
353
|
+
problems,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Reasonix, discovered through `reasonix doctor --json`.
|
|
358
|
+
*
|
|
359
|
+
* There is no `models` subcommand, but `doctor --json` carries the provider
|
|
360
|
+
* catalogue and the per-provider key state in one call, so discovery and the
|
|
361
|
+
* auth check are the same request. `parseReasonixRefs` drops providers without
|
|
362
|
+
* a key, which is why an install that lists nothing is reported as a warning
|
|
363
|
+
* rather than an error: a machine with only opencode is normal.
|
|
364
|
+
*/
|
|
365
|
+
export async function detectReasonix(_env) {
|
|
366
|
+
const version = await tryRun('reasonix', ['--version'], process.env);
|
|
367
|
+
if (version === null) {
|
|
368
|
+
return { name: 'reasonix', installed: false, supported: false, refs: [], authedProviders: [], problems: [] };
|
|
369
|
+
}
|
|
370
|
+
const json = await reasonixDoctorJson();
|
|
371
|
+
const refs = json === null ? [] : parseReasonixRefs(json);
|
|
372
|
+
const problems = [];
|
|
373
|
+
if (refs.length === 0) {
|
|
374
|
+
problems.push({
|
|
375
|
+
severity: 'warn',
|
|
376
|
+
message: 'reasonix is installed but no provider has a usable API key',
|
|
377
|
+
fix: 'reasonix setup',
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
name: 'reasonix',
|
|
382
|
+
installed: true,
|
|
383
|
+
version,
|
|
384
|
+
supported: true,
|
|
385
|
+
refs,
|
|
386
|
+
// `key_present` has already filtered these, so every provider that survived
|
|
387
|
+
// is one reasonix can actually run against.
|
|
388
|
+
authedProviders: [...new Set(refs.map((r) => r.provider))],
|
|
389
|
+
problems,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
export async function detectHarnesses(env) {
|
|
393
|
+
return Promise.all([detectOpenCode(env), detectPi(env), detectCodex(env), detectReasonix(env)]);
|
|
394
|
+
}
|
|
395
|
+
/** Where opencode keeps its own configuration. */
|
|
396
|
+
function opencodeConfigPath(home) {
|
|
397
|
+
return join(home, '.config', 'opencode', 'opencode.json');
|
|
398
|
+
}
|
|
399
|
+
function readOpencodeConfig(home) {
|
|
400
|
+
const path = opencodeConfigPath(home);
|
|
401
|
+
if (!existsSync(path))
|
|
402
|
+
return null;
|
|
403
|
+
try {
|
|
404
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
// opencode's config is not sonata's to repair. An unreadable one is
|
|
408
|
+
// opencode's problem to report, not a reason for doctor to fail.
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Agents the user's opencode config switches off.
|
|
414
|
+
*
|
|
415
|
+
* A disabled agent is not an error opencode reports: `opencode run --agent
|
|
416
|
+
* explore` falls back to the default agent with a warning in the pane that
|
|
417
|
+
* nothing parses. A read-only role then runs under the write-capable `build`,
|
|
418
|
+
* which is how sonata came to document an enforcement it did not have.
|
|
419
|
+
*/
|
|
420
|
+
export function disabledOpencodeAgents(home) {
|
|
421
|
+
const cfg = readOpencodeConfig(home);
|
|
422
|
+
const agents = (cfg?.agent ?? {});
|
|
423
|
+
return Object.entries(agents)
|
|
424
|
+
.filter(([, v]) => v?.disable === true)
|
|
425
|
+
.map(([name]) => name);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Switches one opencode agent back on, returning whether anything changed.
|
|
429
|
+
*
|
|
430
|
+
* This writes another tool's configuration, so it changes exactly one field
|
|
431
|
+
* and leaves the rest of the document alone. It never creates a config that
|
|
432
|
+
* was not already there.
|
|
433
|
+
*/
|
|
434
|
+
export function enableOpencodeAgent(home, name) {
|
|
435
|
+
const path = opencodeConfigPath(home);
|
|
436
|
+
const cfg = readOpencodeConfig(home);
|
|
437
|
+
if (cfg === null)
|
|
438
|
+
return false;
|
|
439
|
+
if (cfg.agent?.[name]?.disable !== true)
|
|
440
|
+
return false;
|
|
441
|
+
cfg.agent[name].disable = false;
|
|
442
|
+
writeFileSync(path, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.js","sourceRoot":"","sources":["../src/detect.ts"],"names":[],"mappings":"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"}
|
package/dist/filelock.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A mkdir-based lockfile mutex, shared by the route and usage systems that do
|
|
3
|
+
* read-modify-write on a JSON file from separate processes.
|
|
4
|
+
*
|
|
5
|
+
* Two `SessionStart` hooks can fire near-simultaneously (a user can launch
|
|
6
|
+
* sessions in different projects around the same time). If their updates to a
|
|
7
|
+
* shared file each did a plain `load`-then-`write`, one's write would silently
|
|
8
|
+
* clobber the other's — neither ever sees the other's in-flight change. This
|
|
9
|
+
* serialises those updates so the read-modify-write is atomic across processes.
|
|
10
|
+
*
|
|
11
|
+
* The lock is the creation of a sibling `<file>.lock` directory (mkdir is
|
|
12
|
+
* atomic): the winner of `mkdirSync` owns the lock. The loser polls until the
|
|
13
|
+
* owner removes it, giving up after 2s; a lock older than 5s is treated as
|
|
14
|
+
* stale (the holder crashed) and reclaimed.
|
|
15
|
+
*
|
|
16
|
+
* A lock is a renewable lease, not a fixed claim. On winning, the holder writes
|
|
17
|
+
* a unique owner token into `<lock>/owner` and refreshes the lock's mtime every
|
|
18
|
+
* 2s (well under the 5s staleness threshold) for as long as `fn()` runs, so a
|
|
19
|
+
* holder that is legitimately slow — `route.ts` awaits `cmdRoute('on'/'off')`
|
|
20
|
+
* inside the lock — is never mistaken for a crash. When `fn()` finishes the
|
|
21
|
+
* holder only deletes the lock if `<lock>/owner` still holds *its own* token:
|
|
22
|
+
* if another process reclaimed it in the meantime (a stale lock the holder
|
|
23
|
+
* failed to renew), that process's live lock is left alone.
|
|
24
|
+
*/
|
|
25
|
+
import { mkdirSync, rmSync, statSync, writeFileSync, readFileSync, utimesSync } from 'node:fs';
|
|
26
|
+
import { dirname, join } from 'node:path';
|
|
27
|
+
import { randomUUID } from 'node:crypto';
|
|
28
|
+
const RENEW_INTERVAL_MS = 2000;
|
|
29
|
+
export async function withSessionLock(file, fn) {
|
|
30
|
+
const lock = `${file}.lock`;
|
|
31
|
+
const ownerPath = join(lock, 'owner');
|
|
32
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
33
|
+
const deadline = Date.now() + 2000;
|
|
34
|
+
for (;;) {
|
|
35
|
+
try {
|
|
36
|
+
mkdirSync(lock);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
try {
|
|
41
|
+
const age = Date.now() - statSync(lock).mtimeMs;
|
|
42
|
+
if (age > 5000)
|
|
43
|
+
rmSync(lock, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
catch { /* raced with the holder releasing it */ }
|
|
46
|
+
if (Date.now() > deadline) {
|
|
47
|
+
throw new Error(`sonata: timed out waiting for lock on ${file}`);
|
|
48
|
+
}
|
|
49
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const token = randomUUID();
|
|
53
|
+
writeFileSync(ownerPath, token);
|
|
54
|
+
let timer;
|
|
55
|
+
const renew = () => {
|
|
56
|
+
try {
|
|
57
|
+
// Someone else owns it now (a reclaim wrote their token): stop touching
|
|
58
|
+
// their lock, or we would keep it alive on their behalf.
|
|
59
|
+
if (readFileSync(ownerPath, 'utf8') !== token) {
|
|
60
|
+
clearInterval(timer);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
utimesSync(lock, new Date(), new Date());
|
|
64
|
+
}
|
|
65
|
+
catch { /* lock gone — nothing left to renew */
|
|
66
|
+
clearInterval(timer);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
timer = setInterval(renew, RENEW_INTERVAL_MS);
|
|
70
|
+
try {
|
|
71
|
+
return await fn();
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
clearInterval(timer);
|
|
75
|
+
try {
|
|
76
|
+
if (readFileSync(ownerPath, 'utf8') === token) {
|
|
77
|
+
rmSync(lock, { recursive: true, force: true });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch { /* already gone */ }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=filelock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filelock.js","sourceRoot":"","sources":["../src/filelock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC/F,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,iBAAiB,GAAG,IAAI,CAAC;AAE/B,MAAM,CAAC,KAAK,UAAU,eAAe,CAAI,IAAY,EAAE,EAAwB;IAC7E,MAAM,IAAI,GAAG,GAAG,IAAI,OAAO,CAAC;IAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACtC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IACnC,SAAS,CAAC;QACR,IAAI,CAAC;YACH,SAAS,CAAC,IAAI,CAAC,CAAC;YAChB,MAAM;QACR,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC;gBAChD,IAAI,GAAG,GAAG,IAAI;oBAAE,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACjE,CAAC;YAAC,MAAM,CAAC,CAAC,wCAAwC,CAAC,CAAC;YACpD,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;gBAC1B,MAAM,IAAI,KAAK,CAAC,yCAAyC,IAAI,EAAE,CAAC,CAAC;YACnE,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC;IAC3B,aAAa,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAEhC,IAAI,KAAqC,CAAC;IAC1C,MAAM,KAAK,GAAG,GAAS,EAAE;QACvB,IAAI,CAAC;YACH,wEAAwE;YACxE,yDAAyD;YACzD,IAAI,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,KAAK,EAAE,CAAC;gBAC9C,aAAa,CAAC,KAAK,CAAC,CAAC;gBACrB,OAAO;YACT,CAAC;YACD,UAAU,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC,CAAC,uCAAuC;YAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAAC,CAAC;IAC3E,CAAC,CAAC;IACF,KAAK,GAAG,WAAW,CAAC,KAAK,EAAE,iBAAiB,CAAC,CAAC;IAE9C,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,CAAC;IACpB,CAAC;YAAS,CAAC;QACT,aAAa,CAAC,KAAK,CAAC,CAAC;QACrB,IAAI,CAAC;YACH,IAAI,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,KAAK,EAAE,CAAC;gBAC9C,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAChC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { installHook, allowSonataTools, hookCommand, readSettings, settingsPath, writeSettings, } from '../settings.js';
|
|
4
|
+
import { pruneAgents } from '../detect.js';
|
|
5
|
+
import { cmdSync } from '../commands/sync.js';
|
|
6
|
+
import { cmdRoute } from '../commands/route.js';
|
|
7
|
+
import { writeSonataKey } from '../native/credentials.js';
|
|
8
|
+
export async function apply(plan, opts, io) {
|
|
9
|
+
const { cwd, home, packageRoot } = opts;
|
|
10
|
+
// ---- credentials (first, so a partially-failed run still leaves keys behind) ----
|
|
11
|
+
// Keys typed in the wizard are stored here and nowhere earlier: a cancelled
|
|
12
|
+
// run must leave no credential behind. The gateway is printed, never the key.
|
|
13
|
+
for (const { gateway, key } of plan.keysToStore) {
|
|
14
|
+
writeSonataKey(home, gateway, key);
|
|
15
|
+
io.out(` ✓ stored the key for ${gateway}`);
|
|
16
|
+
}
|
|
17
|
+
// ---- config ----
|
|
18
|
+
mkdirSync(dirname(plan.configPath), { recursive: true });
|
|
19
|
+
writeFileSync(plan.configPath, plan.configToml);
|
|
20
|
+
io.out(` ✓ wrote ${plan.configPath}`);
|
|
21
|
+
// ---- settings (hook + allow-list) ----
|
|
22
|
+
let hookChanged = false;
|
|
23
|
+
// `runInit` resolves `allowListScope` against `env.existingHookScope`
|
|
24
|
+
// (an upgrade from the MCP-based release otherwise keeps its old
|
|
25
|
+
// mcp__sonata__* entries forever, since "hook already installed" used to
|
|
26
|
+
// mean skipping this whole block) and writes the result onto the plan.
|
|
27
|
+
if (plan.hook.allowListScope !== undefined && plan.hook.allowListScope !== 'skip') {
|
|
28
|
+
const allowListScope = plan.hook.allowListScope;
|
|
29
|
+
const path = settingsPath(allowListScope, cwd, home);
|
|
30
|
+
const cmd = hookCommand(packageRoot);
|
|
31
|
+
const withHook = plan.hook.scope !== 'skip'
|
|
32
|
+
? installHook(readSettings(path), cmd)
|
|
33
|
+
: { settings: readSettings(path), changed: false };
|
|
34
|
+
const withAllow = allowSonataTools(withHook.settings);
|
|
35
|
+
if (withHook.changed || withAllow.changed)
|
|
36
|
+
writeSettings(path, withAllow.settings);
|
|
37
|
+
hookChanged = withHook.changed;
|
|
38
|
+
if (plan.hook.scope !== 'skip') {
|
|
39
|
+
io.out(withHook.changed ? ` ✓ installed hook in ${path}` : ` · hook already present in ${path}`);
|
|
40
|
+
}
|
|
41
|
+
io.out(withAllow.changed
|
|
42
|
+
? ` ✓ allow-listed the sonata tools in ${path}`
|
|
43
|
+
: ` · sonata tools already allow-listed in ${path}`);
|
|
44
|
+
}
|
|
45
|
+
// ---- skill ----
|
|
46
|
+
mkdirSync(dirname(plan.skillPath), { recursive: true });
|
|
47
|
+
const packageSkill = join(packageRoot, 'skills', 'loop', 'SKILL.md');
|
|
48
|
+
const skillSource = existsSync(packageSkill)
|
|
49
|
+
? packageSkill
|
|
50
|
+
: join(process.cwd(), 'skills', 'loop', 'SKILL.md');
|
|
51
|
+
writeFileSync(plan.skillPath, readFileSync(skillSource));
|
|
52
|
+
io.out(` ✓ installed loop skill in ${plan.skillPath}`);
|
|
53
|
+
// ---- routing ----
|
|
54
|
+
if (plan.routing !== 'skip') {
|
|
55
|
+
await cmdRoute('auto', {
|
|
56
|
+
cwd,
|
|
57
|
+
home,
|
|
58
|
+
packageRoot,
|
|
59
|
+
scope: plan.routing,
|
|
60
|
+
});
|
|
61
|
+
io.out(` ✓ configured sonata route auto${plan.routing === 'global' ? ' --global' : ''}`);
|
|
62
|
+
}
|
|
63
|
+
// ---- sync (generates agent files) ----
|
|
64
|
+
const sync = cmdSync({ cwd: plan.syncCwd, home, agentsDir: plan.agentsDir });
|
|
65
|
+
const agentsWritten = sync.written;
|
|
66
|
+
io.out(` ✓ generated ${agentsWritten.length} agents in ${plan.agentsDir}`);
|
|
67
|
+
if (sync.skipped.length > 0) {
|
|
68
|
+
io.out('');
|
|
69
|
+
io.out(` ! ${sync.skipped.length} existing agent file(s) were NOT overwritten (not sonata-generated):`);
|
|
70
|
+
for (const f of sync.skipped.slice(0, 5))
|
|
71
|
+
io.out(` ${f}`);
|
|
72
|
+
if (sync.skipped.length > 5)
|
|
73
|
+
io.out(` … and ${sync.skipped.length - 5} more`);
|
|
74
|
+
}
|
|
75
|
+
// ---- prune (after sync, since stale files are only known now) ----
|
|
76
|
+
const stale = sync.stale;
|
|
77
|
+
let pruned = [];
|
|
78
|
+
if (stale.length > 0) {
|
|
79
|
+
io.out('');
|
|
80
|
+
io.out(` ! ${stale.length} stale agent file(s) no longer in your config:`);
|
|
81
|
+
for (const f of stale.slice(0, 5))
|
|
82
|
+
io.out(` ${f}`);
|
|
83
|
+
if (stale.length > 5)
|
|
84
|
+
io.out(` … and ${stale.length - 5} more`);
|
|
85
|
+
const remove = typeof io.prune === 'function' ? await io.prune(stale) : io.prune;
|
|
86
|
+
if (remove) {
|
|
87
|
+
pruned = pruneAgents(plan.agentsDir, stale);
|
|
88
|
+
io.out(` ✓ removed ${pruned.length} stale agent file(s)`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
io.out(' ❯ delete them by hand, or re-run with --prune');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { agentsWritten, pruned, hookChanged };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/init/apply.ts"],"names":[],"mappings":"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"}
|