docks-kit 0.17.1 → 0.17.3

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