@nodeint/migent 0.1.6 → 0.1.8

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.
@@ -1,25 +1,30 @@
1
1
  import { readFileSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { confirm as clackConfirm, isCancel, select as clackSelect, text as clackText } from "@clack/prompts";
4
- import { ConfigEditError, ConfigurationError, assignRoles, describeChange, diagnose, inspectConfig, loadModelTarget, loadWorkflowCatalog, loadYamlMapping, matchingModelName, patchConfigText, placeRoleModel, isRoleStage, resolveStageSpec, setRoleOptions, upsertModel, whichSync, } from "@nodeint/migent-kernel";
4
+ import { ConfigEditError, ConfigurationError, LOCAL_CONFIG_FILE, assignRoles, configPath, describeChange, diagnose, hasLocalConfig, inspectConfig, loadModelTarget, loadWorkflowCatalog, loadWorkspaceConfig, loadYamlMapping, matchingModelName, patchConfigText, placeRoleModel, isRoleStage, resolveStageSpec, setRoleOptions, stringifyConfig, upsertModel, whichSync, } from "@nodeint/migent-kernel";
5
5
  import { printError } from "../display.js";
6
6
  import { PromptCancelled } from "../errors.js";
7
7
  import { cliHooks } from "../hooks.js";
8
8
  import { requireCurrentSchema } from "../migrate.js";
9
- import { acceptedOptions, adapterMap, asCatalog, detected, knownModel, loadCatalog, loadOptionValues, parseOptions, requireAdapter, runProviderCommand, } from "../provider.js";
9
+ import { acceptedOptions, providerMap, asCatalog, detected, knownModel, loadCatalog, loadOptionValues, parseOptions, requireProvider, runProviderCommand, } from "../provider.js";
10
10
  import { runDoctor } from "./doctor.js";
11
11
  const NEW = "__new__";
12
12
  const UNSET = Symbol("unset");
13
13
  export function runConfigShow(workspace) {
14
- const text = readFileSync(path.join(workspace, ".migent", "config.yaml"), "utf8");
15
- process.stdout.write(text);
14
+ if (!hasLocalConfig(workspace)) {
15
+ process.stdout.write(readFileSync(configPath(workspace), "utf8"));
16
+ return 0;
17
+ }
18
+ process.stdout.write(stringifyConfig(loadWorkspaceConfig(workspace)));
19
+ process.stderr.write(`Merged with .migent/${LOCAL_CONFIG_FILE}.
20
+ `);
16
21
  return 0;
17
22
  }
18
23
  export async function runConfigModel(workspace, options = {}) {
19
24
  try {
20
25
  return await runModel(workspace, {
21
26
  ...options,
22
- adapters: options.adapters ?? adapterMap(),
27
+ providers: options.providers ?? providerMap(),
23
28
  which: options.which ?? cliHooks.which ?? whichSync,
24
29
  prompter: options.prompter ?? new ClackConfigPrompter(),
25
30
  runCommand: options.runCommand ?? runProviderCommand,
@@ -39,7 +44,7 @@ export async function runConfigRole(workspace, options = {}) {
39
44
  try {
40
45
  return await runRole(workspace, {
41
46
  ...options,
42
- adapters: options.adapters ?? adapterMap(),
47
+ providers: options.providers ?? providerMap(),
43
48
  which: options.which ?? cliHooks.which ?? whichSync,
44
49
  prompter: options.prompter ?? new ClackConfigPrompter(),
45
50
  runCommand: options.runCommand ?? runProviderCommand,
@@ -69,7 +74,7 @@ async function runModel(workspace, args) {
69
74
  const mutating = provider !== null || optionUpdates !== UNSET;
70
75
  if (key === null && !mutating) {
71
76
  if (interactive) {
72
- return interactiveModels(workspace, config, workflows, view, args.adapters, args.which, args.prompter, args.runCommand, asJson);
77
+ return interactiveModels(workspace, config, workflows, view, args.providers, args.which, args.prompter, args.runCommand, asJson);
73
78
  }
74
79
  emitView(view, asJson, true, false);
75
80
  return 0;
@@ -78,19 +83,19 @@ async function runModel(workspace, args) {
78
83
  throw new ConfigEditError("Pass a name.");
79
84
  if (!mutating) {
80
85
  if (interactive) {
81
- return editModel(workspace, config, workflows, view, key, args.adapters, args.which, args.prompter, args.runCommand, asJson);
86
+ return editModel(workspace, config, workflows, view, key, args.providers, args.which, args.prompter, args.runCommand, asJson);
82
87
  }
83
88
  emitModels(selectedModels(view, [key]), asJson);
84
89
  return 0;
85
90
  }
86
91
  let catalog = null;
87
92
  if (provider !== null && model !== null) {
88
- [provider, model, catalog] = await catalogModel(args.adapters, args.which, args.runCommand, provider, model);
93
+ [provider, model, catalog] = await catalogModel(args.providers, args.which, args.runCommand, provider, model);
89
94
  }
90
95
  if (optionUpdates !== UNSET) {
91
96
  const currentProvider = provider ?? modelProvider(config, key);
92
97
  const currentModel = model ?? loadModelTarget(config, key).model;
93
- optionUpdates = await checkedOptions(args.adapters, args.which, args.runCommand, currentProvider, currentModel, optionUpdates, provider !== null ? catalog : null);
98
+ optionUpdates = await checkedOptions(args.providers, args.which, args.runCommand, currentProvider, currentModel, optionUpdates, provider !== null ? catalog : null);
94
99
  }
95
100
  const change = upsertModel(config, key, {
96
101
  provider,
@@ -98,7 +103,7 @@ async function runModel(workspace, args) {
98
103
  ...(optionUpdates === UNSET ? {} : { options: optionUpdates }),
99
104
  workflows,
100
105
  });
101
- validateChange(config, change.config, workflows, args.adapters);
106
+ validateChange(config, change.config, workflows, args.providers);
102
107
  return await commit(workspace, change, { asJson, which: args.which });
103
108
  }
104
109
  async function runRole(workspace, args) {
@@ -119,7 +124,7 @@ async function runRole(workspace, args) {
119
124
  const mutating = modelName !== null || provider !== null || optionUpdates !== UNSET;
120
125
  if (roles.length === 0 && !mutating) {
121
126
  if (interactive) {
122
- return interactiveRoles(workspace, config, workflows, view, args.adapters, args.which, args.prompter, args.runCommand, asJson);
127
+ return interactiveRoles(workspace, config, workflows, view, args.providers, args.which, args.prompter, args.runCommand, asJson);
123
128
  }
124
129
  emitView(view, asJson, false, true);
125
130
  return 0;
@@ -132,7 +137,7 @@ async function runRole(workspace, args) {
132
137
  if (!mutating) {
133
138
  if (interactive && roles.length === 1) {
134
139
  knownRole(view, roles[0] ?? "");
135
- return pointRole(workspace, config, workflows, view, roles[0] ?? "", args.adapters, args.which, args.prompter, args.runCommand, asJson);
140
+ return pointRole(workspace, config, workflows, view, roles[0] ?? "", args.providers, args.which, args.prompter, args.runCommand, asJson);
136
141
  }
137
142
  emitRoles(selectedRoles(view, roles), asJson);
138
143
  return 0;
@@ -140,9 +145,9 @@ async function runRole(workspace, args) {
140
145
  let change;
141
146
  if (provider !== null && model !== null) {
142
147
  let catalog;
143
- [provider, model, catalog] = await catalogModel(args.adapters, args.which, args.runCommand, provider, model);
148
+ [provider, model, catalog] = await catalogModel(args.providers, args.which, args.runCommand, provider, model);
144
149
  if (optionUpdates !== UNSET) {
145
- optionUpdates = await checkedOptions(args.adapters, args.which, args.runCommand, provider, model, optionUpdates, catalog);
150
+ optionUpdates = await checkedOptions(args.providers, args.which, args.runCommand, provider, model, optionUpdates, catalog);
146
151
  }
147
152
  change = placeRoleModel(config, roles[0] ?? "", {
148
153
  provider,
@@ -155,7 +160,7 @@ async function runRole(workspace, args) {
155
160
  change = assignRoles(config, roles, modelName, { workflows });
156
161
  if (optionUpdates !== UNSET) {
157
162
  const target = loadModelTarget(change.config, modelName);
158
- optionUpdates = await checkedOptions(args.adapters, args.which, args.runCommand, target.provider, target.model, optionUpdates);
163
+ optionUpdates = await checkedOptions(args.providers, args.which, args.runCommand, target.provider, target.model, optionUpdates);
159
164
  const follow = setRoleOptions(change.config, roles[0] ?? "", optionUpdates, { workflows });
160
165
  change = describeChange(config, follow.config, workflows);
161
166
  }
@@ -164,40 +169,40 @@ async function runRole(workspace, args) {
164
169
  const target = roleModel(config, roles[0] ?? "");
165
170
  if (optionUpdates === UNSET)
166
171
  throw new ConfigEditError("Pass a role.");
167
- optionUpdates = await checkedOptions(args.adapters, args.which, args.runCommand, target.provider, target.model, optionUpdates);
172
+ optionUpdates = await checkedOptions(args.providers, args.which, args.runCommand, target.provider, target.model, optionUpdates);
168
173
  change = setRoleOptions(config, roles[0] ?? "", optionUpdates, { workflows });
169
174
  }
170
- validateChange(config, change.config, workflows, args.adapters);
175
+ validateChange(config, change.config, workflows, args.providers);
171
176
  return await commit(workspace, change, { asJson, which: args.which });
172
177
  }
173
- async function interactiveModels(workspace, config, workflows, view, adapters, which, prompter, runCommand, asJson) {
178
+ async function interactiveModels(workspace, config, workflows, view, providers, which, prompter, runCommand, asJson) {
174
179
  const choices = view.models.map((model) => [modelLabel(model), model.key]);
175
180
  choices.push(["New model", NEW]);
176
181
  const selected = await prompter.select("Model", choices, view.models[0]?.key ?? NEW);
177
182
  if (selected === NEW)
178
- return addModel(workspace, config, workflows, null, adapters, which, prompter, runCommand, asJson);
179
- return editModel(workspace, config, workflows, view, selected, adapters, which, prompter, runCommand, asJson);
183
+ return addModel(workspace, config, workflows, null, providers, which, prompter, runCommand, asJson);
184
+ return editModel(workspace, config, workflows, view, selected, providers, which, prompter, runCommand, asJson);
180
185
  }
181
- async function editModel(workspace, config, workflows, view, key, adapters, which, prompter, runCommand, asJson) {
186
+ async function editModel(workspace, config, workflows, view, key, providers, which, prompter, runCommand, asJson) {
182
187
  const current = selectedModels(view, [key])[0];
183
188
  if (current === undefined)
184
189
  throw new ConfigEditError(`Unknown name: ${key}.`);
185
- const provider = await selectProvider(prompter, adapters, which, "Provider", current.provider);
186
- const adapter = requireAdapter(adapters, provider);
187
- const catalog = await loadCatalog(adapter, which, runCommand);
190
+ const provider = await selectProvider(prompter, providers, which, "Provider", current.provider);
191
+ const impl = requireProvider(providers, provider);
192
+ const catalog = await loadCatalog(impl, which, runCommand);
188
193
  const initial = catalog.ids.includes(current.model) ? current.model : (catalog.defaultId ?? undefined);
189
194
  let modelId = knownModel(await prompter.select("Model", catalog.ids.map((item) => [item, item]), initial), catalog);
190
- const stored = await promptedOptions(prompter, adapter, modelId, catalog, which, runCommand, current.options);
195
+ const stored = await promptedOptions(prompter, impl, modelId, catalog, which, runCommand, current.options);
191
196
  const change = upsertModel(config, key, { provider, model: modelId, options: stored, workflows });
192
- validateChange(config, change.config, workflows, adapters);
197
+ validateChange(config, change.config, workflows, providers);
193
198
  return confirmAndCommit(workspace, change, prompter, asJson, which);
194
199
  }
195
- async function addModel(workspace, config, workflows, role, adapters, which, prompter, runCommand, asJson) {
196
- const provider = await selectProvider(prompter, adapters, which, "Provider", null);
197
- const adapter = requireAdapter(adapters, provider);
198
- const catalog = await loadCatalog(adapter, which, runCommand);
200
+ async function addModel(workspace, config, workflows, role, providers, which, prompter, runCommand, asJson) {
201
+ const provider = await selectProvider(prompter, providers, which, "Provider", null);
202
+ const impl = requireProvider(providers, provider);
203
+ const catalog = await loadCatalog(impl, which, runCommand);
199
204
  const modelId = knownModel(await prompter.select("Model", catalog.ids.map((item) => [item, item]), catalog.defaultId), catalog);
200
- const options = await promptedOptions(prompter, adapter, modelId, catalog, which, runCommand, {});
205
+ const options = await promptedOptions(prompter, impl, modelId, catalog, which, runCommand, {});
201
206
  const present = presentOptions(options);
202
207
  const existing = matchingModelName(config, { provider, model: modelId, options: present });
203
208
  let change;
@@ -224,10 +229,10 @@ async function addModel(workspace, config, workflows, role, adapters, which, pro
224
229
  change = describeChange(config, assigned.config, workflows);
225
230
  }
226
231
  }
227
- validateChange(config, change.config, workflows, adapters);
232
+ validateChange(config, change.config, workflows, providers);
228
233
  return confirmAndCommit(workspace, change, prompter, asJson, which);
229
234
  }
230
- async function interactiveRoles(workspace, config, workflows, view, adapters, which, prompter, runCommand, asJson) {
235
+ async function interactiveRoles(workspace, config, workflows, view, providers, which, prompter, runCommand, asJson) {
231
236
  const choices = view.roles.map((role) => [roleLabel(role), role.role]);
232
237
  choices.push(["New role", NEW]);
233
238
  const selected = await prompter.select("Role", choices, view.roles[0]?.role ?? NEW);
@@ -237,20 +242,20 @@ async function interactiveRoles(workspace, config, workflows, view, adapters, wh
237
242
  throw new ConfigEditError("Role name is required.");
238
243
  if (view.roles.some((role) => role.role === name))
239
244
  throw new ConfigEditError(`Role ${name} already exists.`);
240
- return pointRole(workspace, config, workflows, view, name, adapters, which, prompter, runCommand, asJson);
245
+ return pointRole(workspace, config, workflows, view, name, providers, which, prompter, runCommand, asJson);
241
246
  }
242
- return pointRole(workspace, config, workflows, view, selected, adapters, which, prompter, runCommand, asJson);
247
+ return pointRole(workspace, config, workflows, view, selected, providers, which, prompter, runCommand, asJson);
243
248
  }
244
- async function pointRole(workspace, config, workflows, view, role, adapters, which, prompter, runCommand, asJson) {
249
+ async function pointRole(workspace, config, workflows, view, role, providers, which, prompter, runCommand, asJson) {
245
250
  const current = view.roles.find((item) => item.role === role) ?? null;
246
251
  const choices = view.models.map((model) => [modelLabel(model), model.key]);
247
252
  choices.push(["New model", NEW]);
248
253
  const selected = await prompter.select("Model", choices, current?.modelName ?? NEW);
249
254
  if (selected === NEW) {
250
- return addModel(workspace, config, workflows, role, adapters, which, prompter, runCommand, asJson);
255
+ return addModel(workspace, config, workflows, role, providers, which, prompter, runCommand, asJson);
251
256
  }
252
257
  const change = assignRoles(config, [role], selected, { workflows });
253
- validateChange(config, change.config, workflows, adapters);
258
+ validateChange(config, change.config, workflows, providers);
254
259
  return confirmAndCommit(workspace, change, prompter, asJson, which);
255
260
  }
256
261
  async function confirmAndCommit(workspace, change, prompter, asJson, which) {
@@ -262,7 +267,8 @@ async function confirmAndCommit(workspace, change, prompter, asJson, which) {
262
267
  return commit(workspace, change, { asJson, which, echo: false });
263
268
  }
264
269
  async function commit(workspace, change, args) {
265
- const filePath = path.join(workspace, ".migent", "config.yaml");
270
+ // Edits patch config.yaml only, so config.local.yaml values never reach the committed file.
271
+ const filePath = configPath(workspace);
266
272
  const original = readFileSync(filePath, "utf8");
267
273
  const updated = patchConfigText(original, change.config);
268
274
  if (updated === original) {
@@ -274,7 +280,7 @@ async function commit(workspace, change, args) {
274
280
  }
275
281
  writeFileSync(filePath, updated, "utf8");
276
282
  if (args.asJson) {
277
- const checks = diagnose(workspace, { adapters: adapterMap(), which: args.which });
283
+ const checks = diagnose(workspace, { providers: providerMap(), which: args.which });
278
284
  const doctor = doctorPayload(checks);
279
285
  process.stdout.write(`${JSON.stringify(changePayload(change, true, doctor))}\n`);
280
286
  return doctor.ok ? 0 : 1;
@@ -283,7 +289,7 @@ async function commit(workspace, change, args) {
283
289
  printChange(change);
284
290
  return runDoctor(workspace, { which: args.which });
285
291
  }
286
- function validateChange(before, after, workflows, adapters) {
292
+ function validateChange(before, after, workflows, providers) {
287
293
  const beforeModels = isRecord(before.models) ? before.models : {};
288
294
  const afterModels = isRecord(after.models) ? after.models : {};
289
295
  const changed = new Set();
@@ -294,7 +300,7 @@ function validateChange(before, after, workflows, adapters) {
294
300
  }
295
301
  for (const key of changed) {
296
302
  const target = loadModelTarget(after, key);
297
- requireAdapter(adapters, target.provider).validateOptions(target.options);
303
+ requireProvider(providers, target.provider).validateOptions(target.options);
298
304
  }
299
305
  const beforeRoles = isRecord(before.roles) ? before.roles : {};
300
306
  const afterRoles = isRecord(after.roles) ? after.roles : {};
@@ -312,7 +318,7 @@ function validateChange(before, after, workflows, adapters) {
312
318
  options[optionName] = value.trim();
313
319
  }
314
320
  }
315
- requireAdapter(adapters, target.provider).validateOptions(options);
321
+ requireProvider(providers, target.provider).validateOptions(options);
316
322
  }
317
323
  for (const document of workflows) {
318
324
  for (const stage of Object.values(document.stages)) {
@@ -331,35 +337,35 @@ function validateChange(before, after, workflows, adapters) {
331
337
  }
332
338
  if (sameTarget(beforeTarget, afterTarget))
333
339
  continue;
334
- requireAdapter(adapters, afterTarget.provider).validateOptions(afterTarget.options);
340
+ requireProvider(providers, afterTarget.provider).validateOptions(afterTarget.options);
335
341
  }
336
342
  }
337
343
  }
338
- async function catalogModel(adapters, which, runCommand, provider, model) {
339
- const adapter = requireAdapter(adapters, provider);
340
- const catalog = await loadCatalog(adapter, which, runCommand);
344
+ async function catalogModel(providers, which, runCommand, provider, model) {
345
+ const impl = requireProvider(providers, provider);
346
+ const catalog = await loadCatalog(impl, which, runCommand);
341
347
  return [provider, knownModel(model, catalog), catalog];
342
348
  }
343
- async function checkedOptions(adapters, which, runCommand, provider, model, updates, catalog = null) {
344
- const adapter = requireAdapter(adapters, provider);
345
- const resolved = catalog ?? (await loadCatalog(adapter, which, runCommand));
349
+ async function checkedOptions(providers, which, runCommand, provider, model, updates, catalog = null) {
350
+ const impl = requireProvider(providers, provider);
351
+ const resolved = catalog ?? (await loadCatalog(impl, which, runCommand));
346
352
  const present = {};
347
353
  for (const [key, value] of Object.entries(updates)) {
348
354
  if (value !== null)
349
355
  present[key] = value;
350
356
  }
351
357
  if (Object.keys(present).length > 0) {
352
- await acceptedOptions(adapter, model, resolved, which, runCommand, present);
358
+ await acceptedOptions(impl, model, resolved, which, runCommand, present);
353
359
  }
354
360
  return { ...updates };
355
361
  }
356
- async function promptedOptions(prompter, adapter, model, catalog, which, runCommand, current) {
362
+ async function promptedOptions(prompter, provider, model, catalog, which, runCommand, current) {
357
363
  const stored = {};
358
- const catalogAdapter = asCatalog(adapter);
359
- for (const spec of catalogAdapter.providerOptions()) {
364
+ const catalogProvider = asCatalog(provider);
365
+ for (const spec of catalogProvider.providerOptions()) {
360
366
  if (!spec.prompt)
361
367
  continue;
362
- const values = await loadOptionValues(adapter, spec.name, model, catalog, which, runCommand);
368
+ const values = await loadOptionValues(provider, spec.name, model, catalog, which, runCommand);
363
369
  if (values === null || values.length === 0)
364
370
  continue;
365
371
  const choices = values.map((value) => [value, value]);
@@ -386,8 +392,8 @@ function optionUpdatesOf(options, clearOptions) {
386
392
  }
387
393
  return updates;
388
394
  }
389
- async function selectProvider(prompter, adapters, which, message, defaultValue) {
390
- const rows = detected(adapters, which);
395
+ async function selectProvider(prompter, providers, which, message, defaultValue) {
396
+ const rows = detected(providers, which);
391
397
  if (rows.length === 0)
392
398
  throw new ConfigurationError("No providers are available.");
393
399
  const names = new Set(rows.map(([name]) => name));
@@ -395,7 +401,8 @@ async function selectProvider(prompter, adapters, which, message, defaultValue)
395
401
  }
396
402
  function loaded(workspace) {
397
403
  requireCurrentSchema(workspace);
398
- const config = loadYamlMapping(path.join(workspace, ".migent", "config.yaml"));
404
+ // Base file only: this snapshot feeds validation and patching of config.yaml.
405
+ const config = loadYamlMapping(configPath(workspace));
399
406
  const workflows = Object.values(loadWorkflowCatalog(workspace).documents);
400
407
  return [config, workflows, inspectConfig(config, workflows)];
401
408
  }
@@ -2,11 +2,11 @@ import { diagnose, whichSync } from "@nodeint/migent-kernel";
2
2
  import { printDoctor } from "../display.js";
3
3
  import { cliHooks } from "../hooks.js";
4
4
  import { requireCurrentSchema } from "../migrate.js";
5
- import { adapterMap } from "../provider.js";
5
+ import { providerMap } from "../provider.js";
6
6
  export async function runDoctor(workspace, options = {}) {
7
7
  requireCurrentSchema(workspace);
8
8
  const which = options.which ?? cliHooks.which ?? whichSync;
9
- const checks = diagnose(workspace, { adapters: adapterMap(), which });
9
+ const checks = diagnose(workspace, { providers: providerMap(), which });
10
10
  if (options.asJson)
11
11
  process.stdout.write(`${JSON.stringify(payload(checks))}\n`);
12
12
  else
@@ -21,6 +21,7 @@ function payload(checks) {
21
21
  ok: check.ok,
22
22
  items: [...check.items],
23
23
  problems: [...check.problems],
24
+ warnings: [...check.warnings],
24
25
  })),
25
26
  };
26
27
  }
@@ -4,7 +4,7 @@ import { AgentToolRunner, DEFAULT_MAX_DISPATCHES, executeNamedWorkflow, formatDu
4
4
  import { printResumeCommand } from "../display.js";
5
5
  import { cliHooks } from "../hooks.js";
6
6
  import { requireCurrentSchema } from "../migrate.js";
7
- import { adapterMap } from "../provider.js";
7
+ import { providerMap } from "../provider.js";
8
8
  import { text } from "../text.js";
9
9
  export async function executeWorkflow(workspace, options) {
10
10
  requireCurrentSchema(workspace);
@@ -13,7 +13,7 @@ export async function executeWorkflow(workspace, options) {
13
13
  const sessionId = text(options.sessionId);
14
14
  const creating = !startPrepared && (options.action === "start" || (once && sessionId === null));
15
15
  const maxDispatches = options.maxDispatches === undefined ? DEFAULT_MAX_DISPATCHES : options.maxDispatches;
16
- const runner = options.runner ?? cliHooks.runner ?? new AgentToolRunner(adapterMap());
16
+ const runner = options.runner ?? cliHooks.runner ?? new AgentToolRunner(providerMap());
17
17
  runner.itemProgress = options.itemProgress === true;
18
18
  const result = await executeNamedWorkflow(workspace, {
19
19
  workflowId: creating || startPrepared ? (options.workflowId ?? null) : null,
@@ -1,5 +1,5 @@
1
1
  import type { ModelCatalog } from "@nodeint/migent-adapters";
2
- import { type AgentAdapter } from "@nodeint/migent-kernel";
2
+ import { type Provider } from "@nodeint/migent-kernel";
3
3
  import { type RunCommand, type Which } from "../provider.js";
4
4
  export declare const PRESETS: readonly ["implement", "plan"];
5
5
  export interface ModelChoice {
@@ -8,7 +8,7 @@ export interface ModelChoice {
8
8
  options: Record<string, string>;
9
9
  }
10
10
  export interface Prompter {
11
- selectProvider(rows: ReadonlyArray<readonly [string, string | null, AgentAdapter]>, message: string): string | Promise<string>;
11
+ selectProvider(rows: ReadonlyArray<readonly [string, string | null, Provider]>, message: string): string | Promise<string>;
12
12
  selectModel(provider: string, catalog: ModelCatalog, message: string): string | Promise<string>;
13
13
  selectOption(name: string, values: readonly string[], allowDefault: boolean): string | Promise<string>;
14
14
  confirm(message: string): boolean | Promise<boolean>;
@@ -21,7 +21,7 @@ export declare function runInit(workspace: string, options?: {
21
21
  presetExplicit?: boolean;
22
22
  skill?: boolean;
23
23
  interactive?: boolean;
24
- adapters?: Record<string, AgentAdapter> | null;
24
+ providers?: Record<string, Provider> | null;
25
25
  which?: Which | null;
26
26
  prompter?: Prompter | null;
27
27
  runCommand?: RunCommand | null;
@@ -8,7 +8,7 @@ import { printAlreadyInitialized, printCreated, printError, printInitConflicts,
8
8
  import { cliHooks } from "../hooks.js";
9
9
  import { runDoctor } from "./doctor.js";
10
10
  import { installSkill } from "./skill.js";
11
- import { acceptedOptions, adapterMap, asCatalog, detected, knownModel, loadCatalog, loadOptionValues, parseOptions, requireAdapter, runProviderCommand, } from "../provider.js";
11
+ import { acceptedOptions, providerMap, asCatalog, detected, knownModel, loadCatalog, loadOptionValues, parseOptions, requireProvider, runProviderCommand, } from "../provider.js";
12
12
  export const PRESETS = ["implement", "plan"];
13
13
  export async function runInit(workspace, options = {}) {
14
14
  const provider = options.provider ?? null;
@@ -16,7 +16,7 @@ export async function runInit(workspace, options = {}) {
16
16
  const preset = options.preset ?? "implement";
17
17
  const skill = options.skill === true;
18
18
  const interactive = options.interactive ?? false;
19
- const chosen = options.adapters ?? adapterMap();
19
+ const chosen = options.providers ?? providerMap();
20
20
  const resolve = options.which ?? cliHooks.which ?? whichSync;
21
21
  const prompt = options.prompter ?? new ClackPrompter();
22
22
  const execute = options.runCommand ?? runProviderCommand;
@@ -94,42 +94,42 @@ export async function runInit(workspace, options = {}) {
94
94
  }
95
95
  return 0;
96
96
  }
97
- function writeDoctorProblems(workspace, adapters, which) {
98
- const problems = diagnose(workspace, { adapters, which }).flatMap((check) => check.problems);
97
+ function writeDoctorProblems(workspace, providers, which) {
98
+ const problems = diagnose(workspace, { providers, which }).flatMap((check) => check.problems);
99
99
  if (problems.length === 0)
100
100
  return;
101
101
  process.stderr.write("Doctor reported problems:\n");
102
102
  for (const problem of problems)
103
103
  process.stderr.write(`${problem}\n`);
104
104
  }
105
- async function choices(adapters, which, prompt, args) {
105
+ async function choices(providers, which, prompt, args) {
106
106
  let provider = args.provider;
107
107
  let model = args.model;
108
108
  let stored;
109
109
  if (provider === null) {
110
- const rows = detected(adapters, which);
110
+ const rows = detected(providers, which);
111
111
  provider = await prompt.selectProvider(rows, "Default provider");
112
- const adapter = adapters[provider];
113
- if (adapter === undefined)
112
+ const impl = providers[provider];
113
+ if (impl === undefined)
114
114
  throw new ConfigurationError(`Unknown provider: ${provider}.`);
115
- const catalog = await loadCatalog(adapter, which, args.runCommand);
115
+ const catalog = await loadCatalog(impl, which, args.runCommand);
116
116
  model = await prompt.selectModel(provider, catalog, "Model");
117
- stored = await promptOptions(adapter, model, catalog, which, args.runCommand, prompt);
117
+ stored = await promptOptions(impl, model, catalog, which, args.runCommand, prompt);
118
118
  }
119
119
  else {
120
- const adapter = requireAdapter(adapters, provider);
120
+ const impl = requireProvider(providers, provider);
121
121
  if (model === null)
122
122
  throw new ConfigurationError("Model is required.");
123
- const catalog = await loadCatalog(adapter, which, args.runCommand);
123
+ const catalog = await loadCatalog(impl, which, args.runCommand);
124
124
  model = knownModel(model, catalog);
125
- stored = await acceptedOptions(adapter, model, catalog, which, args.runCommand, args.options);
125
+ stored = await acceptedOptions(impl, model, catalog, which, args.runCommand, args.options);
126
126
  }
127
127
  if (model.trim() === "")
128
128
  throw new ConfigurationError("Model is required.");
129
129
  const primary = { provider, model: model.trim(), options: stored };
130
130
  return primary;
131
131
  }
132
- function classify(workspace, adapters, which) {
132
+ function classify(workspace, providers, which) {
133
133
  const root = path.join(workspace, ".migent");
134
134
  const config = path.join(root, "config.yaml");
135
135
  const workflows = path.join(root, "workflows");
@@ -154,7 +154,7 @@ function classify(workspace, adapters, which) {
154
154
  ? readdirSync(workflows).filter((name) => name.endsWith(".yaml") && isFile(path.join(workflows, name)))
155
155
  : [];
156
156
  if (isFile(config) && yamlFiles.length > 0) {
157
- const checks = diagnose(workspace, { adapters, which });
157
+ const checks = diagnose(workspace, { providers, which });
158
158
  const byName = new Map(checks.map((check) => [check.name, check]));
159
159
  if (byName.get("config")?.ok && byName.get("workflows")?.ok)
160
160
  return { state: "ready", conflicts: [] };
@@ -168,7 +168,7 @@ function writeProject(workspace, preset, primary) {
168
168
  writeFileSync(path.join(root, "config.yaml"), renderConfig(preset, primary), "utf8");
169
169
  const workflowName = `${preset}.yaml`;
170
170
  writeFileSync(path.join(workflows, workflowName), renderWorkflow(preset), "utf8");
171
- writeFileSync(path.join(root, ".gitignore"), "sessions/\n", "utf8");
171
+ writeFileSync(path.join(root, ".gitignore"), "sessions/\nconfig.local.yaml\n", "utf8");
172
172
  return [".migent/config.yaml", `.migent/workflows/${workflowName}`, ".migent/.gitignore"];
173
173
  }
174
174
  function renderConfig(preset, primary) {
@@ -236,12 +236,12 @@ function renderWorkflow(preset) {
236
236
  " from_stage: plan\n" +
237
237
  " artifact: plan.md\n");
238
238
  }
239
- async function promptOptions(adapter, model, catalog, which, runCommand, prompt) {
239
+ async function promptOptions(provider, model, catalog, which, runCommand, prompt) {
240
240
  const stored = {};
241
- for (const spec of providerOptions(adapter)) {
241
+ for (const spec of providerOptions(provider)) {
242
242
  if (!spec.prompt)
243
243
  continue;
244
- const values = await loadOptionValues(adapter, spec.name, model, catalog, which, runCommand);
244
+ const values = await loadOptionValues(provider, spec.name, model, catalog, which, runCommand);
245
245
  if (values === null || values.length === 0)
246
246
  continue;
247
247
  const picked = await prompt.selectOption(spec.name, values, spec.allowDefault ?? false);
@@ -250,8 +250,8 @@ async function promptOptions(adapter, model, catalog, which, runCommand, prompt)
250
250
  }
251
251
  return stored;
252
252
  }
253
- function providerOptions(adapter) {
254
- return asCatalog(adapter).providerOptions();
253
+ function providerOptions(provider) {
254
+ return asCatalog(provider).providerOptions();
255
255
  }
256
256
  class ClackPrompter {
257
257
  async selectProvider(rows, message) {
@@ -5,6 +5,7 @@ export function prepareWorkflowSession(workspace, sessionId, workflowId, priorSe
5
5
  const id = createPreparedSession(workspace, sessionId, workflowId, priorSessionId);
6
6
  const payload = {
7
7
  session_id: id,
8
+ title: sessionId.trim(),
8
9
  status: "prepared",
9
10
  task_path: relativeTaskPath(id),
10
11
  };
@@ -0,0 +1,10 @@
1
+ import { type Provider } from "@nodeint/migent-kernel";
2
+ import { type ProviderRow } from "../display.js";
3
+ import { type RunCommand, type Which } from "../provider.js";
4
+ export declare function collectProviders(providers: Record<string, Provider>, which: Which, runCommand: RunCommand): Promise<ProviderRow[]>;
5
+ export declare function runProviderList(options?: {
6
+ asJson?: boolean;
7
+ providers?: Record<string, Provider> | null;
8
+ which?: Which | null;
9
+ runCommand?: RunCommand | null;
10
+ }): Promise<number>;
@@ -0,0 +1,77 @@
1
+ import { whichSync } from "@nodeint/migent-kernel";
2
+ import { printProviders } from "../display.js";
3
+ import { cliHooks } from "../hooks.js";
4
+ import { providerMap, loadCatalog, runProviderCommand, } from "../provider.js";
5
+ function mapProviderOption(opt) {
6
+ return {
7
+ name: opt.name,
8
+ prompt: opt.prompt === true,
9
+ allow_default: opt.allowDefault === true,
10
+ overridable: opt.overridable === true,
11
+ };
12
+ }
13
+ function getProviderOptions(provider) {
14
+ const candidate = provider;
15
+ if (typeof candidate.providerOptions === "function") {
16
+ return candidate.providerOptions().map(mapProviderOption);
17
+ }
18
+ return [];
19
+ }
20
+ export async function collectProviders(providers, which, runCommand) {
21
+ // A provider without a default client has no CLI of its own to probe.
22
+ const names = Object.keys(providers).filter((name) => providers[name].requiresClient !== true).sort();
23
+ const rows = [];
24
+ for (const name of names) {
25
+ const provider = providers[name];
26
+ const path = which(provider.command);
27
+ const options = getProviderOptions(provider);
28
+ const problems = [];
29
+ let models = [];
30
+ let default_model = null;
31
+ if (path === null) {
32
+ problems.push(`${provider.command} is not on PATH.`);
33
+ }
34
+ else {
35
+ try {
36
+ const catalog = await loadCatalog(provider, which, runCommand);
37
+ models = [...catalog.ids];
38
+ default_model = catalog.defaultId;
39
+ }
40
+ catch (error) {
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ problems.push(message);
43
+ }
44
+ }
45
+ rows.push({
46
+ name,
47
+ command: provider.command,
48
+ path,
49
+ available: path !== null,
50
+ models,
51
+ default_model,
52
+ options,
53
+ problems,
54
+ });
55
+ }
56
+ return rows;
57
+ }
58
+ function providerPayload(rows) {
59
+ return {
60
+ ok: rows.every((row) => row.problems.length === 0),
61
+ providers: rows.map((row) => ({ ...row })),
62
+ };
63
+ }
64
+ export async function runProviderList(options = {}) {
65
+ const providers = options.providers ?? providerMap();
66
+ const which = options.which ?? cliHooks.which ?? whichSync;
67
+ const runCommand = options.runCommand ?? cliHooks.runCommand ?? runProviderCommand;
68
+ const rows = await collectProviders(providers, which, runCommand);
69
+ if (options.asJson) {
70
+ process.stdout.write(`${JSON.stringify(providerPayload(rows))}\n`);
71
+ }
72
+ else {
73
+ printProviders(rows);
74
+ }
75
+ const hasAnyModels = rows.some((row) => row.models.length > 0);
76
+ return hasAnyModels ? 0 : 1;
77
+ }
@@ -130,6 +130,7 @@ function sessionJson(row) {
130
130
  const fields = sessionFields(row);
131
131
  return {
132
132
  session: fields.session,
133
+ title: fields.title,
133
134
  workflow: fields.workflow,
134
135
  status: fields.status,
135
136
  execution: fields.execution,
package/dist/display.d.ts CHANGED
@@ -11,3 +11,19 @@ export declare function printAgent(result: Record<string, unknown>, role: string
11
11
  export declare function printWatchSnapshot(snapshot: Record<string, unknown>): void;
12
12
  export declare function printWatchEvent(event: Record<string, unknown>): void;
13
13
  export declare function formatWatchTimestamp(timestamp: unknown): string;
14
+ export interface ProviderRow {
15
+ name: string;
16
+ command: string;
17
+ path: string | null;
18
+ available: boolean;
19
+ models: string[];
20
+ default_model: string | null;
21
+ options: Array<{
22
+ name: string;
23
+ prompt: boolean;
24
+ allow_default: boolean;
25
+ overridable: boolean;
26
+ }>;
27
+ problems: string[];
28
+ }
29
+ export declare function printProviders(rows: readonly ProviderRow[]): void;