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
package/cli/src/commands/omp.ts
CHANGED
|
@@ -1,41 +1,43 @@
|
|
|
1
|
-
import { Argument, Command, Flag, Prompt } from "effect/unstable/cli"
|
|
2
|
-
import { Console, Effect, Option } from "effect"
|
|
3
|
-
import { chmodSync, mkdirSync, writeFileSync } from "node:fs"
|
|
4
|
-
import { bail } from "../engine"
|
|
5
|
-
import { capture, p, spawnHost, which } from "../engine-native/exec"
|
|
1
|
+
import { Argument, Command, Flag, Prompt } from "effect/unstable/cli";
|
|
2
|
+
import { Console, Effect, Option } from "effect";
|
|
3
|
+
import { chmodSync, mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { bail } from "../engine";
|
|
5
|
+
import { capture, p, spawnHost, which } from "../engine-native/exec";
|
|
6
6
|
import {
|
|
7
7
|
DEFAULT_OMP_SESSION_MODEL,
|
|
8
8
|
engineHome,
|
|
9
9
|
readOmpSessionModel,
|
|
10
10
|
writeOmpSessionModel,
|
|
11
|
-
type OmpSessionModel
|
|
12
|
-
} from "../engine-native/harnesses"
|
|
11
|
+
type OmpSessionModel,
|
|
12
|
+
} from "../engine-native/harnesses";
|
|
13
13
|
import {
|
|
14
14
|
advisorLevelFor,
|
|
15
|
+
advisorRecommendation,
|
|
15
16
|
buildOmpArgs,
|
|
16
17
|
ladderCeiling,
|
|
17
18
|
overlayFileName,
|
|
19
|
+
planEffortChoice,
|
|
18
20
|
parseFreeModels,
|
|
19
21
|
renderFreeOverlay,
|
|
20
|
-
type CatalogModel
|
|
21
|
-
} from "../engine-native/ompOverlay"
|
|
22
|
+
type CatalogModel,
|
|
23
|
+
} from "../engine-native/ompOverlay";
|
|
22
24
|
|
|
23
25
|
const model = Flag.String("model").pipe(
|
|
24
26
|
Flag.withDescription("Free model selector to use and remember for omp sessions"),
|
|
25
|
-
Flag.optional
|
|
26
|
-
)
|
|
27
|
+
Flag.optional,
|
|
28
|
+
);
|
|
27
29
|
const pick = Flag.Boolean("pick").pipe(
|
|
28
30
|
Flag.withDescription("Choose the free model interactively"),
|
|
29
|
-
Flag.withDefault(false)
|
|
30
|
-
)
|
|
31
|
+
Flag.withDefault(false),
|
|
32
|
+
);
|
|
31
33
|
const args: Argument.Argument<ReadonlyArray<string>> = Argument.String("args").pipe(
|
|
32
34
|
Argument.withDescription("Arguments forwarded verbatim to omp"),
|
|
33
|
-
Argument.variadic()
|
|
34
|
-
)
|
|
35
|
+
Argument.variadic(),
|
|
36
|
+
);
|
|
35
37
|
|
|
36
38
|
export type FreeSelectorResolution =
|
|
37
39
|
| { readonly ok: true; readonly model: CatalogModel }
|
|
38
|
-
| { readonly ok: false; readonly message: string }
|
|
40
|
+
| { readonly ok: false; readonly message: string };
|
|
39
41
|
|
|
40
42
|
// Pure selector matching so the picker rule stays testable without spawning omp.
|
|
41
43
|
// An exact selector always wins; otherwise one case-insensitive substring hit
|
|
@@ -43,147 +45,235 @@ export type FreeSelectorResolution =
|
|
|
43
45
|
// to mistype and the catalog is small enough to disambiguate safely.
|
|
44
46
|
export function resolveFreeSelector(
|
|
45
47
|
input: string,
|
|
46
|
-
free: ReadonlyArray<CatalogModel
|
|
48
|
+
free: ReadonlyArray<CatalogModel>,
|
|
47
49
|
): FreeSelectorResolution {
|
|
48
|
-
const trimmed = input.trim()
|
|
50
|
+
const trimmed = input.trim();
|
|
49
51
|
if (trimmed === "") {
|
|
50
|
-
return { ok: false, message: "Model selector must not be empty or blank" }
|
|
52
|
+
return { ok: false, message: "Model selector must not be empty or blank" };
|
|
51
53
|
}
|
|
52
|
-
const exact = free.find((candidate) => candidate.selector === trimmed)
|
|
53
|
-
if (exact !== undefined) return { ok: true, model: exact }
|
|
54
|
-
const lowered = trimmed.toLowerCase()
|
|
55
|
-
const matches = free.filter((candidate) => candidate.selector.toLowerCase().includes(lowered))
|
|
56
|
-
const list = free.map((candidate) => candidate.selector).join(", ")
|
|
54
|
+
const exact = free.find((candidate) => candidate.selector === trimmed);
|
|
55
|
+
if (exact !== undefined) return { ok: true, model: exact };
|
|
56
|
+
const lowered = trimmed.toLowerCase();
|
|
57
|
+
const matches = free.filter((candidate) => candidate.selector.toLowerCase().includes(lowered));
|
|
58
|
+
const list = free.map((candidate) => candidate.selector).join(", ");
|
|
57
59
|
if (matches.length === 0) {
|
|
58
60
|
return {
|
|
59
61
|
ok: false,
|
|
60
|
-
message: `Model '${trimmed}' is not in the free catalog. Free models: ${list}
|
|
61
|
-
}
|
|
62
|
+
message: `Model '${trimmed}' is not in the free catalog. Free models: ${list}`,
|
|
63
|
+
};
|
|
62
64
|
}
|
|
63
|
-
const only = matches[0]
|
|
64
|
-
if (matches.length === 1 && only !== undefined) return { ok: true, model: only }
|
|
65
|
+
const only = matches[0];
|
|
66
|
+
if (matches.length === 1 && only !== undefined) return { ok: true, model: only };
|
|
65
67
|
return {
|
|
66
68
|
ok: false,
|
|
67
|
-
message: `Model '${trimmed}' is ambiguous; matches: ${matches.map((candidate) => candidate.selector).join(", ")}. Pass the exact selector
|
|
68
|
-
}
|
|
69
|
+
message: `Model '${trimmed}' is ambiguous; matches: ${matches.map((candidate) => candidate.selector).join(", ")}. Pass the exact selector.`,
|
|
70
|
+
};
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
const loadFreeCatalog = (omp: string): Effect.Effect<ReadonlyArray<CatalogModel>> =>
|
|
72
74
|
Effect.gen(function* () {
|
|
73
|
-
const raw = yield* Effect.promise(() => capture(omp, ["models", "--json"]))
|
|
75
|
+
const raw = yield* Effect.promise(() => capture(omp, ["models", "--json"]));
|
|
74
76
|
if (raw === "") {
|
|
75
77
|
return yield* bail(
|
|
76
|
-
"'omp models --json' returned no output; verify omp runs on this host, then retry"
|
|
77
|
-
)
|
|
78
|
+
"'omp models --json' returned no output; verify omp runs on this host, then retry",
|
|
79
|
+
);
|
|
78
80
|
}
|
|
79
|
-
let parsed: unknown
|
|
81
|
+
let parsed: unknown;
|
|
80
82
|
try {
|
|
81
|
-
parsed = JSON.parse(raw) as unknown
|
|
83
|
+
parsed = JSON.parse(raw) as unknown;
|
|
82
84
|
} catch {
|
|
83
85
|
return yield* bail(
|
|
84
|
-
"'omp models --json' returned output that is not JSON; verify the omp version, then retry"
|
|
85
|
-
)
|
|
86
|
+
"'omp models --json' returned output that is not JSON; verify the omp version, then retry",
|
|
87
|
+
);
|
|
86
88
|
}
|
|
87
|
-
const free = parseFreeModels(parsed)
|
|
89
|
+
const free = parseFreeModels(parsed);
|
|
88
90
|
if (free.length === 0) {
|
|
89
|
-
return yield* bail(
|
|
91
|
+
return yield* bail(
|
|
92
|
+
"'omp models --json' listed no free models; verify the omp login, then retry",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return free;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Ask for the thinking levels of one model. Every choice comes from that
|
|
100
|
+
* model own ladder, because omp rejects a `:level` suffix the model does not
|
|
101
|
+
* publish. A model with no ladder and a model with one level are settled
|
|
102
|
+
* without a question.
|
|
103
|
+
*/
|
|
104
|
+
const pickLevels = (catalogModel: CatalogModel) =>
|
|
105
|
+
Effect.gen(function* () {
|
|
106
|
+
const plan = planEffortChoice(catalogModel.thinking);
|
|
107
|
+
if (plan.kind === "none") {
|
|
108
|
+
yield* Console.error(
|
|
109
|
+
`${catalogModel.name} publishes no thinking levels; the model default applies`,
|
|
110
|
+
);
|
|
111
|
+
return { thinking: undefined, advisorThinking: undefined };
|
|
90
112
|
}
|
|
91
|
-
|
|
92
|
-
|
|
113
|
+
if (plan.kind === "fixed") {
|
|
114
|
+
yield* Console.error(`${catalogModel.name} publishes one thinking level: ${plan.level}`);
|
|
115
|
+
return { thinking: plan.level, advisorThinking: plan.level };
|
|
116
|
+
}
|
|
117
|
+
const uniform = yield* Prompt.Select({
|
|
118
|
+
message: "Use the same thinking level for every role?",
|
|
119
|
+
choices: [
|
|
120
|
+
{
|
|
121
|
+
title: "Yes",
|
|
122
|
+
value: true,
|
|
123
|
+
description: `every role runs at ${plan.highest}, the highest this model offers`,
|
|
124
|
+
},
|
|
125
|
+
{ title: "No", value: false, description: "set the advisor apart from the other roles" },
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
if (uniform) return { thinking: plan.highest, advisorThinking: plan.highest };
|
|
129
|
+
const ladderChoices = plan.levels.map((level) => ({
|
|
130
|
+
title: level,
|
|
131
|
+
value: level,
|
|
132
|
+
description:
|
|
133
|
+
level === plan.highest
|
|
134
|
+
? "highest this model offers"
|
|
135
|
+
: level === plan.levels[0]
|
|
136
|
+
? "lowest this model offers"
|
|
137
|
+
: "",
|
|
138
|
+
}));
|
|
139
|
+
const thinking = yield* Prompt.Select({
|
|
140
|
+
message: "Thinking level for all roles except the advisor",
|
|
141
|
+
choices: ladderChoices,
|
|
142
|
+
});
|
|
143
|
+
const suggested = advisorRecommendation(plan.levels, thinking);
|
|
144
|
+
// The list leads with the suggestion, because Prompt.Select always starts
|
|
145
|
+
// on the first entry and offers no initial index. The full ladder follows
|
|
146
|
+
// in ladder order, so a different level stays one keypress away. A
|
|
147
|
+
// suggestion equal to the main level would only duplicate a row.
|
|
148
|
+
const advisorChoices =
|
|
149
|
+
suggested === undefined || suggested === thinking
|
|
150
|
+
? ladderChoices
|
|
151
|
+
: [
|
|
152
|
+
{
|
|
153
|
+
title: suggested,
|
|
154
|
+
value: suggested,
|
|
155
|
+
description: `recommended, two levels below ${thinking}`,
|
|
156
|
+
},
|
|
157
|
+
...ladderChoices,
|
|
158
|
+
];
|
|
159
|
+
const advisorThinking = yield* Prompt.Select({
|
|
160
|
+
message: "Thinking level for the advisor role",
|
|
161
|
+
choices: advisorChoices,
|
|
162
|
+
});
|
|
163
|
+
return { thinking, advisorThinking };
|
|
164
|
+
});
|
|
93
165
|
|
|
94
166
|
export const ompCommand = Command.make("omp", { model, pick, args }, (config) =>
|
|
95
167
|
Effect.gen(function* () {
|
|
96
168
|
if (Option.isSome(config.model) && config.pick) {
|
|
97
|
-
return yield* bail("Pass either --model <selector> or --pick, not both")
|
|
169
|
+
return yield* bail("Pass either --model <selector> or --pick, not both");
|
|
98
170
|
}
|
|
99
171
|
|
|
100
|
-
const omp = yield* Effect.sync(() => which("omp"))
|
|
172
|
+
const omp = yield* Effect.sync(() => which("omp"));
|
|
101
173
|
if (omp === "") {
|
|
102
|
-
return yield* bail("omp not found on PATH; install omp, then retry", 1)
|
|
174
|
+
return yield* bail("omp not found on PATH; install omp, then retry", 1);
|
|
103
175
|
}
|
|
104
176
|
|
|
105
|
-
const home = engineHome(process.env)
|
|
177
|
+
const home = engineHome(process.env);
|
|
106
178
|
if (Option.isSome(config.model)) {
|
|
107
|
-
const free = yield* loadFreeCatalog(omp)
|
|
108
|
-
const resolved = resolveFreeSelector(config.model.value, free)
|
|
109
|
-
if (!resolved.ok) return yield* bail(resolved.message)
|
|
179
|
+
const free = yield* loadFreeCatalog(omp);
|
|
180
|
+
const resolved = resolveFreeSelector(config.model.value, free);
|
|
181
|
+
if (!resolved.ok) return yield* bail(resolved.message);
|
|
110
182
|
// Compute both levels once from the catalog row so a plain launch
|
|
111
183
|
// never needs the catalog again.
|
|
112
|
-
const thinking = ladderCeiling(resolved.model.thinking)
|
|
113
|
-
const advisorThinking = advisorLevelFor(resolved.model.thinking)
|
|
184
|
+
const thinking = ladderCeiling(resolved.model.thinking);
|
|
185
|
+
const advisorThinking = advisorLevelFor(resolved.model.thinking);
|
|
114
186
|
yield* Effect.sync(() =>
|
|
115
|
-
writeOmpSessionModel(home, {
|
|
116
|
-
|
|
187
|
+
writeOmpSessionModel(home, {
|
|
188
|
+
selector: resolved.model.selector,
|
|
189
|
+
thinking,
|
|
190
|
+
advisorThinking,
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
117
193
|
}
|
|
118
194
|
|
|
119
195
|
if (config.pick) {
|
|
120
196
|
// Check the terminal before the catalog spawn, so a non-interactive run
|
|
121
197
|
// fails with the actionable message instead of an omp subprocess first.
|
|
122
198
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
123
|
-
return yield* bail("Picking needs a terminal; pass --model <selector> instead")
|
|
199
|
+
return yield* bail("Picking needs a terminal; pass --model <selector> instead");
|
|
124
200
|
}
|
|
125
|
-
const free = yield* loadFreeCatalog(omp)
|
|
201
|
+
const free = yield* loadFreeCatalog(omp);
|
|
126
202
|
const chosen = yield* Prompt.Select({
|
|
127
203
|
message: "Choose the free model for this omp session",
|
|
128
204
|
choices: free.map((candidate) => {
|
|
129
|
-
const ceiling = ladderCeiling(candidate.thinking)
|
|
205
|
+
const ceiling = ladderCeiling(candidate.thinking);
|
|
130
206
|
return {
|
|
131
207
|
title: `${candidate.name} — ${candidate.selector}`,
|
|
132
208
|
value: candidate.selector,
|
|
133
209
|
description:
|
|
134
210
|
ceiling === undefined
|
|
135
211
|
? `model default thinking; ${candidate.contextWindow} context`
|
|
136
|
-
: `thinking to ${ceiling}; ${candidate.contextWindow} context
|
|
137
|
-
}
|
|
138
|
-
})
|
|
139
|
-
})
|
|
140
|
-
const match = free.find((candidate) => candidate.selector === chosen)
|
|
141
|
-
if (match === undefined) return yield* bail(`Model '${chosen}' is not in the free catalog`)
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
212
|
+
: `thinking to ${ceiling}; ${candidate.contextWindow} context`,
|
|
213
|
+
};
|
|
214
|
+
}),
|
|
215
|
+
});
|
|
216
|
+
const match = free.find((candidate) => candidate.selector === chosen);
|
|
217
|
+
if (match === undefined) return yield* bail(`Model '${chosen}' is not in the free catalog`);
|
|
218
|
+
const chosenLevels = yield* pickLevels(match);
|
|
219
|
+
yield* Effect.sync(() =>
|
|
220
|
+
writeOmpSessionModel(home, {
|
|
221
|
+
selector: match.selector,
|
|
222
|
+
thinking: chosenLevels.thinking,
|
|
223
|
+
advisorThinking: chosenLevels.advisorThinking,
|
|
224
|
+
}),
|
|
225
|
+
);
|
|
145
226
|
}
|
|
146
227
|
|
|
147
|
-
const session: OmpSessionModel =
|
|
148
|
-
DEFAULT_OMP_SESSION_MODEL
|
|
228
|
+
const session: OmpSessionModel =
|
|
229
|
+
(yield* Effect.sync(() => readOmpSessionModel(home))) ?? DEFAULT_OMP_SESSION_MODEL;
|
|
149
230
|
|
|
150
231
|
// A stable cache path, not a temp file deleted at exit, because omp may
|
|
151
232
|
// re-read the overlay during a live session. The name carries the model,
|
|
152
233
|
// so a second launcher on another model cannot rewrite the file a running
|
|
153
234
|
// session still reads.
|
|
154
|
-
const directory = p(home, ".cache", "docks-kit")
|
|
155
|
-
const overlayPath = p(directory, overlayFileName(session.selector))
|
|
235
|
+
const directory = p(home, ".cache", "docks-kit");
|
|
236
|
+
const overlayPath = p(directory, overlayFileName(session.selector));
|
|
156
237
|
yield* Effect.sync(() => {
|
|
157
238
|
// `mode` applies only when mkdir creates the path, so an existing
|
|
158
239
|
// permissive cache directory keeps its mode until the chmod below.
|
|
159
|
-
mkdirSync(directory, { recursive: true, mode: 0o700 })
|
|
160
|
-
chmodSync(directory, 0o700)
|
|
161
|
-
writeFileSync(overlayPath, renderFreeOverlay(session), { mode: 0o600 })
|
|
162
|
-
chmodSync(overlayPath, 0o600)
|
|
163
|
-
})
|
|
240
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
241
|
+
chmodSync(directory, 0o700);
|
|
242
|
+
writeFileSync(overlayPath, renderFreeOverlay(session), { mode: 0o600 });
|
|
243
|
+
chmodSync(overlayPath, 0o600);
|
|
244
|
+
});
|
|
164
245
|
|
|
165
246
|
// A level-free model states the model default so the line never shows
|
|
166
|
-
// an undefined level.
|
|
167
|
-
|
|
247
|
+
// an undefined level. The advisor appears only when it differs, because
|
|
248
|
+
// a uniform session has nothing extra to report.
|
|
249
|
+
const level =
|
|
250
|
+
typeof session.thinking === "string" && session.thinking.trim() !== ""
|
|
251
|
+
? session.thinking
|
|
252
|
+
: undefined;
|
|
253
|
+
const advisor =
|
|
254
|
+
typeof session.advisorThinking === "string" && session.advisorThinking.trim() !== ""
|
|
255
|
+
? session.advisorThinking
|
|
256
|
+
: undefined;
|
|
257
|
+
const levelText =
|
|
258
|
+
level === undefined ? "the model default thinking level" : `${level} thinking`;
|
|
259
|
+
const advisorText = advisor !== undefined && advisor !== level ? `, advisor at ${advisor}` : "";
|
|
168
260
|
yield* Console.error(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
: `Starting omp with ${session.selector} at the model default thinking level (session only; deployed config unchanged)`
|
|
172
|
-
)
|
|
261
|
+
`Starting omp with ${session.selector} at ${levelText}${advisorText} (session only; deployed config unchanged)`,
|
|
262
|
+
);
|
|
173
263
|
|
|
174
264
|
const child = yield* Effect.sync(() =>
|
|
175
|
-
spawnHost("omp", buildOmpArgs(overlayPath, config.args), { stdio: "inherit" })
|
|
176
|
-
)
|
|
265
|
+
spawnHost("omp", buildOmpArgs(overlayPath, config.args), { stdio: "inherit" }),
|
|
266
|
+
);
|
|
177
267
|
if (child.error !== undefined) {
|
|
178
268
|
return yield* bail(
|
|
179
|
-
`Failed to start omp: ${child.error instanceof Error ? child.error.message : String(child.error)}
|
|
180
|
-
)
|
|
269
|
+
`Failed to start omp: ${child.error instanceof Error ? child.error.message : String(child.error)}`,
|
|
270
|
+
);
|
|
181
271
|
}
|
|
182
272
|
// A signalled child reports a null status, which must not look successful.
|
|
183
|
-
yield* Effect.sync(() => process.exit(child.status ?? 1))
|
|
184
|
-
})
|
|
273
|
+
yield* Effect.sync(() => process.exit(child.status ?? 1));
|
|
274
|
+
}),
|
|
185
275
|
).pipe(
|
|
186
276
|
Command.withDescription(
|
|
187
|
-
"Start omp with every model role overridden to the remembered free model for this run only (no deployed configuration is changed; --model or --pick remembers a new free default)."
|
|
188
|
-
)
|
|
189
|
-
)
|
|
277
|
+
"Start omp with every model role overridden to the remembered free model for this run only (no deployed configuration is changed; --model or --pick remembers a new free default).",
|
|
278
|
+
),
|
|
279
|
+
);
|
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
import { Argument, Command, Flag } from "effect/unstable/cli"
|
|
2
|
-
import { Console, Effect, Option } from "effect"
|
|
3
|
-
import { bail } from "../engine"
|
|
4
|
-
import { pluginsView } from "../manifests"
|
|
1
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
2
|
+
import { Console, Effect, Option } from "effect";
|
|
3
|
+
import { bail } from "../engine";
|
|
4
|
+
import { pluginsView } from "../manifests";
|
|
5
5
|
|
|
6
6
|
const action = Argument.String("action").pipe(
|
|
7
7
|
Argument.withDescription("list (default)"),
|
|
8
|
-
Argument.optional
|
|
9
|
-
)
|
|
8
|
+
Argument.optional,
|
|
9
|
+
);
|
|
10
10
|
const json = Flag.Boolean("json").pipe(
|
|
11
11
|
Flag.withDescription("Machine-readable output"),
|
|
12
|
-
Flag.withDefault(false)
|
|
13
|
-
)
|
|
12
|
+
Flag.withDefault(false),
|
|
13
|
+
);
|
|
14
14
|
|
|
15
15
|
export const pluginsCommand = Command.make("plugins", { action, json }, (config) =>
|
|
16
16
|
Effect.gen(function* () {
|
|
17
|
-
const act = Option.getOrElse(config.action, () => "list")
|
|
17
|
+
const act = Option.getOrElse(config.action, () => "list");
|
|
18
18
|
if (act !== "list") {
|
|
19
19
|
return yield* bail(
|
|
20
|
-
`Unknown plugins action '${act}' (valid: list). Install/removal runs through sync: --claude-plugin=<name> opts in, --prune reconciles
|
|
21
|
-
)
|
|
20
|
+
`Unknown plugins action '${act}' (valid: list). Install/removal runs through sync: --claude-plugin=<name> opts in, --prune reconciles.`,
|
|
21
|
+
);
|
|
22
22
|
}
|
|
23
|
-
const view = pluginsView()
|
|
23
|
+
const view = pluginsView();
|
|
24
24
|
if (config.json) {
|
|
25
|
-
return yield* Console.log(JSON.stringify(view, null, 2))
|
|
25
|
+
return yield* Console.log(JSON.stringify(view, null, 2));
|
|
26
26
|
}
|
|
27
|
-
yield* Console.log(
|
|
28
|
-
|
|
27
|
+
yield* Console.log(
|
|
28
|
+
"SoT tri-state: true = enabled everywhere, false = installed-but-disabled (per-project enable), absent = not kit-managed\n",
|
|
29
|
+
);
|
|
30
|
+
yield* Console.log(`${"PLUGIN".padEnd(44)} ${"SOT".padEnd(7)} INSTALLED`);
|
|
29
31
|
for (const p of view) {
|
|
30
|
-
yield* Console.log(`${p.plugin.padEnd(44)} ${p.sot.padEnd(7)} ${p.installed ? "yes" : "no"}`)
|
|
32
|
+
yield* Console.log(`${p.plugin.padEnd(44)} ${p.sot.padEnd(7)} ${p.installed ? "yes" : "no"}`);
|
|
31
33
|
}
|
|
32
|
-
})
|
|
33
|
-
).pipe(
|
|
34
|
-
Command.withDescription("Installed plugins vs SoT enabledPlugins tri-state (read-only).")
|
|
35
|
-
)
|
|
34
|
+
}),
|
|
35
|
+
).pipe(Command.withDescription("Installed plugins vs SoT enabledPlugins tri-state (read-only)."));
|
|
@@ -1,38 +1,40 @@
|
|
|
1
|
-
import { Argument, Command, Flag } from "effect/unstable/cli"
|
|
2
|
-
import { Console, Effect, Option } from "effect"
|
|
3
|
-
import { bail } from "../engine"
|
|
4
|
-
import { skillsView } from "../manifests"
|
|
1
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
2
|
+
import { Console, Effect, Option } from "effect";
|
|
3
|
+
import { bail } from "../engine";
|
|
4
|
+
import { skillsView } from "../manifests";
|
|
5
5
|
|
|
6
6
|
const action = Argument.String("action").pipe(
|
|
7
7
|
Argument.withDescription("list (default)"),
|
|
8
|
-
Argument.optional
|
|
9
|
-
)
|
|
8
|
+
Argument.optional,
|
|
9
|
+
);
|
|
10
10
|
const json = Flag.Boolean("json").pipe(
|
|
11
11
|
Flag.withDescription("Machine-readable output"),
|
|
12
|
-
Flag.withDefault(false)
|
|
13
|
-
)
|
|
12
|
+
Flag.withDefault(false),
|
|
13
|
+
);
|
|
14
14
|
|
|
15
15
|
export const skillsCommand = Command.make("skills", { action, json }, (config) =>
|
|
16
16
|
Effect.gen(function* () {
|
|
17
|
-
const act = Option.getOrElse(config.action, () => "list")
|
|
17
|
+
const act = Option.getOrElse(config.action, () => "list");
|
|
18
18
|
if (act !== "list") {
|
|
19
19
|
return yield* bail(
|
|
20
|
-
`Unknown skills action '${act}' (valid: list). Declare skills in SoT/.agents/skills.txt and run sync; --prune removes undeclared kit-managed ones
|
|
21
|
-
)
|
|
20
|
+
`Unknown skills action '${act}' (valid: list). Declare skills in SoT/.agents/skills.txt and run sync; --prune removes undeclared kit-managed ones.`,
|
|
21
|
+
);
|
|
22
22
|
}
|
|
23
|
-
const view = skillsView()
|
|
23
|
+
const view = skillsView();
|
|
24
24
|
if (config.json) {
|
|
25
|
-
return yield* Console.log(JSON.stringify(view, null, 2))
|
|
25
|
+
return yield* Console.log(JSON.stringify(view, null, 2));
|
|
26
26
|
}
|
|
27
|
-
yield* Console.log(
|
|
27
|
+
yield* Console.log(
|
|
28
|
+
`${"SKILL".padEnd(28)} ${"DECLARED".padEnd(9)} ${"INSTALLED".padEnd(10)} CLAUDE-SYMLINK`,
|
|
29
|
+
);
|
|
28
30
|
for (const s of view) {
|
|
29
31
|
yield* Console.log(
|
|
30
|
-
`${s.skill.padEnd(28)} ${(s.declared ? "yes" : "no").padEnd(9)} ${(s.installed ? "yes" : "no").padEnd(10)} ${s.claudeSymlink ? "ok" : "-"}
|
|
31
|
-
)
|
|
32
|
+
`${s.skill.padEnd(28)} ${(s.declared ? "yes" : "no").padEnd(9)} ${(s.installed ? "yes" : "no").padEnd(10)} ${s.claudeSymlink ? "ok" : "-"}`,
|
|
33
|
+
);
|
|
32
34
|
}
|
|
33
|
-
})
|
|
35
|
+
}),
|
|
34
36
|
).pipe(
|
|
35
37
|
Command.withDescription(
|
|
36
|
-
"Universal agent skills: SoT/.agents/skills.txt vs ~/.agents/skills + the ~/.claude/skills symlink health (read-only)."
|
|
37
|
-
)
|
|
38
|
-
)
|
|
38
|
+
"Universal agent skills: SoT/.agents/skills.txt vs ~/.agents/skills + the ~/.claude/skills symlink health (read-only).",
|
|
39
|
+
),
|
|
40
|
+
);
|