docks-kit 0.17.0 → 0.17.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/AGENTS.md +37 -33
- package/README.md +1 -0
- package/cli/docs/omp-models.md +114 -58
- package/cli/src/argv.ts +38 -469
- package/cli/src/argvSurface.ts +371 -0
- package/cli/src/argvValidate.ts +122 -0
- package/cli/src/commands/docs.ts +57 -42
- package/cli/src/commands/harnesses.ts +38 -45
- package/cli/src/commands/model.ts +48 -53
- package/cli/src/commands/models.ts +24 -26
- package/cli/src/commands/omp.ts +178 -88
- package/cli/src/commands/plugins.ts +20 -20
- package/cli/src/commands/skills.ts +22 -20
- package/cli/src/commands/status.ts +87 -80
- package/cli/src/commands/sync.ts +113 -87
- package/cli/src/commands/toolchain.ts +21 -21
- package/cli/src/commands/update.ts +139 -117
- package/cli/src/efforts.ts +35 -34
- package/cli/src/engine-native/DESIGN.md +30 -30
- package/cli/src/engine-native/bun.ts +77 -55
- package/cli/src/engine-native/claudeLsp.ts +154 -0
- package/cli/src/engine-native/claudeOptionalPlugins.ts +113 -0
- package/cli/src/engine-native/claudePluginPasses.ts +348 -0
- package/cli/src/engine-native/claudeRemovals.ts +195 -0
- package/cli/src/engine-native/claudeRetired.ts +4 -4
- package/cli/src/engine-native/claudeRuntime.ts +94 -68
- package/cli/src/engine-native/claudeSettings.ts +174 -0
- package/cli/src/engine-native/claudeSettingsModifiers.ts +50 -54
- package/cli/src/engine-native/claudeSync.ts +176 -456
- package/cli/src/engine-native/codexConfig.ts +158 -0
- package/cli/src/engine-native/codexHooks.ts +91 -0
- package/cli/src/engine-native/codexPlugins.ts +290 -0
- package/cli/src/engine-native/codexStatus.ts +24 -0
- package/cli/src/engine-native/codexSync.ts +110 -568
- package/cli/src/engine-native/codexToml.ts +140 -138
- package/cli/src/engine-native/deps.ts +144 -107
- package/cli/src/engine-native/engineCtx.ts +126 -0
- package/cli/src/engine-native/exec.ts +98 -87
- package/cli/src/engine-native/failures.ts +3 -3
- package/cli/src/engine-native/harnesses.ts +62 -72
- package/cli/src/engine-native/index.ts +34 -315
- package/cli/src/engine-native/jq.ts +25 -24
- package/cli/src/engine-native/logger.ts +98 -100
- package/cli/src/engine-native/models.ts +50 -45
- package/cli/src/engine-native/modes.ts +83 -80
- package/cli/src/engine-native/ompFileDeploy.ts +111 -0
- package/cli/src/engine-native/ompMarketplace.ts +136 -0
- package/cli/src/engine-native/ompOverlay.ts +93 -53
- package/cli/src/engine-native/ompPaths.ts +49 -38
- package/cli/src/engine-native/ompPlugins.ts +170 -0
- package/cli/src/engine-native/ompSync.ts +64 -408
- package/cli/src/engine-native/ompYaml.ts +43 -41
- package/cli/src/engine-native/os/darwin.ts +5 -5
- package/cli/src/engine-native/os/index.ts +15 -15
- package/cli/src/engine-native/os/linux.ts +5 -5
- package/cli/src/engine-native/os/posix.ts +28 -24
- package/cli/src/engine-native/os/targets.ts +17 -17
- package/cli/src/engine-native/os/types.ts +31 -31
- package/cli/src/engine-native/os/windows.ts +61 -61
- package/cli/src/engine-native/parseArgs.ts +122 -372
- package/cli/src/engine-native/parseHelp.ts +66 -0
- package/cli/src/engine-native/parseModifiers.ts +222 -0
- package/cli/src/engine-native/services.ts +34 -34
- package/cli/src/engine-native/settings.ts +15 -15
- package/cli/src/engine-native/sharedTypes.d.ts +46 -0
- package/cli/src/engine-native/skillsInstall.ts +105 -0
- package/cli/src/engine-native/skillsLinks.ts +203 -0
- package/cli/src/engine-native/skillsManifest.ts +16 -0
- package/cli/src/engine-native/skillsPrune.ts +108 -0
- package/cli/src/engine-native/skillsSync.ts +28 -362
- package/cli/src/engine-native/syncConcurrency.ts +55 -0
- package/cli/src/engine-native/syncDispatch.ts +141 -0
- package/cli/src/engine-native/toolchain.ts +65 -62
- package/cli/src/engine.ts +65 -57
- package/cli/src/generated/sotPayload.ts +7 -7
- package/cli/src/kitHome.ts +44 -40
- package/cli/src/main.ts +63 -56
- package/cli/src/manifests.ts +53 -54
- package/cli/src/md.d.ts +2 -2
- package/cli/src/payload.ts +9 -9
- package/cli/src/services.ts +21 -13
- package/cli/tsconfig.json +4 -1
- package/package.json +9 -2
- package/cli/src/engine-native/claudePlugins.ts +0 -510
|
@@ -6,585 +6,305 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import {
|
|
8
8
|
appendFileSync,
|
|
9
|
-
copyFileSync,
|
|
10
9
|
existsSync,
|
|
11
10
|
mkdirSync,
|
|
12
11
|
readFileSync,
|
|
13
12
|
renameSync,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from "
|
|
18
|
-
import { bunBootstrap } from "./bun"
|
|
13
|
+
writeFileSync,
|
|
14
|
+
} from "node:fs";
|
|
15
|
+
import { bunBootstrap } from "./bun";
|
|
16
|
+
import { syncClaudeAdvisor, syncClaudeEffort, syncClaudeModel } from "./claudeSettingsModifiers";
|
|
19
17
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
} from "./
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
18
|
+
claudeRuntimePaths,
|
|
19
|
+
materializeClaudeSettings,
|
|
20
|
+
type ClaudeRuntimePaths,
|
|
21
|
+
} from "./claudeRuntime";
|
|
22
|
+
import {
|
|
23
|
+
commitClaudeSettings,
|
|
24
|
+
describeSettingsSync,
|
|
25
|
+
prepareClaudeSettings,
|
|
26
|
+
syncCompactWindow,
|
|
27
|
+
syncPermissive,
|
|
28
|
+
} from "./claudeSettings";
|
|
29
|
+
import { syncRemovals } from "./claudeRemovals";
|
|
30
|
+
import { syncPlugins } from "./claudePluginPasses";
|
|
31
|
+
import { syncOptionalPlugins } from "./claudeOptionalPlugins";
|
|
32
|
+
import { syncLspServers } from "./claudeLsp";
|
|
33
|
+
import { p, spawnProcess, writeBytesIfChanged, writeTextIfChanged } from "./exec";
|
|
34
|
+
import type { Ctx } from "./index";
|
|
35
|
+
import { deepMerge, isObject, jqStringify, parseJson, readJsonFile, type Json } from "./jq";
|
|
36
|
+
import { ExitError } from "./parseArgs";
|
|
37
|
+
import { hostOs } from "./os";
|
|
38
|
+
import { payloadBytes, payloadText } from "../payload";
|
|
39
|
+
import type { JsonObject } from "./sharedTypes";
|
|
40
|
+
export type { PreparedClaudeSettings } from "./claudeSettings";
|
|
41
|
+
export { commitClaudeSettings, prepareClaudeSettings } from "./claudeSettings";
|
|
34
42
|
|
|
35
43
|
export type ClaudeRuntimeState =
|
|
36
44
|
| { readonly kind: "ready"; readonly paths: ClaudeRuntimePaths }
|
|
37
|
-
| { readonly kind: "deferred"; readonly reason: "bun-unavailable" }
|
|
45
|
+
| { readonly kind: "deferred"; readonly reason: "bun-unavailable" };
|
|
38
46
|
|
|
39
47
|
export async function claudeSync(ctx: Ctx): Promise<ClaudeRuntimeState> {
|
|
40
|
-
const { err, warn } = ctx.services.logger
|
|
41
|
-
const claudeDir = p(ctx.home, ".claude")
|
|
48
|
+
const { err, warn } = ctx.services.logger;
|
|
49
|
+
const claudeDir = p(ctx.home, ".claude");
|
|
42
50
|
|
|
43
|
-
if (!ctx.dryRun) mkdirSync(claudeDir, { recursive: true })
|
|
51
|
+
if (!ctx.dryRun) mkdirSync(claudeDir, { recursive: true });
|
|
44
52
|
|
|
45
53
|
if (ctx.services.deps.probe("claude").state === "missing") {
|
|
46
54
|
warn(
|
|
47
|
-
`claude CLI not found - config deploys, but plugin passes are skipped. Install Claude Code: ${ctx.services.deps.spec("claude").installHint()} | docs: https://code.claude.com/docs/en/setup
|
|
48
|
-
)
|
|
55
|
+
`claude CLI not found - config deploys, but plugin passes are skipped. Install Claude Code: ${ctx.services.deps.spec("claude").installHint()} | docs: https://code.claude.com/docs/en/setup`,
|
|
56
|
+
);
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
const bun = await bunBootstrap(ctx, ctx.services)
|
|
52
|
-
const runtime: ClaudeRuntimeState =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
const bun = await bunBootstrap(ctx, ctx.services);
|
|
60
|
+
const runtime: ClaudeRuntimeState =
|
|
61
|
+
bun.kind === "ready"
|
|
62
|
+
? { kind: "ready", paths: claudeRuntimePaths(claudeDir, bun.executable) }
|
|
63
|
+
: { kind: "deferred", reason: "bun-unavailable" };
|
|
64
|
+
const template = parseJson(payloadText("SoT/.claude/settings.json"));
|
|
56
65
|
if (template === undefined) {
|
|
57
|
-
err("Embedded SoT/.claude/settings.json is not valid JSON")
|
|
58
|
-
throw new ExitError(1)
|
|
66
|
+
err("Embedded SoT/.claude/settings.json is not valid JSON");
|
|
67
|
+
throw new ExitError(1);
|
|
59
68
|
}
|
|
60
69
|
const materialized = materializeClaudeSettings(
|
|
61
70
|
template,
|
|
62
|
-
runtime.kind === "ready" ? runtime.paths : undefined
|
|
63
|
-
)
|
|
64
|
-
const prepared = prepareClaudeSettings(ctx, claudeDir, materialized)
|
|
71
|
+
runtime.kind === "ready" ? runtime.paths : undefined,
|
|
72
|
+
);
|
|
73
|
+
const prepared = prepareClaudeSettings(ctx, claudeDir, materialized);
|
|
65
74
|
|
|
66
|
-
syncClaudeRuntime(ctx, runtime)
|
|
67
|
-
syncClaudeMd(ctx, claudeDir)
|
|
75
|
+
syncClaudeRuntime(ctx, runtime);
|
|
76
|
+
syncClaudeMd(ctx, claudeDir);
|
|
68
77
|
if (ctx.dryRun) {
|
|
69
|
-
describeSettingsSync(ctx, claudeDir)
|
|
78
|
+
describeSettingsSync(ctx, claudeDir);
|
|
70
79
|
} else {
|
|
71
|
-
commitClaudeSettings(ctx, prepared)
|
|
72
|
-
}
|
|
73
|
-
syncRemovals(ctx, claudeDir, runtime)
|
|
74
|
-
syncCompactWindow(ctx, claudeDir)
|
|
75
|
-
syncPermissive(ctx, claudeDir)
|
|
76
|
-
syncClaudeModel(ctx, ctx.claudeModel)
|
|
77
|
-
syncClaudeEffort(ctx, ctx.claudeEffort)
|
|
78
|
-
syncClaudeAdvisor(ctx, ctx.claudeAdvisor)
|
|
79
|
-
syncClaudeJson(ctx)
|
|
80
|
-
await syncConnectorEnv(ctx)
|
|
81
|
-
await syncPlugins(ctx, claudeDir)
|
|
82
|
-
await syncOptionalPlugins(ctx, claudeDir)
|
|
83
|
-
await syncLspServers(ctx)
|
|
84
|
-
return runtime
|
|
80
|
+
commitClaudeSettings(ctx, prepared);
|
|
81
|
+
}
|
|
82
|
+
syncRemovals(ctx, claudeDir, runtime);
|
|
83
|
+
syncCompactWindow(ctx, claudeDir);
|
|
84
|
+
syncPermissive(ctx, claudeDir);
|
|
85
|
+
syncClaudeModel(ctx, ctx.claudeModel);
|
|
86
|
+
syncClaudeEffort(ctx, ctx.claudeEffort);
|
|
87
|
+
syncClaudeAdvisor(ctx, ctx.claudeAdvisor);
|
|
88
|
+
syncClaudeJson(ctx);
|
|
89
|
+
await syncConnectorEnv(ctx);
|
|
90
|
+
await syncPlugins(ctx, claudeDir);
|
|
91
|
+
await syncOptionalPlugins(ctx, claudeDir);
|
|
92
|
+
await syncLspServers(ctx);
|
|
93
|
+
return runtime;
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
// ----------------------------------------------------------- runtime ----
|
|
88
97
|
|
|
89
98
|
function syncClaudeRuntime(ctx: Ctx, runtime: ClaudeRuntimeState): void {
|
|
90
|
-
const { change, echo, verbose, warn } = ctx.services.logger
|
|
99
|
+
const { change, echo, verbose, warn } = ctx.services.logger;
|
|
91
100
|
if (runtime.kind === "deferred") {
|
|
92
|
-
warn(
|
|
93
|
-
|
|
101
|
+
warn(
|
|
102
|
+
"Bun unavailable — Claude statusline/hooks migration deferred; install Bun, then re-run sync claude",
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
94
105
|
}
|
|
95
106
|
if (ctx.dryRun) {
|
|
96
|
-
echo("[dry-run] install statusline.mjs, session-start.mjs, notify.mjs, notification.mp3")
|
|
97
|
-
return
|
|
107
|
+
echo("[dry-run] install statusline.mjs, session-start.mjs, notify.mjs, notification.mp3");
|
|
108
|
+
return;
|
|
98
109
|
}
|
|
99
110
|
|
|
100
|
-
mkdirSync(p(ctx.home, ".claude", "bin"), { recursive: true })
|
|
101
|
-
let changed = false
|
|
111
|
+
mkdirSync(p(ctx.home, ".claude", "bin"), { recursive: true });
|
|
112
|
+
let changed = false;
|
|
102
113
|
for (const [path, source] of [
|
|
103
114
|
[runtime.paths.statusline, "SoT/.claude/bin/statusline.mjs"],
|
|
104
115
|
[runtime.paths.sessionStart, "SoT/.claude/bin/session-start.mjs"],
|
|
105
|
-
[runtime.paths.notify, "SoT/.claude/bin/notify.mjs"]
|
|
116
|
+
[runtime.paths.notify, "SoT/.claude/bin/notify.mjs"],
|
|
106
117
|
] as const) {
|
|
107
|
-
if (writeTextIfChanged(path, payloadText(source))) changed = true
|
|
118
|
+
if (writeTextIfChanged(path, payloadText(source))) changed = true;
|
|
108
119
|
}
|
|
109
|
-
if (
|
|
120
|
+
if (
|
|
121
|
+
writeBytesIfChanged(
|
|
122
|
+
p(ctx.home, ".claude", "notification.mp3"),
|
|
123
|
+
payloadBytes("notification.mp3"),
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
changed = true;
|
|
110
127
|
if (changed) {
|
|
111
|
-
change("Claude runtime synced (statusline, session-start, notify, notification)")
|
|
112
|
-
ctx.nextStepTriggers.claudeRestart = true
|
|
128
|
+
change("Claude runtime synced (statusline, session-start, notify, notification)");
|
|
129
|
+
ctx.nextStepTriggers.claudeRestart = true;
|
|
113
130
|
} else {
|
|
114
|
-
verbose("Claude runtime already in sync (statusline, session-start, notify, notification)")
|
|
131
|
+
verbose("Claude runtime already in sync (statusline, session-start, notify, notification)");
|
|
115
132
|
}
|
|
116
133
|
}
|
|
117
134
|
|
|
118
135
|
function syncClaudeMd(ctx: Ctx, claudeDir: string): void {
|
|
119
|
-
const { change, echo, verbose } = ctx.services.logger
|
|
136
|
+
const { change, echo, verbose } = ctx.services.logger;
|
|
120
137
|
if (ctx.dryRun) {
|
|
121
|
-
echo("[dry-run] cp SoT/.claude/CLAUDE.md -> ~/.claude/CLAUDE.md")
|
|
122
|
-
return
|
|
138
|
+
echo("[dry-run] cp SoT/.claude/CLAUDE.md -> ~/.claude/CLAUDE.md");
|
|
139
|
+
return;
|
|
123
140
|
}
|
|
124
141
|
|
|
125
|
-
const source = payloadText("SoT/.claude/CLAUDE.md")
|
|
142
|
+
const source = payloadText("SoT/.claude/CLAUDE.md");
|
|
126
143
|
if (writeTextIfChanged(p(claudeDir, "CLAUDE.md"), source)) {
|
|
127
|
-
change("CLAUDE.md synced")
|
|
128
|
-
} else {
|
|
129
|
-
verbose("CLAUDE.md already in sync")
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ------------------------------------------------------------- settings ----
|
|
134
|
-
|
|
135
|
-
export interface PreparedClaudeSettings {
|
|
136
|
-
readonly path: string
|
|
137
|
-
readonly bytes: string
|
|
138
|
-
readonly previousBytes: string | undefined
|
|
139
|
-
readonly changed: boolean
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function assertMaterializedSettings(bytes: string): void {
|
|
143
|
-
if (bytes.includes("__DOCKS_KIT_")) throw new Error("Claude settings contain unresolved runtime sentinels")
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Build the candidate settings bytes before the readiness-gated runtime cutover mutates disk. */
|
|
147
|
-
export function prepareClaudeSettings(ctx: Ctx, claudeDir: string, repo: Json): PreparedClaudeSettings {
|
|
148
|
-
const path = p(claudeDir, "settings.json")
|
|
149
|
-
if (!existsSync(path)) {
|
|
150
|
-
const bytes = jqStringify(repo)
|
|
151
|
-
assertMaterializedSettings(bytes)
|
|
152
|
-
return { path, bytes, previousBytes: undefined, changed: true }
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const previousBytes = readFileSync(path, "utf8")
|
|
156
|
-
const user = parseJson(previousBytes)
|
|
157
|
-
if (user === undefined || !isObject(user)) {
|
|
158
|
-
const reason = user === undefined ? "is not valid JSON" : "must contain a JSON object"
|
|
159
|
-
ctx.services.logger.err(`Aborting sync: ${path} ${reason}. Fix it manually or delete it to reinstall.`)
|
|
160
|
-
throw new ExitError(1)
|
|
161
|
-
}
|
|
162
|
-
const merged = ctx.reconcile ? reconcileSettings(repo, user) : mergeSettings(repo, user)
|
|
163
|
-
const bytes = jqStringify(merged)
|
|
164
|
-
assertMaterializedSettings(bytes)
|
|
165
|
-
return { path, bytes, previousBytes, changed: bytes !== previousBytes }
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** Commit a fully prepared document; callers must finish runtime preparation first. */
|
|
169
|
-
export function commitClaudeSettings(ctx: Ctx, prepared: PreparedClaudeSettings): void {
|
|
170
|
-
const { change, verbose } = ctx.services.logger
|
|
171
|
-
if (!prepared.changed) {
|
|
172
|
-
verbose("Settings already in sync")
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (prepared.previousBytes !== undefined) copyFileSync(prepared.path, `${prepared.path}.bak`)
|
|
177
|
-
writeFileSync(`${prepared.path}.tmp`, prepared.bytes)
|
|
178
|
-
renameSync(`${prepared.path}.tmp`, prepared.path)
|
|
179
|
-
ctx.nextStepTriggers.claudeRestart = true
|
|
180
|
-
if (prepared.previousBytes === undefined) {
|
|
181
|
-
change("Settings installed")
|
|
182
|
-
} else if (ctx.reconcile) {
|
|
183
|
-
change("Settings reconciled (backup at settings.json.bak; user-only keys preserved, permissions arrays replaced by SoT)")
|
|
144
|
+
change("CLAUDE.md synced");
|
|
184
145
|
} else {
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function describeSettingsSync(ctx: Ctx, claudeDir: string): void {
|
|
190
|
-
const { echo } = ctx.services.logger
|
|
191
|
-
const repoSettings = payloadDisplayPath("SoT/.claude/settings.json")
|
|
192
|
-
const userSettings = p(claudeDir, "settings.json")
|
|
193
|
-
|
|
194
|
-
if (!existsSync(userSettings)) {
|
|
195
|
-
echo(`[dry-run] install ${repoSettings} -> ${userSettings}`)
|
|
196
|
-
} else if (ctx.reconcile) {
|
|
197
|
-
echo(`[dry-run] reconcile ${repoSettings} -> ${userSettings} (SoT keys win; permissions arrays replaced; user-only keys preserved)`)
|
|
198
|
-
} else {
|
|
199
|
-
echo(`[dry-run] merge ${repoSettings} -> ${userSettings} (SoT keys win; permissions arrays unioned; user-only keys preserved)`)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/** Shared shape of the three jq-edit modifiers (compact window, permissive). */
|
|
204
|
-
function jqEditSettings(ctx: Ctx, claudeDir: string, tag: string, edit: (doc: Json) => void): boolean {
|
|
205
|
-
const { err, warn } = ctx.services.logger
|
|
206
|
-
const userSettings = p(claudeDir, "settings.json")
|
|
207
|
-
if (!existsSync(userSettings)) {
|
|
208
|
-
warn(`(${tag}) ${userSettings} missing — skipped`)
|
|
209
|
-
return false
|
|
210
|
-
}
|
|
211
|
-
const before = readFileSync(userSettings, "utf8")
|
|
212
|
-
const doc = parseJson(before)
|
|
213
|
-
if (doc === undefined) {
|
|
214
|
-
err(`(${tag}) ${userSettings} is not valid JSON — skipped`)
|
|
215
|
-
return false
|
|
216
|
-
}
|
|
217
|
-
edit(doc)
|
|
218
|
-
const out = jqStringify(doc)
|
|
219
|
-
if (out === before) return false
|
|
220
|
-
writeFileSync(`${userSettings}.tmp`, out)
|
|
221
|
-
renameSync(`${userSettings}.tmp`, userSettings)
|
|
222
|
-
return true
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function syncCompactWindow(ctx: Ctx, claudeDir: string): void {
|
|
226
|
-
const { change, echo, verbose } = ctx.services.logger
|
|
227
|
-
if (ctx.claudeCompactWindow === "") return
|
|
228
|
-
|
|
229
|
-
if (ctx.dryRun) {
|
|
230
|
-
echo(`[dry-run] (--claude-compact-window) set env.CLAUDE_CODE_AUTO_COMPACT_WINDOW=${ctx.claudeCompactWindow} in ${p(claudeDir, "settings.json")}`)
|
|
231
|
-
return
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const changed = jqEditSettings(ctx, claudeDir, "--claude-compact-window", (doc) => {
|
|
235
|
-
if (!isObject(doc)) return
|
|
236
|
-
const env = isObject(doc["env"]) ? doc["env"] : {}
|
|
237
|
-
env["CLAUDE_CODE_AUTO_COMPACT_WINDOW"] = ctx.claudeCompactWindow
|
|
238
|
-
doc["env"] = env
|
|
239
|
-
})
|
|
240
|
-
if (changed) {
|
|
241
|
-
change(`Compact window: set to ${ctx.claudeCompactWindow} tokens in deployed settings (SoT and model unchanged; flag-less sync reverts)`)
|
|
242
|
-
ctx.nextStepTriggers.claudeRestart = true
|
|
243
|
-
}
|
|
244
|
-
else verbose(`Compact window: already set to ${ctx.claudeCompactWindow} tokens in deployed settings`)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function syncPermissive(ctx: Ctx, claudeDir: string): void {
|
|
248
|
-
const { change, echo, verbose } = ctx.services.logger
|
|
249
|
-
if (!ctx.claudePermissive) return
|
|
250
|
-
|
|
251
|
-
if (ctx.dryRun) {
|
|
252
|
-
echo(`[dry-run] (--claude-permissive) empty permissions.ask and permissions.deny in ${p(claudeDir, "settings.json")}`)
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const changed = jqEditSettings(ctx, claudeDir, "--claude-permissive", (doc) => {
|
|
257
|
-
if (!isObject(doc)) return
|
|
258
|
-
const permissions = isObject(doc["permissions"]) ? doc["permissions"] : {}
|
|
259
|
-
permissions["ask"] = []
|
|
260
|
-
permissions["deny"] = []
|
|
261
|
-
doc["permissions"] = permissions
|
|
262
|
-
})
|
|
263
|
-
if (changed) {
|
|
264
|
-
change("Permissive mode: permissions.ask/deny emptied in deployed settings (sandbox use; SoT unchanged)")
|
|
265
|
-
ctx.nextStepTriggers.claudeRestart = true
|
|
146
|
+
verbose("CLAUDE.md already in sync");
|
|
266
147
|
}
|
|
267
|
-
else verbose("Permissive mode: permissions.ask/deny already empty in deployed settings")
|
|
268
148
|
}
|
|
269
149
|
|
|
270
150
|
// ---------------------------------------------------------- claude.json ----
|
|
271
151
|
|
|
272
152
|
function syncClaudeJson(ctx: Ctx): void {
|
|
273
|
-
const { change, echo, err, verbose } = ctx.services.logger
|
|
274
|
-
const claudeJson = p(ctx.home, ".claude.json")
|
|
153
|
+
const { change, echo, err, verbose } = ctx.services.logger;
|
|
154
|
+
const claudeJson = p(ctx.home, ".claude.json");
|
|
275
155
|
|
|
276
|
-
const mcp: Json | undefined = parseJson(payloadText("SoT/.claude/mcp-servers.json"))
|
|
277
|
-
const haveMcp = mcp !== undefined
|
|
156
|
+
const mcp: Json | undefined = parseJson(payloadText("SoT/.claude/mcp-servers.json"));
|
|
157
|
+
const haveMcp = mcp !== undefined;
|
|
278
158
|
|
|
279
159
|
if (ctx.dryRun) {
|
|
280
|
-
echo("[dry-run] set showTurnDuration=true in ~/.claude.json")
|
|
281
|
-
if (haveMcp)
|
|
282
|
-
|
|
160
|
+
echo("[dry-run] set showTurnDuration=true in ~/.claude.json");
|
|
161
|
+
if (haveMcp)
|
|
162
|
+
echo("[dry-run] merge mcpServers from SoT/.claude/mcp-servers.json into ~/.claude.json");
|
|
163
|
+
return;
|
|
283
164
|
}
|
|
284
165
|
|
|
285
|
-
const applyFilter = (doc:
|
|
286
|
-
doc["showTurnDuration"] = true
|
|
166
|
+
const applyFilter = (doc: JsonObject): void => {
|
|
167
|
+
doc["showTurnDuration"] = true;
|
|
287
168
|
if (haveMcp) {
|
|
288
|
-
const sotServers = isObject(mcp!) ? mcp!["mcpServers"] ?? {} : {}
|
|
289
|
-
doc["mcpServers"] = deepMerge(
|
|
169
|
+
const sotServers = isObject(mcp!) ? (mcp!["mcpServers"] ?? {}) : {};
|
|
170
|
+
doc["mcpServers"] = deepMerge(
|
|
171
|
+
isObject(doc["mcpServers"]) ? doc["mcpServers"] : {},
|
|
172
|
+
sotServers,
|
|
173
|
+
);
|
|
290
174
|
}
|
|
291
|
-
}
|
|
175
|
+
};
|
|
292
176
|
|
|
293
|
-
let changed = true
|
|
177
|
+
let changed = true;
|
|
294
178
|
if (existsSync(claudeJson)) {
|
|
295
|
-
const before = readFileSync(claudeJson, "utf8")
|
|
296
|
-
const doc = parseJson(before)
|
|
179
|
+
const before = readFileSync(claudeJson, "utf8");
|
|
180
|
+
const doc = parseJson(before);
|
|
297
181
|
if (doc === undefined || !isObject(doc)) {
|
|
298
|
-
const reason = doc === undefined ? "not valid JSON" : "root must be a JSON object"
|
|
299
|
-
err(`Skipping ~/.claude.json edit: ${reason}. Fix or delete it.`)
|
|
300
|
-
return
|
|
182
|
+
const reason = doc === undefined ? "not valid JSON" : "root must be a JSON object";
|
|
183
|
+
err(`Skipping ~/.claude.json edit: ${reason}. Fix or delete it.`);
|
|
184
|
+
return;
|
|
301
185
|
}
|
|
302
|
-
const obj = doc
|
|
303
|
-
applyFilter(obj)
|
|
304
|
-
const out = jqStringify(obj)
|
|
186
|
+
const obj = doc;
|
|
187
|
+
applyFilter(obj);
|
|
188
|
+
const out = jqStringify(obj);
|
|
305
189
|
if (out === before) {
|
|
306
|
-
changed = false
|
|
190
|
+
changed = false;
|
|
307
191
|
} else {
|
|
308
|
-
writeFileSync(`${claudeJson}.tmp`, out)
|
|
309
|
-
renameSync(`${claudeJson}.tmp`, claudeJson)
|
|
192
|
+
writeFileSync(`${claudeJson}.tmp`, out);
|
|
193
|
+
renameSync(`${claudeJson}.tmp`, claudeJson);
|
|
310
194
|
}
|
|
311
195
|
} else {
|
|
312
|
-
const obj:
|
|
313
|
-
applyFilter(obj)
|
|
314
|
-
writeFileSync(claudeJson, jqStringify(obj))
|
|
196
|
+
const obj: JsonObject = {};
|
|
197
|
+
applyFilter(obj);
|
|
198
|
+
writeFileSync(claudeJson, jqStringify(obj));
|
|
315
199
|
}
|
|
316
200
|
if (changed) {
|
|
317
|
-
change(`~/.claude.json updated (showTurnDuration${haveMcp ? ", mcpServers" : ""})`)
|
|
318
|
-
ctx.nextStepTriggers.claudeRestart = true
|
|
319
|
-
}
|
|
320
|
-
|
|
201
|
+
change(`~/.claude.json updated (showTurnDuration${haveMcp ? ", mcpServers" : ""})`);
|
|
202
|
+
ctx.nextStepTriggers.claudeRestart = true;
|
|
203
|
+
} else
|
|
204
|
+
verbose(`~/.claude.json already in sync (showTurnDuration${haveMcp ? ", mcpServers" : ""})`);
|
|
321
205
|
}
|
|
322
206
|
|
|
323
207
|
// -------------------------------------------------------- connector env ----
|
|
324
208
|
|
|
325
209
|
async function syncConnectorEnv(ctx: Ctx): Promise<void> {
|
|
326
|
-
const { change, echo, verbose, warn } = ctx.services.logger
|
|
327
|
-
const name = "ENABLE_CLAUDEAI_MCP_SERVERS"
|
|
328
|
-
const setting = hostOs().environmentSetting(name, "false")
|
|
210
|
+
const { change, echo, verbose, warn } = ctx.services.logger;
|
|
211
|
+
const name = "ENABLE_CLAUDEAI_MCP_SERVERS";
|
|
212
|
+
const setting = hostOs().environmentSetting(name, "false");
|
|
329
213
|
|
|
330
214
|
switch (setting.kind) {
|
|
331
215
|
case "profile": {
|
|
332
|
-
const marker = "# docks-kit: disable claude.ai cloud MCP connectors (set =true to keep them)"
|
|
333
|
-
const candidates = setting.candidates.map((candidate) => p(ctx.home, candidate))
|
|
216
|
+
const marker = "# docks-kit: disable claude.ai cloud MCP connectors (set =true to keep them)";
|
|
217
|
+
const candidates = setting.candidates.map((candidate) => p(ctx.home, candidate));
|
|
334
218
|
|
|
335
219
|
for (const f of candidates) {
|
|
336
220
|
if (existsSync(f) && readFileSync(f, "utf8").includes(name)) {
|
|
337
221
|
if (ctx.dryRun) {
|
|
338
|
-
echo(`[dry-run] ${name} already in ${f} — would skip`)
|
|
222
|
+
echo(`[dry-run] ${name} already in ${f} — would skip`);
|
|
339
223
|
} else {
|
|
340
|
-
verbose(`claude.ai connectors: ${name} already set in ${f} (left as-is)`)
|
|
224
|
+
verbose(`claude.ai connectors: ${name} already set in ${f} (left as-is)`);
|
|
341
225
|
}
|
|
342
|
-
return
|
|
226
|
+
return;
|
|
343
227
|
}
|
|
344
228
|
}
|
|
345
229
|
|
|
346
|
-
const target = p(ctx.home, setting.target(process.env["SHELL"]))
|
|
230
|
+
const target = p(ctx.home, setting.target(process.env["SHELL"]));
|
|
347
231
|
if (ctx.dryRun) {
|
|
348
|
-
echo(`[dry-run] append '${setting.line}' to ${target}`)
|
|
349
|
-
return
|
|
232
|
+
echo(`[dry-run] append '${setting.line}' to ${target}`);
|
|
233
|
+
return;
|
|
350
234
|
}
|
|
351
235
|
|
|
352
|
-
appendFileSync(target, `\n${marker}\n${setting.line}\n`)
|
|
353
|
-
change(`claude.ai connectors disabled via ${target} (start a new shell to apply)`)
|
|
354
|
-
ctx.nextStepTriggers.claudeRestart = true
|
|
355
|
-
return
|
|
236
|
+
appendFileSync(target, `\n${marker}\n${setting.line}\n`);
|
|
237
|
+
change(`claude.ai connectors disabled via ${target} (start a new shell to apply)`);
|
|
238
|
+
ctx.nextStepTriggers.claudeRestart = true;
|
|
239
|
+
return;
|
|
356
240
|
}
|
|
357
241
|
case "command": {
|
|
358
|
-
const existing = await spawnProcess(setting.probe.command, setting.probe.args, {
|
|
242
|
+
const existing = await spawnProcess(setting.probe.command, setting.probe.args, {
|
|
243
|
+
stdio: "ignore",
|
|
244
|
+
});
|
|
359
245
|
if (existing.error === undefined && existing.exitCode === 0) {
|
|
360
|
-
if (ctx.dryRun) echo(`[dry-run] ${name} already in ${setting.location} — would skip`)
|
|
361
|
-
else
|
|
362
|
-
|
|
246
|
+
if (ctx.dryRun) echo(`[dry-run] ${name} already in ${setting.location} — would skip`);
|
|
247
|
+
else
|
|
248
|
+
verbose(`claude.ai connectors: ${name} already set in ${setting.location} (left as-is)`);
|
|
249
|
+
return;
|
|
363
250
|
}
|
|
364
251
|
|
|
365
|
-
const applyCommand = [setting.apply.command, ...setting.apply.args].join(" ")
|
|
252
|
+
const applyCommand = [setting.apply.command, ...setting.apply.args].join(" ");
|
|
366
253
|
if (ctx.dryRun) {
|
|
367
|
-
echo(`[dry-run] ${applyCommand} (${setting.location})`)
|
|
368
|
-
return
|
|
254
|
+
echo(`[dry-run] ${applyCommand} (${setting.location})`);
|
|
255
|
+
return;
|
|
369
256
|
}
|
|
370
257
|
|
|
371
|
-
const applied = await spawnProcess(setting.apply.command, setting.apply.args, {
|
|
258
|
+
const applied = await spawnProcess(setting.apply.command, setting.apply.args, {
|
|
259
|
+
stdio: "ignore",
|
|
260
|
+
});
|
|
372
261
|
if (applied.error === undefined && applied.exitCode === 0) {
|
|
373
|
-
change(
|
|
374
|
-
|
|
262
|
+
change(
|
|
263
|
+
`claude.ai connectors disabled via ${setting.apply.command} (open a new terminal to apply)`,
|
|
264
|
+
);
|
|
265
|
+
ctx.nextStepTriggers.claudeRestart = true;
|
|
375
266
|
} else {
|
|
376
|
-
warn(`${applyCommand} failed — ${setting.manualHint}`)
|
|
267
|
+
warn(`${applyCommand} failed — ${setting.manualHint}`);
|
|
377
268
|
}
|
|
378
|
-
return
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// ------------------------------------------------------------- removals ----
|
|
384
|
-
|
|
385
|
-
const REMOVED_MANIFEST = {
|
|
386
|
-
hooks: ["disable-claudeai-connectors.sh"],
|
|
387
|
-
files: ["alert_bubble.mp3"],
|
|
388
|
-
settingsKeys: [
|
|
389
|
-
"showTurnDuration",
|
|
390
|
-
"advisorModel",
|
|
391
|
-
"env.CLAUDE_CODE_SUBAGENT_MODEL",
|
|
392
|
-
"env.ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
393
|
-
"env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE",
|
|
394
|
-
"env.CLAUDE_CODE_DISABLE_1M_CONTEXT",
|
|
395
|
-
"env.CLAUDE_CODE_FORK_SUBAGENT",
|
|
396
|
-
"env.CLAUDE_CODE_EFFORT_LEVEL",
|
|
397
|
-
"enabledPlugins.effect-kit@docks",
|
|
398
|
-
"enabledPlugins.session-relay@docks",
|
|
399
|
-
"hooks.PreToolUse"
|
|
400
|
-
],
|
|
401
|
-
permissionRules: {
|
|
402
|
-
allow: ["Write(./)", "Bash(rtk *)", ...RETIRED_PERMISSION_RULES.allow],
|
|
403
|
-
deny: [
|
|
404
|
-
"Write(**/.env)",
|
|
405
|
-
"Write(**/.env.local)",
|
|
406
|
-
"Write(**/secrets/**)",
|
|
407
|
-
...RETIRED_PERMISSION_RULES.deny
|
|
408
|
-
],
|
|
409
|
-
ask: [...RETIRED_PERMISSION_RULES.ask]
|
|
410
|
-
},
|
|
411
|
-
claudeJsonKeys: [] as Array<string>,
|
|
412
|
-
/** Home-relative artifacts the kit installed outside ~/.claude. */
|
|
413
|
-
homeFiles: [".local/bin/effect-solutions", ".local/bin/session-relay"],
|
|
414
|
-
runtimeReady: {
|
|
415
|
-
hooks: ["notify.sh"],
|
|
416
|
-
files: ["statusline.sh", "fetch-usage.sh"],
|
|
417
|
-
settingsKeys: ["hooks.Stop"]
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/** claude::_prune_json_keys — present-count; deletes when !dryRun. */
|
|
422
|
-
function pruneJsonKeys(ctx: Ctx, file: string, keys: Array<string>): number {
|
|
423
|
-
if (keys.length === 0 || !existsSync(file)) return 0
|
|
424
|
-
const doc = parseJson(readFileSync(file, "utf8"))
|
|
425
|
-
if (doc === undefined) return 0
|
|
426
|
-
|
|
427
|
-
const hasPath = (root: Json, path: Array<string>): boolean => {
|
|
428
|
-
let cur: Json = root
|
|
429
|
-
for (const seg of path.slice(0, -1)) {
|
|
430
|
-
if (!isObject(cur) || cur[seg] === undefined) return false
|
|
431
|
-
cur = cur[seg]!
|
|
432
|
-
}
|
|
433
|
-
return isObject(cur) && Object.prototype.hasOwnProperty.call(cur, path[path.length - 1]!)
|
|
434
|
-
}
|
|
435
|
-
const presentKeys = keys.filter((k) => hasPath(doc, k.split(".")))
|
|
436
|
-
if (presentKeys.length === 0) return 0
|
|
437
|
-
|
|
438
|
-
if (!ctx.dryRun) {
|
|
439
|
-
for (const k of presentKeys) {
|
|
440
|
-
const path = k.split(".")
|
|
441
|
-
let cur: Json = doc
|
|
442
|
-
for (const seg of path.slice(0, -1)) {
|
|
443
|
-
if (!isObject(cur)) break
|
|
444
|
-
cur = cur[seg]!
|
|
445
|
-
}
|
|
446
|
-
if (isObject(cur)) delete cur[path[path.length - 1]!]
|
|
447
|
-
}
|
|
448
|
-
writeFileSync(`${file}.tmp`, jqStringify(doc))
|
|
449
|
-
renameSync(`${file}.tmp`, file)
|
|
450
|
-
}
|
|
451
|
-
return presentKeys.length
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
function prunePermissionRules(
|
|
455
|
-
ctx: Ctx,
|
|
456
|
-
file: string,
|
|
457
|
-
rules: Readonly<Record<"allow" | "deny" | "ask", ReadonlyArray<string>>>
|
|
458
|
-
): number {
|
|
459
|
-
if (!existsSync(file)) return 0
|
|
460
|
-
const doc = parseJson(readFileSync(file, "utf8"))
|
|
461
|
-
if (doc === undefined || !isObject(doc) || !isObject(doc["permissions"])) return 0
|
|
462
|
-
const permissions = doc["permissions"]
|
|
463
|
-
let present = 0
|
|
464
|
-
for (const key of ["allow", "deny", "ask"] as const) {
|
|
465
|
-
const values = permissions[key]
|
|
466
|
-
if (!Array.isArray(values)) continue
|
|
467
|
-
const removed = new Set(rules[key])
|
|
468
|
-
present += rules[key].filter((rule) => values.includes(rule)).length
|
|
469
|
-
if (!ctx.dryRun) permissions[key] = values.filter((value) => typeof value !== "string" || !removed.has(value))
|
|
470
|
-
}
|
|
471
|
-
if (present > 0 && !ctx.dryRun) {
|
|
472
|
-
writeFileSync(`${file}.tmp`, jqStringify(doc))
|
|
473
|
-
renameSync(`${file}.tmp`, file)
|
|
474
|
-
}
|
|
475
|
-
return present
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
function syncRemovals(ctx: Ctx, claudeDir: string, runtime: ClaudeRuntimeState): void {
|
|
479
|
-
const { change, echo } = ctx.services.logger
|
|
480
|
-
let hooksRemoved = 0
|
|
481
|
-
let filesRemoved = 0
|
|
482
|
-
const hooks = [
|
|
483
|
-
...REMOVED_MANIFEST.hooks,
|
|
484
|
-
...(runtime.kind === "ready" ? REMOVED_MANIFEST.runtimeReady.hooks : [])
|
|
485
|
-
]
|
|
486
|
-
const files = [
|
|
487
|
-
...REMOVED_MANIFEST.files,
|
|
488
|
-
...(runtime.kind === "ready" ? REMOVED_MANIFEST.runtimeReady.files : [])
|
|
489
|
-
]
|
|
490
|
-
const settingsKeys = [
|
|
491
|
-
...REMOVED_MANIFEST.settingsKeys.filter((key) => key !== "advisorModel" || ctx.claudeAdvisor === ""),
|
|
492
|
-
...(runtime.kind === "ready" ? REMOVED_MANIFEST.runtimeReady.settingsKeys : [])
|
|
493
|
-
]
|
|
494
|
-
|
|
495
|
-
for (const name of hooks) {
|
|
496
|
-
const path = p(claudeDir, "hooks", name)
|
|
497
|
-
if (!existsSync(path)) continue
|
|
498
|
-
if (ctx.dryRun) {
|
|
499
|
-
echo(`[dry-run] rm ${path}`)
|
|
500
|
-
} else {
|
|
501
|
-
rmSync(path, { force: true })
|
|
502
|
-
hooksRemoved++
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
if (!ctx.dryRun && hooksRemoved > 0) {
|
|
506
|
-
try {
|
|
507
|
-
rmdirSync(p(claudeDir, "hooks"))
|
|
508
|
-
} catch {
|
|
509
|
-
// Preserve a non-empty user hooks directory.
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
for (const rel of files) {
|
|
514
|
-
const path = p(claudeDir, rel)
|
|
515
|
-
if (!existsSync(path)) continue
|
|
516
|
-
if (ctx.dryRun) {
|
|
517
|
-
echo(`[dry-run] rm ${path}`)
|
|
518
|
-
} else {
|
|
519
|
-
rmSync(path, { force: true })
|
|
520
|
-
filesRemoved++
|
|
269
|
+
return;
|
|
521
270
|
}
|
|
522
271
|
}
|
|
523
|
-
|
|
524
|
-
for (const rel of REMOVED_MANIFEST.homeFiles) {
|
|
525
|
-
const path = p(ctx.home, rel)
|
|
526
|
-
if (!existsSync(path)) continue
|
|
527
|
-
if (ctx.dryRun) {
|
|
528
|
-
echo(`[dry-run] rm ${path}`)
|
|
529
|
-
} else {
|
|
530
|
-
rmSync(path, { force: true })
|
|
531
|
-
filesRemoved++
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
const skeys = pruneJsonKeys(ctx, p(claudeDir, "settings.json"), settingsKeys)
|
|
536
|
-
const permissionRules = prunePermissionRules(
|
|
537
|
-
ctx,
|
|
538
|
-
p(claudeDir, "settings.json"),
|
|
539
|
-
REMOVED_MANIFEST.permissionRules
|
|
540
|
-
)
|
|
541
|
-
const cjkeys = pruneJsonKeys(ctx, p(ctx.home, ".claude.json"), REMOVED_MANIFEST.claudeJsonKeys)
|
|
542
|
-
|
|
543
|
-
if (ctx.dryRun) {
|
|
544
|
-
if (skeys > 0) echo(`[dry-run] del ${skeys} stale key(s) from ${p(claudeDir, "settings.json")}`)
|
|
545
|
-
if (permissionRules > 0) {
|
|
546
|
-
echo(`[dry-run] del ${permissionRules} stale permission rule(s) from ${p(claudeDir, "settings.json")}`)
|
|
547
|
-
}
|
|
548
|
-
if (cjkeys > 0) echo(`[dry-run] del ${cjkeys} stale key(s) from ${p(ctx.home, ".claude.json")}`)
|
|
549
|
-
return
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (hooksRemoved + filesRemoved + skeys + permissionRules + cjkeys > 0) {
|
|
553
|
-
const permissionSummary = permissionRules > 0 ? `, permission rules: ${permissionRules}` : ""
|
|
554
|
-
change(`Pruned stale artifacts (hooks: ${hooksRemoved}, files: ${filesRemoved}, settings keys: ${skeys}, claude.json keys: ${cjkeys}${permissionSummary})`)
|
|
555
|
-
ctx.nextStepTriggers.claudeRestart = true
|
|
556
|
-
}
|
|
557
272
|
}
|
|
558
273
|
|
|
559
274
|
// -------------------------------------------------------------- summary ----
|
|
560
275
|
|
|
561
276
|
export function claudeSummary(ctx: Ctx, runtime: ClaudeRuntimeState): void {
|
|
562
|
-
const { echo } = ctx.services.logger
|
|
563
|
-
const claudeDir = p(ctx.home, ".claude")
|
|
564
|
-
echo(`Claude: ${claudeDir}`)
|
|
277
|
+
const { echo } = ctx.services.logger;
|
|
278
|
+
const claudeDir = p(ctx.home, ".claude");
|
|
279
|
+
echo(`Claude: ${claudeDir}`);
|
|
565
280
|
if (!ctx.dryRun) {
|
|
566
281
|
if (runtime.kind === "ready") {
|
|
567
|
-
echo("Hooks: Bun (statusline, session-start, notify)")
|
|
282
|
+
echo("Hooks: Bun (statusline, session-start, notify)");
|
|
568
283
|
} else {
|
|
569
|
-
echo(
|
|
284
|
+
echo(
|
|
285
|
+
"Hooks: migration deferred (Bun unavailable; existing hook/statusline settings preserved)",
|
|
286
|
+
);
|
|
570
287
|
}
|
|
571
288
|
if (ctx.services.deps.probe("claude").state === "present") {
|
|
572
|
-
const installed = readJsonFile(p(claudeDir, "plugins", "installed_plugins.json"))
|
|
573
|
-
const count =
|
|
574
|
-
|
|
289
|
+
const installed = readJsonFile(p(claudeDir, "plugins", "installed_plugins.json"));
|
|
290
|
+
const count =
|
|
291
|
+
installed !== undefined && isObject(installed) && isObject(installed["plugins"])
|
|
292
|
+
? Object.keys(installed["plugins"]).length
|
|
293
|
+
: 0;
|
|
294
|
+
echo(`Plugins: ${count} installed (from SoT enabledPlugins + Anthropic auto-installs)`);
|
|
575
295
|
} else {
|
|
576
|
-
echo("Plugins: skipped - claude CLI not installed")
|
|
296
|
+
echo("Plugins: skipped - claude CLI not installed");
|
|
577
297
|
}
|
|
578
298
|
}
|
|
579
299
|
}
|
|
580
300
|
|
|
581
301
|
export function claudeNextSteps(ctx: Ctx): Array<string> {
|
|
582
|
-
const lines: Array<string> = []
|
|
302
|
+
const lines: Array<string> = [];
|
|
583
303
|
if (ctx.verbose || ctx.nextStepTriggers.claudePlugins) {
|
|
584
|
-
lines.push("In a Claude Code session, run /reload-plugins to pick up newly installed plugins.")
|
|
304
|
+
lines.push("In a Claude Code session, run /reload-plugins to pick up newly installed plugins.");
|
|
585
305
|
}
|
|
586
306
|
if (ctx.verbose || ctx.nextStepTriggers.claudeRestart) {
|
|
587
|
-
lines.push("Restart Claude Code for hook/env-var changes to take effect.")
|
|
307
|
+
lines.push("Restart Claude Code for hook/env-var changes to take effect.");
|
|
588
308
|
}
|
|
589
|
-
return lines
|
|
309
|
+
return lines;
|
|
590
310
|
}
|