@nodeint/migent 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +192 -2
- package/dist/commands/agent.d.ts +14 -0
- package/dist/commands/agent.js +97 -0
- package/dist/commands/config.d.ts +35 -0
- package/dist/commands/config.js +613 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +26 -0
- package/dist/commands/execute.d.ts +12 -0
- package/dist/commands/execute.js +39 -0
- package/dist/commands/init.d.ts +26 -0
- package/dist/commands/init.js +311 -0
- package/dist/commands/service.d.ts +11 -0
- package/dist/commands/service.js +60 -0
- package/dist/commands/sessions.d.ts +3 -0
- package/dist/commands/sessions.js +20 -0
- package/dist/commands/skill.d.ts +4 -0
- package/dist/commands/skill.js +98 -0
- package/dist/commands/watch.d.ts +7 -0
- package/dist/commands/watch.js +169 -0
- package/dist/commands/workspace-registry.d.ts +8 -0
- package/dist/commands/workspace-registry.js +106 -0
- package/dist/display.d.ts +12 -0
- package/dist/display.js +123 -0
- package/dist/hooks.d.ts +6 -0
- package/dist/hooks.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +57 -0
- package/dist/migrate.d.ts +8 -0
- package/dist/migrate.js +191 -0
- package/dist/program.d.ts +6 -0
- package/dist/program.js +414 -0
- package/dist/provider.d.ts +23 -0
- package/dist/provider.js +136 -0
- package/dist/templates/migent/SKILL.md +62 -0
- package/dist/workspace.d.ts +8 -0
- package/dist/workspace.js +50 -0
- package/package.json +59 -3
package/dist/provider.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { ModelCatalog, defaultAdapters } from "@nodeint/migent-adapters";
|
|
3
|
+
export class ProviderCommandFailed extends Error {
|
|
4
|
+
}
|
|
5
|
+
export function adapterMap() {
|
|
6
|
+
return defaultAdapters();
|
|
7
|
+
}
|
|
8
|
+
export function asCatalog(adapter) {
|
|
9
|
+
const candidate = adapter;
|
|
10
|
+
if (typeof candidate.modelCatalogCommand !== "function" || typeof candidate.parseModelCatalog !== "function") {
|
|
11
|
+
throw new ProviderCommandFailed(`${adapter.command} does not list models.`);
|
|
12
|
+
}
|
|
13
|
+
return candidate;
|
|
14
|
+
}
|
|
15
|
+
export function requireAdapter(adapters, provider) {
|
|
16
|
+
const adapter = adapters[provider];
|
|
17
|
+
if (adapter === undefined) {
|
|
18
|
+
const supported = Object.keys(adapters).sort().join(", ");
|
|
19
|
+
throw new ProviderCommandFailed(`Unknown provider: ${provider}. Supported: ${supported}.`);
|
|
20
|
+
}
|
|
21
|
+
return adapter;
|
|
22
|
+
}
|
|
23
|
+
export function detected(adapters, which) {
|
|
24
|
+
const rows = Object.entries(adapters).map(([name, adapter]) => [name, which(adapter.command), adapter]);
|
|
25
|
+
rows.sort((left, right) => {
|
|
26
|
+
const leftMissing = left[1] === null ? 1 : 0;
|
|
27
|
+
const rightMissing = right[1] === null ? 1 : 0;
|
|
28
|
+
if (leftMissing !== rightMissing)
|
|
29
|
+
return leftMissing - rightMissing;
|
|
30
|
+
if (left[0] < right[0])
|
|
31
|
+
return -1;
|
|
32
|
+
if (left[0] > right[0])
|
|
33
|
+
return 1;
|
|
34
|
+
return 0;
|
|
35
|
+
});
|
|
36
|
+
return rows;
|
|
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.`);
|
|
41
|
+
}
|
|
42
|
+
const catalogAdapter = asCatalog(adapter);
|
|
43
|
+
let stdout;
|
|
44
|
+
try {
|
|
45
|
+
stdout = await runCommand(catalogAdapter.modelCatalogCommand());
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error instanceof ProviderCommandFailed)
|
|
49
|
+
throw error;
|
|
50
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
51
|
+
throw new ProviderCommandFailed(`Could not list ${adapter.command} models: ${reason}`);
|
|
52
|
+
}
|
|
53
|
+
const catalog = catalogAdapter.parseModelCatalog(stdout);
|
|
54
|
+
if (catalog.ids.length === 0)
|
|
55
|
+
throw new ProviderCommandFailed(`${adapter.command} returned no models.`);
|
|
56
|
+
return catalog;
|
|
57
|
+
}
|
|
58
|
+
export function knownModel(model, catalog) {
|
|
59
|
+
const value = model.trim();
|
|
60
|
+
if (!catalog.ids.includes(value))
|
|
61
|
+
throw new ProviderCommandFailed(`Unknown model: ${value}.`);
|
|
62
|
+
return value;
|
|
63
|
+
}
|
|
64
|
+
export function parseOptions(pairs) {
|
|
65
|
+
const parsed = {};
|
|
66
|
+
for (const pair of pairs) {
|
|
67
|
+
if (!pair.includes("="))
|
|
68
|
+
throw new ProviderCommandFailed(`Option must be KEY=VALUE: ${pair}.`);
|
|
69
|
+
const index = pair.indexOf("=");
|
|
70
|
+
const name = pair.slice(0, index).trim();
|
|
71
|
+
const value = pair.slice(index + 1).trim();
|
|
72
|
+
if (name === "" || value === "")
|
|
73
|
+
throw new ProviderCommandFailed(`Option must be KEY=VALUE: ${pair}.`);
|
|
74
|
+
parsed[name] = value;
|
|
75
|
+
}
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
export async function loadOptionValues(adapter, name, model, catalog, which, runCommand) {
|
|
79
|
+
const found = catalog.valuesFor(model, name);
|
|
80
|
+
if (found !== null)
|
|
81
|
+
return found;
|
|
82
|
+
const catalogAdapter = asCatalog(adapter);
|
|
83
|
+
const argv = catalogAdapter.optionValuesCommand(name, model);
|
|
84
|
+
if (argv === null)
|
|
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.`);
|
|
88
|
+
}
|
|
89
|
+
let text;
|
|
90
|
+
try {
|
|
91
|
+
text = await runCommand(argv);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (error instanceof ProviderCommandFailed)
|
|
95
|
+
text = error.message;
|
|
96
|
+
else {
|
|
97
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
98
|
+
throw new ProviderCommandFailed(`Could not list ${adapter.command} ${name} values for ${model}: ${reason}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return catalogAdapter.parseOptionValues(name, text, model);
|
|
102
|
+
}
|
|
103
|
+
export async function acceptedOptions(adapter, model, catalog, which, runCommand, options) {
|
|
104
|
+
if (Object.keys(options).length === 0)
|
|
105
|
+
return {};
|
|
106
|
+
adapter.validateOptions(options);
|
|
107
|
+
const stored = {};
|
|
108
|
+
for (const [name, value] of Object.entries(options)) {
|
|
109
|
+
const values = await loadOptionValues(adapter, name, model, catalog, which, runCommand);
|
|
110
|
+
if (values === null) {
|
|
111
|
+
stored[name] = value;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!values.includes(value)) {
|
|
115
|
+
if (values.length === 0)
|
|
116
|
+
throw new ProviderCommandFailed(`${model} does not accept ${name}.`);
|
|
117
|
+
throw new ProviderCommandFailed(`${name} must be one of: ${values.join(", ")}.`);
|
|
118
|
+
}
|
|
119
|
+
stored[name] = value;
|
|
120
|
+
}
|
|
121
|
+
return stored;
|
|
122
|
+
}
|
|
123
|
+
export function runProviderCommand(argv) {
|
|
124
|
+
const [command, ...args] = argv;
|
|
125
|
+
if (command === undefined)
|
|
126
|
+
throw new ProviderCommandFailed("Provider command is empty.");
|
|
127
|
+
const completed = spawnSync(command, args, { encoding: "utf8", timeout: 60_000 });
|
|
128
|
+
if (completed.error) {
|
|
129
|
+
throw new Error(completed.error.message);
|
|
130
|
+
}
|
|
131
|
+
if (completed.status !== 0) {
|
|
132
|
+
const detail = (completed.stderr || completed.stdout || "").trim();
|
|
133
|
+
throw new ProviderCommandFailed(detail || `${command} exited ${completed.status}.`);
|
|
134
|
+
}
|
|
135
|
+
return completed.stdout ?? "";
|
|
136
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: migent
|
|
3
|
+
description: Set up Migent in a project, or configure, run, inspect, and resume its workflows. Use when the user asks about Migent configuration, workflows, sessions, stages, or published outputs.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Migent
|
|
7
|
+
|
|
8
|
+
Use Migent as the control plane for the project's multi-stage agent workflows.
|
|
9
|
+
|
|
10
|
+
## Working rules
|
|
11
|
+
|
|
12
|
+
- For an existing project, work from the root containing
|
|
13
|
+
`.migent/config.yaml`. To set up a new project, run `migent init` from
|
|
14
|
+
its root and follow the CLI's provider and model prompts or flags.
|
|
15
|
+
- Read `.migent/config.yaml` and the relevant file under
|
|
16
|
+
`.migent/workflows/` before changing or running a workflow.
|
|
17
|
+
- Run `migent doctor` after editing configuration or workflows.
|
|
18
|
+
`migent config model` and `migent config role` run it after a write.
|
|
19
|
+
- Treat `.migent/sessions/` as generated, resumable state. Do not edit its
|
|
20
|
+
files directly.
|
|
21
|
+
- Preserve the user's task wording when passing `--task`.
|
|
22
|
+
- Before continuing a stopped session, inspect the command's `stop_reason` and
|
|
23
|
+
latest execution. Fix the cause before retrying.
|
|
24
|
+
|
|
25
|
+
## Choose the command
|
|
26
|
+
|
|
27
|
+
- Start a workflow: `migent start <workflow> --task "<task>"`. `<workflow>`
|
|
28
|
+
is the workflow id, or a path to a workflow file. An omitted `id` uses the
|
|
29
|
+
file name. If its
|
|
30
|
+
`requires` block names a prerequisite workflow and decision, find a matching
|
|
31
|
+
completed session and add `--prior <session-id>`.
|
|
32
|
+
- Resume an active session: `migent continue <session-id>`.
|
|
33
|
+
- Advance exactly one eligible stage: `migent stage <session-id>`.
|
|
34
|
+
- List recorded sessions: `migent sessions` or `migent sessions --json`.
|
|
35
|
+
- Inspect a snapshot: `migent watch <session-id> --once --json`.
|
|
36
|
+
- Follow new events: `migent watch <session-id>`.
|
|
37
|
+
- Run a configured role outside a workflow only when the user asks for ad hoc
|
|
38
|
+
role execution: `migent agent start --role <role> --prompt "<prompt>"`.
|
|
39
|
+
- Read `.migent/config.yaml`: `migent config show`.
|
|
40
|
+
- Change a model entry: `migent config model <name> --provider <provider> --model <model-id>`.
|
|
41
|
+
This updates every role and stage that uses the name.
|
|
42
|
+
- Point one role at a model without changing a shared entry:
|
|
43
|
+
`migent config role <role> --provider <provider> --model <model-id>`.
|
|
44
|
+
`migent config role <role> --model-name <name>` points it at a name that
|
|
45
|
+
already exists. Add `--json` to read the result.
|
|
46
|
+
|
|
47
|
+
Workflow commands emit progress on stderr and one result object on stdout.
|
|
48
|
+
Report the session ID, session status, stop reason, decisions, and published
|
|
49
|
+
outputs that matter to the user. A stopped active session is resumable; do not
|
|
50
|
+
describe it as completed.
|
|
51
|
+
|
|
52
|
+
`retry_exhausted` means the stage has used its total failed-attempt allowance;
|
|
53
|
+
`--attempts` on `continue` sets a new total, not extra retries.
|
|
54
|
+
`revisions_exhausted` leaves the session active. `continue` resets revision
|
|
55
|
+
counts for the existing session, allowing another batch up to `max_revisions`.
|
|
56
|
+
`dispatch_limit` applies to the current command; continue the same session if
|
|
57
|
+
further stages are appropriate. Do not start a replacement session to reset
|
|
58
|
+
these limits.
|
|
59
|
+
|
|
60
|
+
Published output paths are relative to
|
|
61
|
+
`.migent/sessions/<session-id>/`. Prefer the paths reported by Migent over
|
|
62
|
+
guessing artifact locations.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function setInvocationCwd(cwd?: string): void;
|
|
2
|
+
export declare function invocationCwdPath(): string;
|
|
3
|
+
export declare function findWorkspace(start: string): string;
|
|
4
|
+
export declare function resolvePrompt(args: {
|
|
5
|
+
prompt?: string | null;
|
|
6
|
+
promptFile?: string | null;
|
|
7
|
+
promptStdin: boolean;
|
|
8
|
+
}): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ConfigurationError } from "@nodeint/migent-kernel";
|
|
4
|
+
let invocationCwd = null;
|
|
5
|
+
export function setInvocationCwd(cwd) {
|
|
6
|
+
invocationCwd = cwd === undefined ? null : path.resolve(cwd);
|
|
7
|
+
}
|
|
8
|
+
export function invocationCwdPath() {
|
|
9
|
+
return invocationCwd ?? process.cwd();
|
|
10
|
+
}
|
|
11
|
+
export function findWorkspace(start) {
|
|
12
|
+
let current = path.resolve(start);
|
|
13
|
+
for (;;) {
|
|
14
|
+
if (isFile(path.join(current, ".migent", "config.yaml")))
|
|
15
|
+
return current;
|
|
16
|
+
const parent = path.dirname(current);
|
|
17
|
+
if (parent === current)
|
|
18
|
+
break;
|
|
19
|
+
current = parent;
|
|
20
|
+
}
|
|
21
|
+
throw new ConfigurationError("Could not find .migent/config.yaml in this directory or its parents.");
|
|
22
|
+
}
|
|
23
|
+
export function resolvePrompt(args) {
|
|
24
|
+
if (args.promptStdin) {
|
|
25
|
+
const text = readFileSync(0, "utf8");
|
|
26
|
+
if (text === "")
|
|
27
|
+
throw new Error("Prompt from stdin is empty.");
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
if (args.promptFile == null) {
|
|
31
|
+
if (args.prompt == null)
|
|
32
|
+
throw new Error("Pass one of --prompt, --file, or --stdin.");
|
|
33
|
+
return args.prompt;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return readFileSync(args.promptFile, "utf8");
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
40
|
+
throw new Error(`Unable to read prompt file: ${args.promptFile} (${reason})`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function isFile(filePath) {
|
|
44
|
+
try {
|
|
45
|
+
return statSync(filePath).isFile();
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nodeint/migent",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Compose AI coding agents into repeatable engineering workflows.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"agents",
|
|
8
|
+
"workflow",
|
|
9
|
+
"cli",
|
|
10
|
+
"claude",
|
|
11
|
+
"codex",
|
|
12
|
+
"grok",
|
|
13
|
+
"migent"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"homepage": "https://github.com/nodeint/migent/tree/main/packages/cli#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/nodeint/migent/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/nodeint/migent.git",
|
|
23
|
+
"directory": "packages/cli"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"bin": {
|
|
27
|
+
"migent": "dist/index.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22"
|
|
40
|
+
},
|
|
41
|
+
"os": [
|
|
42
|
+
"darwin",
|
|
43
|
+
"linux"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@clack/prompts": "^1.8.1",
|
|
50
|
+
"@nodeint/migent-adapters": "0.1.0",
|
|
51
|
+
"@nodeint/migent-kernel": "0.1.0",
|
|
52
|
+
"@nodeint/migent-registry": "0.1.0",
|
|
53
|
+
"commander": "^14.0.2",
|
|
54
|
+
"picocolors": "^1.1.1"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^26.6.4"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsc -b && rm -rf dist/templates && cp -R src/templates dist/templates"
|
|
61
|
+
}
|
|
6
62
|
}
|