docks-kit 0.17.1 → 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 +36 -32
- package/cli/docs/omp-models.md +88 -53
- 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 +132 -112
- 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 +67 -64
- 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
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flag grammar and parsing for the Effect CLI layer: flag surfaces derived
|
|
3
|
+
* from caller-supplied command values, argv scanning/normalization,
|
|
4
|
+
* subcommand detection, and the omp passthrough boundary. Pure: surfaces are
|
|
5
|
+
* built from structural command values, so this module never touches Effect
|
|
6
|
+
* CLI globals or process state.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface FlagSurface {
|
|
10
|
+
readonly longFlags: ReadonlyArray<string>;
|
|
11
|
+
readonly aliases: ReadonlyMap<string, string>;
|
|
12
|
+
readonly valueFlags: ReadonlyArray<string>;
|
|
13
|
+
readonly repeatableFlags: ReadonlyArray<string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface GlobalFlagSurface extends FlagSurface {
|
|
17
|
+
readonly actionFlags: ReadonlySet<string>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CommandValue {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ArgvSurfaces {
|
|
25
|
+
readonly commandSurfaces: ReadonlyMap<string, FlagSurface>;
|
|
26
|
+
readonly globalSurface: GlobalFlagSurface;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const flagMetadata = (
|
|
30
|
+
param: unknown,
|
|
31
|
+
): {
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly aliases: ReadonlyArray<string>;
|
|
34
|
+
readonly takesValue: boolean;
|
|
35
|
+
readonly repeatable: boolean;
|
|
36
|
+
} => {
|
|
37
|
+
let current = param;
|
|
38
|
+
let repeatable = false;
|
|
39
|
+
|
|
40
|
+
while (typeof current === "object" && current !== null) {
|
|
41
|
+
if (!("_tag" in current)) break;
|
|
42
|
+
if (current._tag === "Variadic") repeatable = true;
|
|
43
|
+
if (current._tag !== "Single") {
|
|
44
|
+
if (!("param" in current)) break;
|
|
45
|
+
current = current.param;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!("name" in current) || typeof current.name !== "string") {
|
|
50
|
+
throw new Error("Effect CLI exposed a flag without a string name");
|
|
51
|
+
}
|
|
52
|
+
if (
|
|
53
|
+
!("aliases" in current) ||
|
|
54
|
+
!Array.isArray(current.aliases) ||
|
|
55
|
+
!current.aliases.every((alias) => typeof alias === "string")
|
|
56
|
+
) {
|
|
57
|
+
throw new Error(`Effect CLI exposed invalid aliases for --${current.name}`);
|
|
58
|
+
}
|
|
59
|
+
if (
|
|
60
|
+
!("primitiveType" in current) ||
|
|
61
|
+
typeof current.primitiveType !== "object" ||
|
|
62
|
+
current.primitiveType === null ||
|
|
63
|
+
!("_tag" in current.primitiveType) ||
|
|
64
|
+
typeof current.primitiveType._tag !== "string"
|
|
65
|
+
) {
|
|
66
|
+
throw new Error(`Effect CLI exposed no primitive type for --${current.name}`);
|
|
67
|
+
}
|
|
68
|
+
const aliases: ReadonlyArray<string> = current.aliases;
|
|
69
|
+
return {
|
|
70
|
+
name: current.name,
|
|
71
|
+
aliases,
|
|
72
|
+
takesValue: current.primitiveType._tag !== "Boolean",
|
|
73
|
+
repeatable,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
throw new Error("Effect CLI exposed an unsupported flag parameter");
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const flagSurface = (params: ReadonlyArray<unknown>): FlagSurface => {
|
|
81
|
+
const longFlags: Array<string> = [];
|
|
82
|
+
const aliases = new Map<string, string>();
|
|
83
|
+
const valueFlags: Array<string> = [];
|
|
84
|
+
const repeatableFlags: Array<string> = [];
|
|
85
|
+
|
|
86
|
+
for (const param of params) {
|
|
87
|
+
const metadata = flagMetadata(param);
|
|
88
|
+
const longName = `--${metadata.name}`;
|
|
89
|
+
longFlags.push(longName);
|
|
90
|
+
if (metadata.takesValue) valueFlags.push(longName);
|
|
91
|
+
if (metadata.repeatable) repeatableFlags.push(longName);
|
|
92
|
+
for (const alias of metadata.aliases) aliases.set(`-${alias}`, longName);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return { longFlags, aliases, valueFlags, repeatableFlags };
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const buildCommandSurface = (command: CommandValue): FlagSurface => {
|
|
99
|
+
if (!("config" in command)) {
|
|
100
|
+
throw new Error(`Effect CLI did not expose flags for '${command.name}'`);
|
|
101
|
+
}
|
|
102
|
+
const config = command.config;
|
|
103
|
+
if (
|
|
104
|
+
typeof config !== "object" ||
|
|
105
|
+
config === null ||
|
|
106
|
+
!("flags" in config) ||
|
|
107
|
+
!Array.isArray(config.flags)
|
|
108
|
+
) {
|
|
109
|
+
throw new Error(`Effect CLI did not expose flags for '${command.name}'`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return flagSurface(config.flags);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const globalSurface = (builtIns: unknown): GlobalFlagSurface => {
|
|
116
|
+
if (!Array.isArray(builtIns)) {
|
|
117
|
+
throw new Error("Effect CLI did not expose its built-in global flags");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const actionFlags = new Set<string>();
|
|
121
|
+
const flags = builtIns.map((builtIn) => {
|
|
122
|
+
if (
|
|
123
|
+
(typeof builtIn !== "object" && typeof builtIn !== "function") ||
|
|
124
|
+
builtIn === null ||
|
|
125
|
+
!("flag" in builtIn) ||
|
|
126
|
+
!("_tag" in builtIn) ||
|
|
127
|
+
typeof builtIn._tag !== "string"
|
|
128
|
+
) {
|
|
129
|
+
throw new Error("Effect CLI exposed an unsupported built-in global flag");
|
|
130
|
+
}
|
|
131
|
+
if (builtIn._tag === "Action") {
|
|
132
|
+
// Only a presence-based action is idempotent. `--completions` is an action
|
|
133
|
+
// that takes a value, and Effect 4 would silently keep the first one, which
|
|
134
|
+
// is exactly what the duplicate rule exists to refuse.
|
|
135
|
+
const metadata = flagMetadata(builtIn.flag);
|
|
136
|
+
if (!metadata.takesValue) actionFlags.add(`--${metadata.name}`);
|
|
137
|
+
}
|
|
138
|
+
return builtIn.flag;
|
|
139
|
+
});
|
|
140
|
+
return { ...flagSurface(flags), actionFlags };
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const buildSurfaces = (
|
|
144
|
+
commands: ReadonlyArray<CommandValue>,
|
|
145
|
+
builtIns: unknown,
|
|
146
|
+
): ArgvSurfaces => {
|
|
147
|
+
// A Map, not a plain object: an object literal answers `toString` and friends from
|
|
148
|
+
// `Object.prototype`, so an unknown subcommand with such a name would slip past the
|
|
149
|
+
// unknown-command guard and dereference a surface that was never built.
|
|
150
|
+
const commandSurfaces: ReadonlyMap<string, FlagSurface> = new Map(
|
|
151
|
+
commands.map((command) => [command.name, buildCommandSurface(command)]),
|
|
152
|
+
);
|
|
153
|
+
return { commandSurfaces, globalSurface: globalSurface(builtIns) };
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const flagNameOf = (token: string): string => {
|
|
157
|
+
const equals = token.indexOf("=");
|
|
158
|
+
return equals === -1 ? token : token.slice(0, equals);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const canonicalFlagName = (name: string, surface: FlagSurface | undefined): string | undefined => {
|
|
162
|
+
if (surface === undefined) return undefined;
|
|
163
|
+
const aliased = surface.aliases.get(name);
|
|
164
|
+
if (aliased !== undefined) return aliased;
|
|
165
|
+
return surface.longFlags.includes(name) ? name : undefined;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const declaredFlagName = (
|
|
169
|
+
name: string,
|
|
170
|
+
commandSurface: FlagSurface | undefined,
|
|
171
|
+
surfaces: ArgvSurfaces,
|
|
172
|
+
): string | undefined =>
|
|
173
|
+
canonicalFlagName(name, commandSurface) ?? canonicalFlagName(name, surfaces.globalSurface);
|
|
174
|
+
|
|
175
|
+
const declaredInAnySurface = (name: string, surfaces: ArgvSurfaces): boolean => {
|
|
176
|
+
if (canonicalFlagName(name, surfaces.globalSurface) !== undefined) return true;
|
|
177
|
+
for (const surface of surfaces.commandSurfaces.values()) {
|
|
178
|
+
if (canonicalFlagName(name, surface) !== undefined) return true;
|
|
179
|
+
}
|
|
180
|
+
return false;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const takesValueInAnySurface = (name: string, surfaces: ArgvSurfaces): boolean => {
|
|
184
|
+
const globalName = canonicalFlagName(name, surfaces.globalSurface);
|
|
185
|
+
if (globalName !== undefined && surfaces.globalSurface.valueFlags.includes(globalName))
|
|
186
|
+
return true;
|
|
187
|
+
for (const surface of surfaces.commandSurfaces.values()) {
|
|
188
|
+
const canonicalName = canonicalFlagName(name, surface);
|
|
189
|
+
if (canonicalName !== undefined && surface.valueFlags.includes(canonicalName)) return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/** The resolved subcommand word, or undefined at the root. */
|
|
195
|
+
export const subcommandName = (
|
|
196
|
+
args: ReadonlyArray<string>,
|
|
197
|
+
surfaces: ArgvSurfaces,
|
|
198
|
+
): string | undefined => {
|
|
199
|
+
for (let index = 0; index < args.length; index++) {
|
|
200
|
+
const token = args[index];
|
|
201
|
+
if (token === "--") return undefined;
|
|
202
|
+
const name = flagNameOf(token);
|
|
203
|
+
if (token === name && takesValueInAnySurface(name, surfaces)) {
|
|
204
|
+
const next = args[index + 1];
|
|
205
|
+
if (next === "--") return undefined;
|
|
206
|
+
if (
|
|
207
|
+
next !== undefined &&
|
|
208
|
+
!(next.startsWith("-") && declaredInAnySurface(flagNameOf(next), surfaces))
|
|
209
|
+
) {
|
|
210
|
+
index++;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (!token.startsWith("-")) return token;
|
|
215
|
+
}
|
|
216
|
+
return undefined;
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export interface ScannedFlag {
|
|
220
|
+
readonly token: string;
|
|
221
|
+
readonly name: string;
|
|
222
|
+
readonly canonicalName: string | undefined;
|
|
223
|
+
readonly hasValue: boolean;
|
|
224
|
+
readonly takesValue: boolean;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface ArgvNormalization {
|
|
228
|
+
readonly flagIndex: number;
|
|
229
|
+
readonly valueIndex: number;
|
|
230
|
+
readonly token: string;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface ScannedArgv {
|
|
234
|
+
readonly flags: ReadonlyArray<ScannedFlag>;
|
|
235
|
+
readonly normalizations: ReadonlyArray<ArgvNormalization>;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export const scanArgv = (
|
|
239
|
+
args: ReadonlyArray<string>,
|
|
240
|
+
commandSurface: FlagSurface | undefined,
|
|
241
|
+
surfaces: ArgvSurfaces,
|
|
242
|
+
): ScannedArgv => {
|
|
243
|
+
const flags: Array<ScannedFlag> = [];
|
|
244
|
+
const normalizations: Array<ArgvNormalization> = [];
|
|
245
|
+
|
|
246
|
+
for (let index = 0; index < args.length; index++) {
|
|
247
|
+
const token = args[index];
|
|
248
|
+
if (token === "--") break;
|
|
249
|
+
if (!token.startsWith("-")) continue;
|
|
250
|
+
|
|
251
|
+
const name = flagNameOf(token);
|
|
252
|
+
const canonicalName = declaredFlagName(name, commandSurface, surfaces);
|
|
253
|
+
let hasValue = token.includes("=");
|
|
254
|
+
const takesValue =
|
|
255
|
+
canonicalName !== undefined &&
|
|
256
|
+
(commandSurface?.valueFlags.includes(canonicalName) === true ||
|
|
257
|
+
surfaces.globalSurface.valueFlags.includes(canonicalName));
|
|
258
|
+
|
|
259
|
+
if (!hasValue && takesValue) {
|
|
260
|
+
const next = args[index + 1];
|
|
261
|
+
const nextIsRecognizedFlag =
|
|
262
|
+
next !== undefined &&
|
|
263
|
+
next.startsWith("-") &&
|
|
264
|
+
declaredFlagName(flagNameOf(next), commandSurface, surfaces) !== undefined;
|
|
265
|
+
// A legitimate value may begin with `-`; only a recognized flag proves it is missing.
|
|
266
|
+
if (next !== undefined && next !== "--" && !nextIsRecognizedFlag) {
|
|
267
|
+
hasValue = true;
|
|
268
|
+
if (next.startsWith("-")) {
|
|
269
|
+
// Effect 4's lexer treats any `-`-leading token as an option, so a legitimate
|
|
270
|
+
// dash-leading value only survives in the inline form.
|
|
271
|
+
normalizations.push({
|
|
272
|
+
flagIndex: index,
|
|
273
|
+
valueIndex: index + 1,
|
|
274
|
+
token: `${token}=${next}`,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
index++;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
flags.push({ token, name, canonicalName, hasValue, takesValue });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return { flags, normalizations };
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export const normalizeArgv = (
|
|
288
|
+
args: ReadonlyArray<string>,
|
|
289
|
+
normalizations: ReadonlyArray<ArgvNormalization>,
|
|
290
|
+
): ReadonlyArray<string> => {
|
|
291
|
+
if (normalizations.length === 0) return args;
|
|
292
|
+
|
|
293
|
+
const normalized: Array<string> = [];
|
|
294
|
+
let normalizationIndex = 0;
|
|
295
|
+
for (let index = 0; index < args.length; index++) {
|
|
296
|
+
const normalization = normalizations[normalizationIndex];
|
|
297
|
+
if (normalization !== undefined && normalization.flagIndex === index) {
|
|
298
|
+
normalized.push(normalization.token);
|
|
299
|
+
index = normalization.valueIndex;
|
|
300
|
+
normalizationIndex++;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
normalized.push(args[index]);
|
|
304
|
+
}
|
|
305
|
+
return normalized;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Without the injected `--`, Effect 4 rejects a forwarded flag such as `-p` or
|
|
309
|
+
// `--mode` as unrecognized. The omp launcher therefore owns a passthrough
|
|
310
|
+
// boundary: the first token after the `omp` word that is neither a flag
|
|
311
|
+
// declared on the omp or global surface nor a value consumed by such a flag
|
|
312
|
+
// starts the verbatim tail forwarded to omp. An undeclared long flag joins the
|
|
313
|
+
// tail too, because most omp flags are long (`--mode`, `--continue`,
|
|
314
|
+
// `--models`), and omp itself reports an unrecognized one accurately. Use
|
|
315
|
+
// `docks-kit omp -- --model x` to reach omp's own same-named flag.
|
|
316
|
+
export const spliceOmpBoundary = (
|
|
317
|
+
args: ReadonlyArray<string>,
|
|
318
|
+
surfaces: ArgvSurfaces,
|
|
319
|
+
): ReadonlyArray<string> => {
|
|
320
|
+
const surface = surfaces.commandSurfaces.get("omp");
|
|
321
|
+
let ompIndex = -1;
|
|
322
|
+
for (let index = 0; index < args.length; index++) {
|
|
323
|
+
const token = args[index] as string;
|
|
324
|
+
if (token === "--") return args;
|
|
325
|
+
const name = flagNameOf(token);
|
|
326
|
+
if (token === name && takesValueInAnySurface(name, surfaces)) {
|
|
327
|
+
const next = args[index + 1];
|
|
328
|
+
if (next === "--") return args;
|
|
329
|
+
if (
|
|
330
|
+
next !== undefined &&
|
|
331
|
+
!(next.startsWith("-") && declaredInAnySurface(flagNameOf(next), surfaces))
|
|
332
|
+
) {
|
|
333
|
+
index++;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (!token.startsWith("-")) {
|
|
338
|
+
ompIndex = index;
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (ompIndex === -1 || args[ompIndex] !== "omp") return args;
|
|
343
|
+
for (let index = ompIndex + 1; index < args.length; index++) {
|
|
344
|
+
const token = args[index] as string;
|
|
345
|
+
// An explicit delimiter already marks the tail; a second one would be
|
|
346
|
+
// forwarded to omp as a literal argument.
|
|
347
|
+
if (token === "--") return args;
|
|
348
|
+
if (!token.startsWith("-")) {
|
|
349
|
+
return [...args.slice(0, index), "--", ...args.slice(index)];
|
|
350
|
+
}
|
|
351
|
+
const name = flagNameOf(token);
|
|
352
|
+
const canonical = declaredFlagName(name, surface, surfaces);
|
|
353
|
+
if (canonical === undefined) return [...args.slice(0, index), "--", ...args.slice(index)];
|
|
354
|
+
const takesValue =
|
|
355
|
+
surface?.valueFlags.includes(canonical) === true ||
|
|
356
|
+
surfaces.globalSurface.valueFlags.includes(canonical);
|
|
357
|
+
if (takesValue && !token.includes("=")) {
|
|
358
|
+
const next = args[index + 1];
|
|
359
|
+
if (
|
|
360
|
+
next !== undefined &&
|
|
361
|
+
!(
|
|
362
|
+
next.startsWith("-") &&
|
|
363
|
+
declaredFlagName(flagNameOf(next), surface, surfaces) !== undefined
|
|
364
|
+
)
|
|
365
|
+
) {
|
|
366
|
+
index++;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return args;
|
|
371
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-command validation for the Effect CLI layer: legacy-flag hints,
|
|
3
|
+
* unknown-command/flag detection, boolean-value and duplicate rejection, and
|
|
4
|
+
* the sync modifiers' missing-value diagnostics. Operates on scanned flags so
|
|
5
|
+
* the grammar machinery stays in argvSurface.ts.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { KNOWN_CLAUDE_OPTIN_PLUGINS } from "./engine-native/parseModifiers";
|
|
9
|
+
import { modelCatalog } from "./engine-native/models";
|
|
10
|
+
import { advisorCatalog, advisorFlagGrammar, effortCatalog, effortFlagGrammar } from "./efforts";
|
|
11
|
+
import type { Tool } from "./manifests";
|
|
12
|
+
import type { ArgvSurfaces, FlagSurface, ScannedFlag } from "./argvSurface";
|
|
13
|
+
|
|
14
|
+
const LEGACY_HINTS: Readonly<Record<string, string>> = {
|
|
15
|
+
"--force": "--force was renamed to --reconcile",
|
|
16
|
+
"--remove-plugins":
|
|
17
|
+
"--remove-plugins was renamed to --prune (it also removes marketplaces + kit-managed skills)",
|
|
18
|
+
"--680k": "--680k was renamed to --claude-compact-window=680k",
|
|
19
|
+
"--permissive": "--permissive was renamed to --claude-permissive",
|
|
20
|
+
"--supabase": "--supabase was renamed to --claude-plugin=supabase",
|
|
21
|
+
"--n8n": "--n8n was renamed to --claude-plugin=n8n",
|
|
22
|
+
"--skip-rtk": "--skip-rtk was renamed to --skip-bubblewrap",
|
|
23
|
+
"--claude": "--claude was renamed: pass the target as a word, e.g. 'sync claude'",
|
|
24
|
+
"--codex": "--codex was renamed: pass the target as a word, e.g. 'sync codex'",
|
|
25
|
+
"--agents": "--agents was renamed: pass the target as a word, e.g. 'sync agents'",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const modelCatalogHint = (tool: Tool): string => {
|
|
29
|
+
const catalog = modelCatalog(tool);
|
|
30
|
+
const list = catalog.models
|
|
31
|
+
.map((model) => ` ${model.id}${model.note !== undefined ? ` — ${model.note}` : ""}`)
|
|
32
|
+
.join("\n");
|
|
33
|
+
return `Available ${tool} models (kit-verified ${catalog.verified} — SoT/models.json):\n${list}`;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const missingModifierValue = (flag: string): string | undefined => {
|
|
37
|
+
switch (flag) {
|
|
38
|
+
case "--claude-model":
|
|
39
|
+
case "--codex-model": {
|
|
40
|
+
const tool: Tool = flag === "--claude-model" ? "claude" : "codex";
|
|
41
|
+
return `${modelCatalogHint(tool)}\n${flag} requires a value: ${flag}=<model>`;
|
|
42
|
+
}
|
|
43
|
+
case "--claude-effort":
|
|
44
|
+
case "--codex-effort": {
|
|
45
|
+
const tool: Tool = flag === "--claude-effort" ? "claude" : "codex";
|
|
46
|
+
return `${effortCatalog(tool)}\n${flag} requires a value: ${effortFlagGrammar(tool)}`;
|
|
47
|
+
}
|
|
48
|
+
case "--claude-advisor":
|
|
49
|
+
return `${advisorCatalog()}\n${flag} requires a value: ${advisorFlagGrammar()}`;
|
|
50
|
+
case "--claude-compact-window":
|
|
51
|
+
return `${flag} requires a value: ${flag}=<tokens> (e.g. 680k)`;
|
|
52
|
+
case "--claude-plugin":
|
|
53
|
+
return `${flag} requires a value: ${flag}=<${KNOWN_CLAUDE_OPTIN_PLUGINS.join("|")}>`;
|
|
54
|
+
default:
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Run the validation checks in diagnostic order. Returns the reject message
|
|
61
|
+
* for the first failure, or undefined when the scanned flags are valid.
|
|
62
|
+
*/
|
|
63
|
+
export const validateScannedArgv = (
|
|
64
|
+
subcommand: string | undefined,
|
|
65
|
+
commandSurface: FlagSurface | undefined,
|
|
66
|
+
surfaces: ArgvSurfaces,
|
|
67
|
+
flags: ReadonlyArray<ScannedFlag>,
|
|
68
|
+
): string | undefined => {
|
|
69
|
+
const unknownCommandWouldMisdiagnoseFlag =
|
|
70
|
+
subcommand !== undefined &&
|
|
71
|
+
commandSurface === undefined &&
|
|
72
|
+
flags.some((flag) => flag.canonicalName === undefined);
|
|
73
|
+
if (unknownCommandWouldMisdiagnoseFlag) {
|
|
74
|
+
return `unknown command '${subcommand}'`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (subcommand === "sync") {
|
|
78
|
+
for (const flag of flags) {
|
|
79
|
+
const hint = LEGACY_HINTS[flag.name];
|
|
80
|
+
if (hint !== undefined) return hint;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Effect 4 expands clustered shorts, but the kit declares no clusterable alias
|
|
85
|
+
// pair, so refusing a cluster as one unknown flag is the conservative direction.
|
|
86
|
+
for (const flag of flags) {
|
|
87
|
+
if (flag.canonicalName !== undefined) continue;
|
|
88
|
+
const scope = subcommand === undefined ? "" : ` for '${subcommand}'`;
|
|
89
|
+
return `unknown flag ${flag.token}${scope}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const flag of flags) {
|
|
93
|
+
if (!flag.token.includes("=") || flag.takesValue) continue;
|
|
94
|
+
// These booleans are presence-based; Effect 4 would otherwise let
|
|
95
|
+
// `--dry-run=false` read as a dry run while performing a real mutating sync.
|
|
96
|
+
return `flag ${flag.name} does not take a value`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const seen = new Set<string>();
|
|
100
|
+
for (const flag of flags) {
|
|
101
|
+
const name = flag.canonicalName;
|
|
102
|
+
if (name === undefined) continue;
|
|
103
|
+
const duplicateAllowed =
|
|
104
|
+
commandSurface?.repeatableFlags.includes(name) === true ||
|
|
105
|
+
surfaces.globalSurface.repeatableFlags.includes(name) ||
|
|
106
|
+
surfaces.globalSurface.actionFlags.has(name);
|
|
107
|
+
if (!duplicateAllowed && seen.has(name)) {
|
|
108
|
+
return `flag ${name} was given more than once`;
|
|
109
|
+
}
|
|
110
|
+
seen.add(name);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (subcommand === "sync") {
|
|
114
|
+
for (const flag of flags) {
|
|
115
|
+
if (flag.hasValue) continue;
|
|
116
|
+
const message = missingModifierValue(flag.name);
|
|
117
|
+
if (message !== undefined) return message;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return undefined;
|
|
122
|
+
};
|
package/cli/src/commands/docs.ts
CHANGED
|
@@ -1,42 +1,57 @@
|
|
|
1
|
-
import { Argument, Command, Flag } from "effect/unstable/cli"
|
|
2
|
-
import { Console, Effect, Option } from "effect"
|
|
3
|
-
import { bail } from "../engine"
|
|
4
|
-
import overview from "../../docs/overview.md" with { type: "text" }
|
|
5
|
-
import flags from "../../docs/flags.md" with { type: "text" }
|
|
6
|
-
import modifiers from "../../docs/modifiers.md" with { type: "text" }
|
|
7
|
-
import models from "../../docs/models.md" with { type: "text" }
|
|
8
|
-
import toolchain from "../../docs/toolchain.md" with { type: "text" }
|
|
9
|
-
import plugins from "../../docs/plugins.md" with { type: "text" }
|
|
10
|
-
import syncLayers from "../../docs/sync-layers.md" with { type: "text" }
|
|
11
|
-
import install from "../../docs/install.md" with { type: "text" }
|
|
12
|
-
import platforms from "../../docs/platforms.md" with { type: "text" }
|
|
13
|
-
import ompModels from "../../docs/omp-models.md" with { type: "text" }
|
|
1
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
2
|
+
import { Console, Effect, Option } from "effect";
|
|
3
|
+
import { bail } from "../engine";
|
|
4
|
+
import overview from "../../docs/overview.md" with { type: "text" };
|
|
5
|
+
import flags from "../../docs/flags.md" with { type: "text" };
|
|
6
|
+
import modifiers from "../../docs/modifiers.md" with { type: "text" };
|
|
7
|
+
import models from "../../docs/models.md" with { type: "text" };
|
|
8
|
+
import toolchain from "../../docs/toolchain.md" with { type: "text" };
|
|
9
|
+
import plugins from "../../docs/plugins.md" with { type: "text" };
|
|
10
|
+
import syncLayers from "../../docs/sync-layers.md" with { type: "text" };
|
|
11
|
+
import install from "../../docs/install.md" with { type: "text" };
|
|
12
|
+
import platforms from "../../docs/platforms.md" with { type: "text" };
|
|
13
|
+
import ompModels from "../../docs/omp-models.md" with { type: "text" };
|
|
14
14
|
|
|
15
15
|
const TOPICS: Record<string, { summary: string; body: string }> = {
|
|
16
|
-
|
|
17
|
-
"sync-layers": {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
16
|
+
overview: { summary: "What docks-kit is and how the pieces fit", body: overview },
|
|
17
|
+
"sync-layers": {
|
|
18
|
+
summary: "The three sync layers and additive-by-default semantics",
|
|
19
|
+
body: syncLayers,
|
|
20
|
+
},
|
|
21
|
+
flags: { summary: "Full flag reference incl. the old→new rename table", body: flags },
|
|
22
|
+
modifiers: {
|
|
23
|
+
summary: "Deploy-time modifiers and the flag-less-sync-reverts contract",
|
|
24
|
+
body: modifiers,
|
|
25
|
+
},
|
|
26
|
+
models: { summary: "Model catalog, validation rules, model get/set", body: models },
|
|
27
|
+
toolchain: { summary: "Verified-version floors and the doctor table", body: toolchain },
|
|
28
|
+
plugins: { summary: "enabledPlugins tri-state + optional plugin opt-ins", body: plugins },
|
|
29
|
+
install: {
|
|
30
|
+
summary: "Install paths: repo checkout, bun add -g, POSIX/Windows installers",
|
|
31
|
+
body: install,
|
|
32
|
+
},
|
|
33
|
+
platforms: {
|
|
34
|
+
summary: "Platform support: Linux, macOS, and Windows on x64 and arm64",
|
|
35
|
+
body: platforms,
|
|
36
|
+
},
|
|
37
|
+
"omp-models": {
|
|
38
|
+
summary: "omp role map and the Artificial Analysis snapshot behind it",
|
|
39
|
+
body: ompModels,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
27
42
|
|
|
28
43
|
const topic = Argument.String("topic").pipe(
|
|
29
44
|
Argument.withDescription(`One of: ${Object.keys(TOPICS).join(", ")}`),
|
|
30
|
-
Argument.optional
|
|
31
|
-
)
|
|
45
|
+
Argument.optional,
|
|
46
|
+
);
|
|
32
47
|
const json = Flag.Boolean("json").pipe(
|
|
33
48
|
Flag.withDescription("List topics as JSON"),
|
|
34
|
-
Flag.withDefault(false)
|
|
35
|
-
)
|
|
49
|
+
Flag.withDefault(false),
|
|
50
|
+
);
|
|
36
51
|
|
|
37
52
|
export const docsCommand = Command.make("docs", { topic, json }, (config) =>
|
|
38
53
|
Effect.gen(function* () {
|
|
39
|
-
const requested = Option.getOrUndefined(config.topic)
|
|
54
|
+
const requested = Option.getOrUndefined(config.topic);
|
|
40
55
|
|
|
41
56
|
if (requested === undefined) {
|
|
42
57
|
if (config.json) {
|
|
@@ -44,25 +59,25 @@ export const docsCommand = Command.make("docs", { topic, json }, (config) =>
|
|
|
44
59
|
JSON.stringify(
|
|
45
60
|
Object.entries(TOPICS).map(([name, t]) => ({ name, summary: t.summary })),
|
|
46
61
|
null,
|
|
47
|
-
2
|
|
48
|
-
)
|
|
49
|
-
)
|
|
62
|
+
2,
|
|
63
|
+
),
|
|
64
|
+
);
|
|
50
65
|
}
|
|
51
|
-
yield* Console.log("docks-kit documentation topics (docks-kit docs <topic>):\n")
|
|
66
|
+
yield* Console.log("docks-kit documentation topics (docks-kit docs <topic>):\n");
|
|
52
67
|
for (const [name, t] of Object.entries(TOPICS)) {
|
|
53
|
-
yield* Console.log(` ${name.padEnd(14)} ${t.summary}`)
|
|
68
|
+
yield* Console.log(` ${name.padEnd(14)} ${t.summary}`);
|
|
54
69
|
}
|
|
55
|
-
return
|
|
70
|
+
return;
|
|
56
71
|
}
|
|
57
72
|
|
|
58
|
-
const entry = TOPICS[requested]
|
|
73
|
+
const entry = TOPICS[requested];
|
|
59
74
|
if (entry === undefined) {
|
|
60
|
-
return yield* bail(`Unknown topic '${requested}'. Valid: ${Object.keys(TOPICS).join(", ")}`)
|
|
75
|
+
return yield* bail(`Unknown topic '${requested}'. Valid: ${Object.keys(TOPICS).join(", ")}`);
|
|
61
76
|
}
|
|
62
|
-
yield* Console.log(entry.body)
|
|
63
|
-
})
|
|
77
|
+
yield* Console.log(entry.body);
|
|
78
|
+
}),
|
|
64
79
|
).pipe(
|
|
65
80
|
Command.withDescription(
|
|
66
|
-
"Self-documentation: the kit's concepts as readable topics, bundled with the CLI — a fresh agent can learn the kit from here alone."
|
|
67
|
-
)
|
|
68
|
-
)
|
|
81
|
+
"Self-documentation: the kit's concepts as readable topics, bundled with the CLI — a fresh agent can learn the kit from here alone.",
|
|
82
|
+
),
|
|
83
|
+
);
|