@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.
package/dist/display.js CHANGED
@@ -41,6 +41,8 @@ export function printDoctor(checks) {
41
41
  }
42
42
  for (const problem of check.problems)
43
43
  process.stdout.write(` ${problem}\n`);
44
+ for (const warning of check.warnings)
45
+ process.stdout.write(` warning: ${warning}\n`);
44
46
  }
45
47
  }
46
48
  export function printSessions(rows) {
@@ -50,7 +52,8 @@ export function printSessions(rows) {
50
52
  }
51
53
  for (const row of rows) {
52
54
  const fields = sessionFields(row);
53
- process.stdout.write(`${fields.session} ${fields.workflow} ${fields.status} ${fields.execution} ${fields.decision} ${fields.outputs} ${fields.task}\n`);
55
+ const identity = fields.title !== "" && fields.title !== fields.session ? `${fields.title} ${fields.session}` : fields.session;
56
+ process.stdout.write(`${identity} ${fields.workflow} ${fields.status} ${fields.execution} ${fields.decision} ${fields.outputs} ${fields.task}\n`);
54
57
  }
55
58
  }
56
59
  export function printAgent(result, role, provider, model) {
@@ -73,6 +76,10 @@ export function printWatchSnapshot(snapshot) {
73
76
  ["session_id", String(snapshot.session_id ?? "--")],
74
77
  ["status", String(snapshot.status ?? "unknown")],
75
78
  ];
79
+ const title = snapshot.title;
80
+ if (typeof title === "string" && title !== "" && title !== lines[0]?.[1]) {
81
+ lines.splice(1, 0, ["title", title]);
82
+ }
76
83
  if (typeof snapshot.workflow_id === "string" && snapshot.workflow_id) {
77
84
  lines.push(["workflow", snapshot.workflow_id]);
78
85
  }
@@ -133,6 +140,40 @@ export function formatWatchTimestamp(timestamp) {
133
140
  return timestamp.trim();
134
141
  return parsed.toISOString().replace("T", " ").replace(/\.\d+Z$/, "+00:00").replace("Z", "+00:00");
135
142
  }
143
+ export function printProviders(rows) {
144
+ const tty = Boolean(process.stdout.isTTY);
145
+ for (const row of rows) {
146
+ const ok = row.problems.length === 0 && row.models.length > 0;
147
+ const status = ok ? (tty ? pc.green("ok") : "ok") : tty ? pc.red("failed") : "failed";
148
+ process.stdout.write(`${row.name.padEnd(12)}${status}\n`);
149
+ if (row.path !== null) {
150
+ const line = ` path: ${row.path}`;
151
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
152
+ }
153
+ else {
154
+ const line = ` path: not found`;
155
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
156
+ }
157
+ if (row.models.length > 0) {
158
+ process.stdout.write(` models:\n`);
159
+ for (const model of row.models) {
160
+ const marked = model === row.default_model ? `${model} (default)` : model;
161
+ const line = ` ${marked}`;
162
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
163
+ }
164
+ }
165
+ if (row.options.length > 0) {
166
+ process.stdout.write(` options:\n`);
167
+ for (const opt of row.options) {
168
+ const line = ` ${opt.name}`;
169
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
170
+ }
171
+ }
172
+ for (const problem of row.problems) {
173
+ process.stdout.write(` ${problem}\n`);
174
+ }
175
+ }
176
+ }
136
177
  function isRecord(value) {
137
178
  return typeof value === "object" && value !== null && !Array.isArray(value);
138
179
  }
package/dist/hooks.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import type { AgentToolRunner } from "@nodeint/migent-kernel";
2
+ import type { RunCommand } from "./provider.js";
2
3
  /** Test seams. Production leaves both null. */
3
4
  export declare const cliHooks: {
4
5
  which: ((command: string) => string | null) | null;
5
6
  runner: AgentToolRunner | null;
7
+ runCommand: RunCommand | null;
6
8
  };
package/dist/hooks.js CHANGED
@@ -2,4 +2,5 @@
2
2
  export const cliHooks = {
3
3
  which: null,
4
4
  runner: null,
5
+ runCommand: null,
5
6
  };
package/dist/index.js CHANGED
@@ -36,6 +36,7 @@ export async function main(argv = process.argv.slice(2), cwd) {
36
36
  setInvocationCwd(undefined);
37
37
  cliHooks.which = null;
38
38
  cliHooks.runner = null;
39
+ cliHooks.runCommand = null;
39
40
  }
40
41
  }
