@nodeint/migent 0.1.6 → 0.1.7

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.
@@ -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;
package/dist/display.js CHANGED
@@ -50,7 +50,8 @@ export function printSessions(rows) {
50
50
  }
51
51
  for (const row of rows) {
52
52
  const fields = sessionFields(row);
53
- process.stdout.write(`${fields.session} ${fields.workflow} ${fields.status} ${fields.execution} ${fields.decision} ${fields.outputs} ${fields.task}\n`);
53
+ const identity = fields.title !== "" && fields.title !== fields.session ? `${fields.title} ${fields.session}` : fields.session;
54
+ process.stdout.write(`${identity} ${fields.workflow} ${fields.status} ${fields.execution} ${fields.decision} ${fields.outputs} ${fields.task}\n`);
54
55
  }
55
56
  }
56
57
  export function printAgent(result, role, provider, model) {
@@ -73,6 +74,10 @@ export function printWatchSnapshot(snapshot) {
73
74
  ["session_id", String(snapshot.session_id ?? "--")],
74
75
  ["status", String(snapshot.status ?? "unknown")],
75
76
  ];
77
+ const title = snapshot.title;
78
+ if (typeof title === "string" && title !== "" && title !== lines[0]?.[1]) {
79
+ lines.splice(1, 0, ["title", title]);
80
+ }
76
81
  if (typeof snapshot.workflow_id === "string" && snapshot.workflow_id) {
77
82
  lines.push(["workflow", snapshot.workflow_id]);
78
83
  }
@@ -133,6 +138,40 @@ export function formatWatchTimestamp(timestamp) {
133
138
  return timestamp.trim();
134
139
  return parsed.toISOString().replace("T", " ").replace(/\.\d+Z$/, "+00:00").replace("Z", "+00:00");
135
140
  }
141
+ export function printProviders(rows) {
142
+ const tty = Boolean(process.stdout.isTTY);
143
+ for (const row of rows) {
144
+ const ok = row.problems.length === 0 && row.models.length > 0;
145
+ const status = ok ? (tty ? pc.green("ok") : "ok") : tty ? pc.red("failed") : "failed";
146
+ process.stdout.write(`${row.name.padEnd(12)}${status}\n`);
147
+ if (row.path !== null) {
148
+ const line = ` path: ${row.path}`;
149
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
150
+ }
151
+ else {
152
+ const line = ` path: not found`;
153
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
154
+ }
155
+ if (row.models.length > 0) {
156
+ process.stdout.write(` models:\n`);
157
+ for (const model of row.models) {
158
+ const marked = model === row.default_model ? `${model} (default)` : model;
159
+ const line = ` ${marked}`;
160
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
161
+ }
162
+ }
163
+ if (row.options.length > 0) {
164
+ process.stdout.write(` options:\n`);
165
+ for (const opt of row.options) {
166
+ const line = ` ${opt.name}`;
167
+ process.stdout.write(`${tty ? pc.dim(line) : line}\n`);
168
+ }
169
+ }
170
+ for (const problem of row.problems) {
171
+ process.stdout.write(` ${problem}\n`);
172
+ }
173
+ }
174
+ }
136
175
  function isRecord(value) {
137
176
  return typeof value === "object" && value !== null && !Array.isArray(value);
138
177
  }
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] ?? "",
@@ -41,18 +41,18 @@ One-line task. The command runs until the session stops:
41
41
  migent session run --workflow <workflow> --task "<task>"
42
42
  ```
43
43
 
44
- Longer task. `<some-id>` is a lowercase slug such as `wave-3`:
44
+ Longer task. `<title>` is a display label such as `some task title`. The command prints a UUID `session_id`:
45
45
 
46
46
  ```
47
- migent session prepare <some-id> --workflow <workflow>
47
+ migent session prepare "<title>" --workflow <workflow>
48
48
  ```
49
49
 
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.
50
+ Write the task, unchanged, to the printed `task_path` (plus any files next to it).
51
+ Do not edit `task.md` once the first stage has started.
52
52
  Then:
53
53
 
54
54
  ```
55
- migent session run --session <some-id> --workflow <workflow>
55
+ migent session run --session <session-id> --workflow <workflow>
56
56
  ```
57
57
 
58
58
  Dispatch only one stage: add `--once`.
@@ -122,18 +122,22 @@ migent session list --latest --json
122
122
  ```
123
123
 
124
124
  `show` gives status, latest execution, decision, and outputs. `list --latest`
125
- is the newest session, or `null`. Report the session ID, status, stop reason,
125
+ is the newest session, or `null`. Both include `title` when the session has one.
126
+ Report the session ID, title, status, stop reason,
126
127
  decisions, and the outputs that matter. Output paths are relative to
127
128
  `.migent/sessions/<session-id>/`; use the paths Migent reports.
128
129
 
129
130
  ## Set up a project
130
131
 
131
132
  ```
133
+ migent provider list --json
132
134
  migent init --no-input --provider <provider> --model <model-id> [--preset implement|plan] [--skill]
133
135
  migent doctor --json
134
136
  ```
135
137
 
136
138
  `--provider` and `--model` go together. The default preset is `implement`.
139
+ `provider list --json` shows which provider CLIs are on PATH and what model
140
+ ids each supports. Use it to pick a `<provider>` and `<model-id>` for `init`.
137
141
  `--skill` installs this skill; `migent skill update` refreshes it.
138
142
 
139
143
  ## Change models
@@ -2,6 +2,10 @@
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
+
5
9
  ## Run one role
6
10
 
7
11
  Without `--workflow`, `session run` runs a single role:
@@ -2,9 +2,11 @@
2
2
  schema_version: 2
3
3
 
4
4
  # models.<name>.model is the provider model id.
5
+ # models.<name>.client is optional: the CLI client id (agy, claude-code, codex, grok, opencode); required for provider byteplus
6
+ # that drives this model. Omit it to use the provider's own client.
5
7
  # roles.<name>.default_model and a stage model are this name.
6
8
  # Options merge in order: model, then role, then stage.
7
- # Only keys the adapter marks overridable are stored on the provider session.
9
+ # Only keys the provider marks overridable are stored on the provider session.
8
10
  models:
9
11
  planner:
10
12
  provider: grok
@@ -19,9 +21,16 @@ models:
19
21
  # disallowed_tools, max_budget_usd, fallback_model, and append_system_prompt.
20
22
  # codex accepts model_reasoning_effort (overridable). Any other key of letters, digits,
21
23
  # dots, underscores, and hyphens is passed through as codex exec -c.
24
+ # opencode accepts agent and thinking (true sends --thinking, false sends nothing). Neither is overridable.
22
25
  options:
23
26
  reasoning-effort: high
24
27
 
28
+ coder:
29
+ provider: byteplus
30
+ # byteplus has no CLI of its own, so client is required and names the CLI that runs it.
31
+ client: opencode
32
+ model: coding-plan/deepseek-v4-pro
33
+
25
34
  roles:
26
35
  planner:
27
36
  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.7",
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.7",
47
+ "@nodeint/migent-kernel": "0.1.7",
48
+ "@nodeint/migent-registry": "0.1.7",
49
49
  "commander": "^14.0.2",
50
50
  "picocolors": "^1.1.1"
51
51
  },