41
42
  function isDirectRun() {
package/dist/migrate.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { type MigrationAdapter } from "@nodeint/migent-kernel";
1
+ import { type MigrationProvider } from "@nodeint/migent-kernel";
2
2
  export declare function requireCurrentSchema(workspace: string): void;
3
3
  export declare function runMigrate(workspace: string, options?: {
4
4
  assumeYes?: boolean;
5
5
  confirm?: ((message: string) => boolean | Promise<boolean>) | null;
6
- adapters?: Record<string, MigrationAdapter> | null;
6
+ providers?: Record<string, MigrationProvider> | null;
7
7
  }): Promise<number>;
8
- export declare function migrationAdapters(adapters?: Record<string, unknown>): Record<string, MigrationAdapter>;
8
+ export declare function migrationProviders(providers?: Record<string, unknown>): Record<string, MigrationProvider>;
package/dist/migrate.js CHANGED
@@ -4,29 +4,31 @@ import { confirm as clackConfirm, isCancel } from "@clack/prompts";
4
4
  import { SCHEMA_VERSION, documentVersion, loadYamlMapping, migrateConfig, migrateWorkflow, patchConfigText, patchWorkflowText, workflowsDirectory, ConfigurationError, } from "@nodeint/migent-kernel";
5
5
  import { printError, printNotice } from "./display.js";
6
6
  import { isDirectory, isFile } from "./fs-utils.js";
7
- import { adapterMap } from "./provider.js";
8
- const OUTDATED = "Schema is outdated. Run `migent migrate` to update.";
7
+ import { providerMap } from "./provider.js";
8
+ function versionList(entries) {
9
+ const width = Math.max(...entries.map(([label]) => label.length));
10
+ return entries.map(([label, version]) => ` ${label.padEnd(width)} v${version}`).join("\n");
11
+ }
9
12
  export function requireCurrentSchema(workspace) {
10
13
  const newer = [];
11
- let outdated = false;
14
+ const older = [];
12
15
  for (const [label, filePath] of schemaFiles(workspace)) {
13
16
  const version = storedVersion(filePath);
14
17
  if (version === null || version === SCHEMA_VERSION)
15
18
  continue;
16
- if (version > SCHEMA_VERSION) {
17
- newer.push(`${label} schema_version ${version} is newer than this Migent (${SCHEMA_VERSION}).`);
18
- continue;
19
- }
20
- outdated = true;
19
+ (version > SCHEMA_VERSION ? newer : older).push([label, version]);
20
+ }
21
+ if (older.length > 0) {
22
+ throw new ConfigurationError(`Schema is outdated (this Migent uses v${SCHEMA_VERSION}).\n${versionList(older)}\n\n` +
23
+ "Run `migent migrate` to update.");
24
+ }
25
+ if (newer.length > 0) {
26
+ throw new ConfigurationError(`Schema is newer than this Migent supports (v${SCHEMA_VERSION}).\n${versionList(newer)}`);
21
27
  }
22
- if (outdated)
23
- throw new ConfigurationError(OUTDATED);
24
- if (newer.length > 0)
25
- throw new ConfigurationError(newer.join(" "));
26
28
  }
27
29
  export async function runMigrate(workspace, options = {}) {
28
- const adapters = options.adapters ?? migrationAdapters();
29
- const rewrites = pendingRewrites(workspace, adapters);
30
+ const providers = options.providers ?? migrationProviders();
31
+ const rewrites = pendingRewrites(workspace, providers);
30
32
  if (rewrites.length === 0) {
31
33
  process.stdout.write("Schema is current.\n");
32
34
  return 0;
@@ -43,10 +45,10 @@ export async function runMigrate(workspace, options = {}) {
43
45
  process.stdout.write(`Upgraded schema to version ${SCHEMA_VERSION}.\n`);
44
46
  return 0;
45
47
  }
46
- export function migrationAdapters(adapters = adapterMap()) {
48
+ export function migrationProviders(providers = providerMap()) {
47
49
  const mapped = {};
48
- for (const [name, adapter] of Object.entries(adapters)) {
49
- const candidate = adapter;
50
+ for (const [name, provider] of Object.entries(providers)) {
51
+ const candidate = provider;
50
52
  mapped[name] = {
51
53
  modelMigrations: candidate.modelMigrations ?? {},
52
54
  roleMigrations: candidate.roleMigrations ?? {},
@@ -74,17 +76,17 @@ async function accepted(confirm) {
74
76
  }
75
77
  return true;
76
78
  }
77
- function pendingRewrites(workspace, adapters) {
79
+ function pendingRewrites(workspace, providers) {
78
80
  const configPath = path.join(workspace, ".migent", "config.yaml");
79
81
  if (!isFile(configPath))
80
82
  return [];
81
- const [config, configRewrite] = configRewriteOf(configPath, adapters);
83
+ const [config, configRewrite] = configRewriteOf(configPath, providers);
82
84
  if (config === null)
83
85
  return [];
84
86
  const rewrites = [];
85
87
  if (configRewrite !== null)
86
88
  rewrites.push(configRewrite);
87
- rewrites.push(...workflowRewrites(workspace, config, adapters));
89
+ rewrites.push(...workflowRewrites(workspace, config, providers));
88
90
  return rewrites;
89
91
  }
90
92
  function schemaFiles(workspace) {
@@ -109,7 +111,7 @@ function storedVersion(filePath) {
109
111
  return null;
110
112
  }
111
113
  }
112
- function configRewriteOf(filePath, adapters) {
114
+ function configRewriteOf(filePath, providers) {
113
115
  const original = readFileSync(filePath, "utf8");
114
116
  let parsed;
115
117
  try {
@@ -121,7 +123,7 @@ function configRewriteOf(filePath, adapters) {
121
123
  const updated = structuredClone(parsed);
122
124
  let notes;
123
125
  try {
124
- notes = migrateConfig(updated, adapters);
126
+ notes = migrateConfig(updated, providers);
125
127
  }
126
128
  catch (error) {
127
129
  if (error instanceof ConfigurationError)
@@ -136,7 +138,7 @@ function configRewriteOf(filePath, adapters) {
136
138
  const labeled = notes.map((note) => note.startsWith("schema_version") ? `Migrated .migent/config.yaml ${note}` : note);
137
139
  return [updated, { path: filePath, text: rewritten, notes: labeled }];
138
140
  }
139
- function workflowRewrites(workspace, config, adapters) {
141
+ function workflowRewrites(workspace, config, providers) {
140
142
  const directory = workflowsDirectory(workspace);
141
143
  if (!isDirectory(directory))
142
144
  return [];
@@ -156,7 +158,7 @@ function workflowRewrites(workspace, config, adapters) {
156
158
  const updated = structuredClone(parsed);
157
159
  let changed;
158
160
  try {
159
- changed = migrateWorkflow(updated, config, adapters);
161
+ changed = migrateWorkflow(updated, config, providers);
160
162
  }
161
163
  catch (error) {
162
164
  if (error instanceof ConfigurationError)
package/dist/program.js CHANGED
@@ -11,6 +11,7 @@ import { runInit } from "./commands/init.js";
11
11
  import { runSessionMetrics, runSessions } from "./commands/sessions.js";
12
12
  import { parsePort, runServiceStart, runServiceStatus, runServiceStop } from "./commands/service.js";
13
13
  import { installSkill, updateSkill } from "./commands/skill.js";
14
+ import { runProviderList } from "./commands/provider.js";
14
15
  import { runRegister, runRemove, runWorkspaceList } from "./commands/workspace-registry.js";
15
16
  import { printError } from "./display.js";
16
17
  import { LOOP_EXITS, STAGE_EXITS } from "./exits.js";
@@ -127,6 +128,18 @@ export function createProgram(status) {
127
128
  .option("--json", "Print the checks as one JSON object.")
128
129
  .addHelpText("after", exitText(["Exit 0 when every check passes.", "Exit 1 when a check fails."]))
129
130
  .action((options) => settle(status, () => runDoctor(findWorkspace(invocationCwdPath()), { asJson: options.json === true })));
131
+ const provider = program.command("provider").description("List registered provider providers and their models.");
132
+ provider
133
+ .command("list")
134
+ .description("List each registered provider, whether its CLI is on PATH, and the models it reports.")
135
+ .option("--json", "Print the provider list as one JSON object.")
136
+ .addHelpText("after", exitText([
137
+ "Exit 0 when the command ran and at least one provider returned models.",
138
+ "Exit 1 when no provider returned any models.",
139
+ "A missing optional CLI is not an error.",
140
+ ]))
141
+ .action((options) => settle(status, () => runProviderList({ asJson: options.json === true })));
142
+ showHelpWhenBare(status, provider);
130
143
  program
131
144
  .command("migrate")
132
145
  .description("Upgrade config and workflows to the current schema version.")
@@ -220,15 +233,15 @@ function registerSessionCommand(program, status) {
220
233
  .option("--file <file>", "Read the role prompt from this file.")
221
234
  .option("--stdin", "Read the role prompt from stdin.")
222
235
  .option("--json", "Print the role result as one JSON object.")
223
- .addHelpText("after", `With --workflow:\n${exitText(LOOP_EXITS)}With --workflow and --once:\n${exitText(STAGE_EXITS)}Without --workflow:\n${exitText(ROLE_EXITS)}Examples:\n migent session run --workflow plan --task "<one line>"\n migent session run --session wave-3 --workflow plan\n migent session run --role reviewer --prompt "<prompt>"\n`)
236
+ .addHelpText("after", `With --workflow:\n${exitText(LOOP_EXITS)}With --workflow and --once:\n${exitText(STAGE_EXITS)}Without --workflow:\n${exitText(ROLE_EXITS)}Examples:\n migent session run --workflow plan --task "<one line>"\n migent session run --session <session> --workflow plan\n migent session run --role reviewer --prompt "<prompt>"\n`)
224
237
  .action((options) => settle(status, () => runSessionCommand(options)));
225
238
  session
226
239
  .command("prepare")
227
240
  .description("Create a prepared session and exit. Write task.md, then session run --session <id> --workflow <workflow>.")
228
- .argument("<id>", "Session id: a lowercase slug such as wave-3.")
241
+ .argument("<title>", "Session title shown in the list, such as \"some task title\". The session id is a UUID.")
229
242
  .requiredOption("--workflow <workflow>", "Workflow id, or a path to a workflow file.")
230
243
  .option("--prior <session>", PRIOR_HELP)
231
- .addHelpText("after", `${exitText(["Exit 0 when the session is prepared.", "Exit 2 on a configuration error."])}Examples:\n migent session prepare wave-3 --workflow plan\n`)
244
+ .addHelpText("after", `${exitText(["Exit 0 when the session is prepared.", "Exit 2 on a configuration error."])}Examples:\n migent session prepare "some task title" --workflow plan\n`)
232
245
  .action((id, options) => settle(status, () => prepareWorkflowSession(findWorkspace(invocationCwdPath()), id, String(options.workflow), optionalText(options.prior))));
233
246
  const continueCommand = session
234
247
  .command("continue")
@@ -1,23 +1,25 @@
1
1
  import { ModelCatalog } from "@nodeint/migent-adapters";
2
- import { type AgentAdapter, type ProviderOption } from "@nodeint/migent-kernel";
3
- export interface CatalogAdapter extends AgentAdapter {
2
+ import { type Provider, type ProviderOption } from "@nodeint/migent-kernel";
3
+ export interface CatalogProvider extends Provider {
4
4
  modelCatalogCommand(): string[];
5
+ /** Written to the catalog command's stdin when the CLI reads its request there. */
6
+ modelCatalogInput?(): string;
5
7
  parseModelCatalog(stdout: string): ModelCatalog;
6
8
  optionValuesCommand(name: string, model: string): string[] | null;
7
9
  parseOptionValues(name: string, text: string, model?: string): readonly string[];
8
10
  providerOptions(): readonly ProviderOption[];
9
11
  }
10
12
  export type Which = (command: string) => string | null;
11
- export type RunCommand = (argv: string[]) => string | Promise<string>;
13
+ export type RunCommand = (argv: string[], input?: string) => string | Promise<string>;
12
14
  export declare class ProviderCommandFailed extends Error {
13
15
  }
14
- export declare function adapterMap(): Record<string, AgentAdapter>;
15
- export declare function asCatalog(adapter: AgentAdapter): CatalogAdapter;
16
- export declare function requireAdapter(adapters: Record<string, AgentAdapter>, provider: string): AgentAdapter;
17
- export declare function detected(adapters: Record<string, AgentAdapter>, which: Which): Array<[string, string | null, AgentAdapter]>;
18
- export declare function loadCatalog(adapter: AgentAdapter, which: Which, runCommand: RunCommand): Promise<ModelCatalog>;
16
+ export declare function providerMap(): Record<string, Provider>;
17
+ export declare function asCatalog(provider: Provider): CatalogProvider;
18
+ export declare function requireProvider(providers: Record<string, Provider>, provider: string): Provider;
19
+ export declare function detected(providers: Record<string, Provider>, which: Which): Array<[string, string | null, Provider]>;
20
+ export declare function loadCatalog(provider: Provider, which: Which, runCommand: RunCommand): Promise<ModelCatalog>;
19
21
  export declare function knownModel(model: string, catalog: ModelCatalog): string;
20
22
  export declare function parseOptions(pairs: readonly string[]): Record<string, string>;
21
- export declare function loadOptionValues(adapter: AgentAdapter, name: string, model: string, catalog: ModelCatalog, which: Which, runCommand: RunCommand): Promise<readonly string[] | null>;
22
- export declare function acceptedOptions(adapter: AgentAdapter, model: string, catalog: ModelCatalog, which: Which, runCommand: RunCommand, options: Readonly<Record<string, string>>): Promise<Record<string, string>>;
23
- export declare function runProviderCommand(argv: string[]): string;
23
+ export declare function loadOptionValues(provider: Provider, name: string, model: string, catalog: ModelCatalog, which: Which, runCommand: RunCommand): Promise<readonly string[] | null>;
24
+ export declare function acceptedOptions(provider: Provider, model: string, catalog: ModelCatalog, which: Which, runCommand: RunCommand, options: Readonly<Record<string, string>>): Promise<Record<string, string>>;
25
+ export declare function runProviderCommand(argv: string[], input?: string): string;
package/dist/provider.js CHANGED
@@ -1,27 +1,27 @@
1
- import { ModelCatalog, defaultAdapters } from "@nodeint/migent-adapters";
1
+ import { ModelCatalog, defaultProviders } from "@nodeint/migent-adapters";
2
2
  import { spawnProviderSync } from "@nodeint/migent-kernel";
3
3
  export class ProviderCommandFailed extends Error {
4
4
  }
5
- export function adapterMap() {
6
- return defaultAdapters();
5
+ export function providerMap() {
6
+ return defaultProviders();
7
7
  }
8
- export function asCatalog(adapter) {
9
- const candidate = adapter;
8
+ export function asCatalog(provider) {
9
+ const candidate = provider;
10
10
  if (typeof candidate.modelCatalogCommand !== "function" || typeof candidate.parseModelCatalog !== "function") {
11
- throw new ProviderCommandFailed(`${adapter.command} does not list models.`);
11
+ throw new ProviderCommandFailed(`${provider.command} does not list models.`);
12
12
  }
13
13
  return candidate;
14
14
  }
15
- export function requireAdapter(adapters, provider) {
16
- const adapter = adapters[provider];
17
- if (adapter === undefined) {
18
- const supported = Object.keys(adapters).sort().join(", ");
15
+ export function requireProvider(providers, provider) {
16
+ const impl = providers[provider];
17
+ if (impl === undefined) {
18
+ const supported = Object.keys(providers).sort().join(", ");
19
19
  throw new ProviderCommandFailed(`Unknown provider: ${provider}. Supported: ${supported}.`);
20
20
  }
21
- return adapter;
21
+ return impl;
22
22
  }
23
- export function detected(adapters, which) {
24
- const rows = Object.entries(adapters).map(([name, adapter]) => [name, which(adapter.command), adapter]);
23
+ export function detected(providers, which) {
24
+ const rows = Object.entries(providers).filter(([, provider]) => provider.requiresClient !== true).map(([name, provider]) => [name, which(provider.command), provider]);
25
25
  rows.sort((left, right) => {
26
26
  const leftMissing = left[1] === null ? 1 : 0;
27
27
  const rightMissing = right[1] === null ? 1 : 0;
@@ -35,24 +35,24 @@ export function detected(adapters, which) {
35
35
  });
36
36
  return rows;
37
37
  }
38
- export async function loadCatalog(adapter, which, runCommand) {
39
- if (which(adapter.command) === null) {
40
- throw new ProviderCommandFailed(`${adapter.command} is not on PATH, so its models could not be listed.`);
38
+ export async function loadCatalog(provider, which, runCommand) {
39
+ if (which(provider.command) === null) {
40
+ throw new ProviderCommandFailed(`${provider.command} is not on PATH, so its models could not be listed.`);
41
41
  }
42
- const catalogAdapter = asCatalog(adapter);
42
+ const catalogProvider = asCatalog(provider);
43
43
  let stdout;
44
44
  try {
45
- stdout = await runCommand(catalogAdapter.modelCatalogCommand());
45
+ stdout = await runCommand(catalogProvider.modelCatalogCommand(), catalogProvider.modelCatalogInput?.());
46
46
  }
47
47
  catch (error) {
48
48
  if (error instanceof ProviderCommandFailed)
49
49
  throw error;
50
50
  const reason = error instanceof Error ? error.message : String(error);
51
- throw new ProviderCommandFailed(`Could not list ${adapter.command} models: ${reason}`);
51
+ throw new ProviderCommandFailed(`Could not list ${provider.command} models: ${reason}`);
52
52
  }
53
- const catalog = catalogAdapter.parseModelCatalog(stdout);
53
+ const catalog = catalogProvider.parseModelCatalog(stdout);
54
54
  if (catalog.ids.length === 0)
55
- throw new ProviderCommandFailed(`${adapter.command} returned no models.`);
55
+ throw new ProviderCommandFailed(`${provider.command} returned no models.`);
56
56
  return catalog;
57
57
  }
58
58
  export function knownModel(model, catalog) {
@@ -75,16 +75,16 @@ export function parseOptions(pairs) {
75
75
  }
76
76
  return parsed;
77
77
  }
78
- export async function loadOptionValues(adapter, name, model, catalog, which, runCommand) {
78
+ export async function loadOptionValues(provider, name, model, catalog, which, runCommand) {
79
79
  const found = catalog.valuesFor(model, name);
80
80
  if (found !== null)
81
81
  return found;
82
- const catalogAdapter = asCatalog(adapter);
83
- const argv = catalogAdapter.optionValuesCommand(name, model);
82
+ const catalogProvider = asCatalog(provider);
83
+ const argv = catalogProvider.optionValuesCommand(name, model);
84
84
  if (argv === null)
85
85
  return null;
86
- if (which(adapter.command) === null) {
87
- throw new ProviderCommandFailed(`${adapter.command} is not on PATH, so its ${name} values could not be listed.`);
86
+ if (which(provider.command) === null) {
87
+ throw new ProviderCommandFailed(`${provider.command} is not on PATH, so its ${name} values could not be listed.`);
88
88
  }
89
89
  let text;
90
90
  try {
@@ -95,18 +95,18 @@ export async function loadOptionValues(adapter, name, model, catalog, which, run
95
95
  text = error.message;
96
96
  else {
97
97
  const reason = error instanceof Error ? error.message : String(error);
98
- throw new ProviderCommandFailed(`Could not list ${adapter.command} ${name} values for ${model}: ${reason}`);
98
+ throw new ProviderCommandFailed(`Could not list ${provider.command} ${name} values for ${model}: ${reason}`);
99
99
  }
100
100
  }
101
- return catalogAdapter.parseOptionValues(name, text, model);
101
+ return catalogProvider.parseOptionValues(name, text, model);
102
102
  }
103
- export async function acceptedOptions(adapter, model, catalog, which, runCommand, options) {
103
+ export async function acceptedOptions(provider, model, catalog, which, runCommand, options) {
104
104
  if (Object.keys(options).length === 0)
105
105
  return {};
106
- adapter.validateOptions(options);
106
+ provider.validateOptions(options);
107
107
  const stored = {};
108
108
  for (const [name, value] of Object.entries(options)) {
109
- const values = await loadOptionValues(adapter, name, model, catalog, which, runCommand);
109
+ const values = await loadOptionValues(provider, name, model, catalog, which, runCommand);
110
110
  if (values === null) {
111
111
  stored[name] = value;
112
112
  continue;
@@ -120,11 +120,16 @@ export async function acceptedOptions(adapter, model, catalog, which, runCommand
120
120
  }
121
121
  return stored;
122
122
  }
123
- export function runProviderCommand(argv) {
123
+ export function runProviderCommand(argv, input) {
124
124
  const [command, ...args] = argv;
125
125
  if (command === undefined)
126
126
  throw new ProviderCommandFailed("Provider command is empty.");
127
- const completed = spawnProviderSync(command, args, { encoding: "utf8", timeout: 60_000, windowsHide: true });
127
+ const completed = spawnProviderSync(command, args, {
128
+ encoding: "utf8",
129
+ timeout: 60_000,
130
+ windowsHide: true,
131
+ ...(input === undefined ? {} : { input }),
132
+ });
128
133
  if (completed.error) {
129
134
  throw new Error(completed.error.message);
130
135
  }
@@ -7,9 +7,11 @@ export declare const SESSION_COLUMN: {
7
7
  readonly decision: 5;
8
8
  readonly outputs: 6;
9
9
  readonly task: 7;
10
+ readonly title: 8;
10
11
  };
11
12
  export interface SessionFields {
12
13
  session: string;
14
+ title: string;
13
15
  workflow: string;
14
16
  status: string;
15
17
  execution: string;
@@ -7,10 +7,12 @@ export const SESSION_COLUMN = {
7
7
  decision: 5,
8
8
  outputs: 6,
9
9
  task: 7,
10
+ title: 8,
10
11
  };
11
12
  export function sessionFields(row) {
12
13
  return {
13
14
  session: row[SESSION_COLUMN.session] ?? "",
15
+ title: row[SESSION_COLUMN.title] ?? "",
14
16
  workflow: row[SESSION_COLUMN.workflow] ?? "",
15
17
  status: row[SESSION_COLUMN.status] ?? "",
16
18
  execution: row[SESSION_COLUMN.execution] ?? "",
@@ -21,6 +21,9 @@ This skill is already in the prompt. Do not open its file again.
21
21
  and `schema-workflow.yaml` in the same folder. A key absent from them is
22
22
  ignored. Then run `migent doctor --json`; if it reports an outdated schema,
23
23
  run `migent migrate --yes`.
24
+ - `.migent/config.local.yaml`, when present, overrides `config.yaml` on this
25
+ machine. It is not committed. Read both, and edit shared settings in
26
+ `config.yaml`, not the local file.
24
27
  - You have no terminal for prompts. Pass `--no-input` (or set
25
28
  `MIGENT_NO_INPUT=1`) to `init`, `config model`, and `config role`, and give
26
29
  every value as a flag.
@@ -41,18 +44,18 @@ One-line task. The command runs until the session stops:
41
44
  migent session run --workflow <workflow> --task "<task>"
42
45
  ```
43
46
 
44
- Longer task. `<some-id>` is a lowercase slug such as `wave-3`:
47
+ Longer task. `<title>` is a display label such as `some task title`. The command prints a UUID `session_id`:
45
48
 
46
49
  ```
47
- migent session prepare <some-id> --workflow <workflow>
50
+ migent session prepare "<title>" --workflow <workflow>
48
51
  ```
49
52
 
50
- Write the task, unchanged, to `.migent/sessions/<some-id>/task.md` (plus any
51
- files next to it). Do not edit `task.md` once the first stage has started.
53
+ Write the task, unchanged, to the printed `task_path` (plus any files next to it).
54
+ Do not edit `task.md` once the first stage has started.
52
55
  Then:
53
56
 
54
57
  ```
55
- migent session run --session <some-id> --workflow <workflow>
58
+ migent session run --session <session-id> --workflow <workflow>
56
59
  ```
57
60
 
58
61
  Dispatch only one stage: add `--once`.
@@ -122,18 +125,22 @@ migent session list --latest --json
122
125
  ```
123
126
 
124
127
  `show` gives status, latest execution, decision, and outputs. `list --latest`
125
- is the newest session, or `null`. Report the session ID, status, stop reason,
128
+ is the newest session, or `null`. Both include `title` when the session has one.
129
+ Report the session ID, title, status, stop reason,
126
130
  decisions, and the outputs that matter. Output paths are relative to
127
131
  `.migent/sessions/<session-id>/`; use the paths Migent reports.
128
132
 
129
133
  ## Set up a project
130
134
 
131
135
  ```
136
+ migent provider list --json
132
137
  migent init --no-input --provider <provider> --model <model-id> [--preset implement|plan] [--skill]
133
138
  migent doctor --json
134
139
  ```
135
140
 
136
141
  `--provider` and `--model` go together. The default preset is `implement`.
142
+ `provider list --json` shows which provider CLIs are on PATH and what model
143
+ ids each supports. Use it to pick a `<provider>` and `<model-id>` for `init`.
137
144
  `--skill` installs this skill; `migent skill update` refreshes it.
138
145
 
139
146
  ## Change models
@@ -2,6 +2,19 @@
2
2
 
3
3
  Write commands take `--no-input --json`. The JSON includes the doctor result.
4
4
 
5
+ Run `migent provider list --json` to see which provider CLIs are available
6
+ and what model ids each supports. Use that output to pick a provider and
7
+ model id before using `config model` or `init`.
8
+
9
+ ## Per-machine override
10
+
11
+ `.migent/config.local.yaml` is an optional, uncommitted file with the same keys
12
+ as `config.yaml`. Mappings merge key by key; scalars and lists, such as
13
+ `instructions`, replace the value from `config.yaml`. Its `schema_version` is
14
+ ignored. Runs, `doctor`, and `config show` read the merged result.
15
+ `config model` and `config role` edit `config.yaml` only, never the local file.
16
+ `doctor` warns if git tracks the local file.
17
+
5
18
  ## Run one role
6
19
 
7
20
  Without `--workflow`, `session run` runs a single role:
@@ -1,10 +1,15 @@
1
1
  # A missing schema_version is 0. Commands refuse the file while it is older than this Migent.
2
2
  schema_version: 2
3
+ # .migent/config.local.yaml is an optional per-machine override with the same keys. It is not committed.
4
+ # Mappings merge key by key; scalars and lists replace the value from config.yaml. Its schema_version is ignored.
5
+ # config model and config role edit config.yaml only.
3
6
 
4
7
  # models.<name>.model is the provider model id.
8
+ # models.<name>.client is optional: the CLI client id (agy, claude-code, codex, grok, opencode); required for provider byteplus
9
+ # that drives this model. Omit it to use the provider's own client.
5
10
  # roles.<name>.default_model and a stage model are this name.
6
11
  # Options merge in order: model, then role, then stage.
7
- # Only keys the adapter marks overridable are stored on the provider session.
12
+ # Only keys the provider marks overridable are stored on the provider session.
8
13
  models:
9
14
  planner:
10
15
  provider: grok
@@ -19,9 +24,16 @@ models:
19
24
  # disallowed_tools, max_budget_usd, fallback_model, and append_system_prompt.
20
25
  # codex accepts model_reasoning_effort (overridable). Any other key of letters, digits,
21
26
  # dots, underscores, and hyphens is passed through as codex exec -c.
27
+ # opencode accepts agent and thinking (true sends --thinking, false sends nothing). Neither is overridable.
22
28
  options:
23
29
  reasoning-effort: high
24
30
 
31
+ coder:
32
+ provider: byteplus
33
+ # byteplus has no CLI of its own, so client is required and names the CLI that runs it.
34
+ client: opencode
35
+ model: coding-plan/deepseek-v4-pro
36
+
25
37
  roles:
26
38
  planner:
27
39
  default_model: planner
@@ -27,7 +27,7 @@ stages:
27
27
  # model is a name under models in config.yaml, not a provider model id.
28
28
  model: planner
29
29
  # Options merge in order: model, then role, then stage.
30
- # Only keys the adapter marks overridable are stored on the provider session.
30
+ # Only keys the provider marks overridable are stored on the provider session.
31
31
  options:
32
32
  reasoning-effort: high
33
33
  depends_on:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodeint/migent",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Compose AI coding agents into repeatable engineering workflows.",
5
5
  "keywords": [
6
6
  "ai",
@@ -43,9 +43,9 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@clack/prompts": "^1.8.1",
46
- "@nodeint/migent-adapters": "0.1.6",
47
- "@nodeint/migent-kernel": "0.1.6",
48
- "@nodeint/migent-registry": "0.1.6",
46
+ "@nodeint/migent-adapters": "0.1.8",
47
+ "@nodeint/migent-kernel": "0.1.8",
48
+ "@nodeint/migent-registry": "0.1.8",
49
49
  "commander": "^14.0.2",
50
50
  "picocolors": "^1.1.1"
51
51
  },