@kisev/agentomatic 11.0.0-dev.69.gd644a007a7ee → 11.0.0-dev.71.gee4e37a5bb61

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/cli.js CHANGED
@@ -9,123 +9,114 @@ import { LifecycleError } from "./lifecycle.js";
9
9
  import { skillsInstallerSpec } from "./package-metadata.js";
10
10
  import { applyReconcile, previewReconcile } from "./reconcile.js";
11
11
  import { confirmQuestion, promptText, selectOption, selectOptions } from "./terminal-wizard.js";
12
+ import { Command, Option, common, applyConfig, configFileSync, reporter } from "./generated/cli.js";
13
+ let diagnostics;
14
+ let machineOutput = false;
15
+ let diagnosticJSON = false;
12
16
  function parseOptions(values) {
13
- const options = {
14
- scope: "project",
15
- global: false,
16
- dryRun: false,
17
- json: false,
18
- yes: false,
19
- selectionFlag: false,
20
- };
21
- for (let index = 0; index < values.length; index += 1) {
22
- const value = values[index];
23
- if (!value.startsWith("--")) {
24
- if (options.name)
25
- throw new InstallerError("invalid_input", `Unexpected argument: ${value}`);
26
- options.name = value;
27
- }
28
- else if (value === "--global") {
29
- if (options.global)
30
- throw new InstallerError("invalid_input", "--global may be supplied once");
31
- options.global = true;
32
- options.scope = "global";
33
- }
34
- else if (value === "--dry-run") {
35
- if (options.dryRun)
36
- throw new InstallerError("invalid_input", "--dry-run may be supplied once");
37
- options.dryRun = true;
38
- }
39
- else if (value === "--yes") {
40
- if (options.yes)
41
- throw new InstallerError("invalid_input", "--yes may be supplied once");
42
- options.yes = true;
43
- }
44
- else if (value === "--core") {
45
- if (options.core === true || options.core === false)
46
- throw new InstallerError("invalid_input", "Use only one core integration option");
47
- options.core = true;
48
- }
49
- else if (value === "--no-core") {
50
- if (options.core === true || options.core === false)
51
- throw new InstallerError("invalid_input", "Use only one core integration option");
52
- options.core = false;
53
- }
54
- else if (value === "--provider") {
55
- options.provider = values[++index];
56
- if (!options.provider)
57
- throw new InstallerError("invalid_input", "--provider requires a value");
58
- }
59
- else if (value === "--model") {
60
- options.model = values[++index];
61
- if (!options.model)
62
- throw new InstallerError("invalid_input", "--model requires a value");
63
- }
64
- else if (value === "--variant") {
65
- options.variant = values[++index];
66
- if (!options.variant)
67
- throw new InstallerError("invalid_input", "--variant requires a value");
68
- }
69
- else if (value === "--clear-variant") {
70
- if (options.variant !== undefined)
71
- throw new InstallerError("invalid_input", "Use only one variant option");
72
- options.variant = null;
73
- }
74
- else if (value === "--json") {
75
- if (options.json)
76
- throw new InstallerError("invalid_input", "--json may be supplied once");
77
- options.json = true;
78
- }
79
- else if (value === "--no-dependency") {
80
- if (options.noDependency)
81
- throw new InstallerError("invalid_input", "--no-dependency may be supplied once");
82
- options.noDependency = true;
83
- }
84
- else if (["--commands", "--skill-commands", "--agents", "--plugins"].includes(value)) {
85
- const raw = values[++index];
86
- if (!raw)
87
- throw new InstallerError("invalid_input", `${value} requires a comma-separated value`);
88
- const target = value === "--agents" ? "agents" : value === "--plugins" ? "plugins" : "commands";
89
- const names = raw === "none"
90
- ? []
91
- : raw
92
- .split(",")
93
- .map((item) => item.trim())
94
- .filter(Boolean);
95
- options.selectionFlag = true;
96
- if (value === "--skill-commands") {
97
- options.commands = [...(options.commands ?? []), ...names];
98
- }
99
- else {
100
- options[target] = names;
101
- }
102
- }
103
- else if (value === "--targets" || value === "--fragments") {
104
- const raw = values[++index];
105
- if (!raw)
106
- throw new InstallerError("invalid_input", `${value} requires a comma-separated value or none`);
107
- const names = raw === "none"
108
- ? []
109
- : raw
110
- .split(",")
111
- .map((item) => item.trim())
112
- .filter(Boolean);
113
- options.selectionFlag = true;
114
- if (value === "--targets")
115
- options.configTargets = names;
116
- else
117
- options.configFragments = names;
118
- }
119
- else {
120
- throw new InstallerError("invalid_input", `Unknown argument: ${value}`);
121
- }
17
+ const parser = common(new Command()
18
+ .exitOverride()
19
+ .helpOption(false)
20
+ .configureOutput({ writeErr: () => { } }), "AGENTOMATIC").argument("[name]");
21
+ for (const [flags, description] of [
22
+ ["--global", "global user scope"],
23
+ ["--dry-run", "preview changes"],
24
+ ["--yes", "explicitly confirm the displayed operation"],
25
+ ["--core", "activate core integration"],
26
+ ["--no-core", "disable core integration"],
27
+ ["--provider <value>", "provider"],
28
+ ["--model <value>", "model"],
29
+ ["--variant <value>", "reasoning variant"],
30
+ ["--clear-variant", "clear the variant"],
31
+ ["--no-dependency", "do not provision dependencies"],
32
+ ["--commands <value>", "command selection"],
33
+ ["--skill-commands <value>", "command selection alias"],
34
+ ["--agents <value>", "agent selection"],
35
+ ["--plugins <value>", "plugin selection"],
36
+ ["--targets <value>", "config targets"],
37
+ ["--fragments <value>", "config fragments"],
38
+ ]) {
39
+ const option = new Option(flags, description);
40
+ if (!["--yes", "--core", "--no-core", "--clear-variant", "--no-dependency"].includes(flags))
41
+ option.env(`AGENTOMATIC_${option
42
+ .attributeName()
43
+ .replace(/[A-Z]/g, (char) => `_${char}`)
44
+ .toUpperCase()}`);
45
+ parser.addOption(option);
46
+ }
47
+ try {
48
+ for (const flag of ["--global", "--dry-run", "--yes", "--json", "--no-dependency"])
49
+ if (values.filter((value) => value === flag).length > 1)
50
+ throw new Error(`${flag} may be supplied once`);
51
+ if (values.includes("--core") && values.includes("--no-core"))
52
+ throw new Error("Use only one core integration option");
53
+ if (values.includes("--variant") && values.includes("--clear-variant"))
54
+ throw new Error("Use only one variant option");
55
+ parser.parse(values, { from: "user" });
56
+ applyConfig(parser, configFileSync(parser.opts().config));
57
+ const parsed = parser.opts();
58
+ machineOutput = parsed.json === true;
59
+ diagnosticJSON = parsed.logFormat === "json";
60
+ diagnostics ??= reporter({ ...parsed, progress: parsed.progress ?? "never" });
61
+ diagnostics.emit({
62
+ phase: "agentomatic",
63
+ message: "Validated command options",
64
+ level: "debug",
65
+ });
66
+ const list = (value) => value === undefined
67
+ ? undefined
68
+ : Array.isArray(value)
69
+ ? value.map(String)
70
+ : String(value) === "none"
71
+ ? []
72
+ : String(value)
73
+ .split(",")
74
+ .map((item) => item.trim())
75
+ .filter(Boolean);
76
+ return {
77
+ scope: parsed.global ? "global" : "project",
78
+ global: parsed.global === true,
79
+ dryRun: parsed.dryRun === true,
80
+ json: parsed.json === true,
81
+ yes: parsed.yes === true,
82
+ name: parser.args[0],
83
+ core: ["cli", "env", "config"].includes(parser.getOptionValueSource("core") ?? "")
84
+ ? parsed.core
85
+ : undefined,
86
+ provider: parsed.provider,
87
+ model: parsed.model,
88
+ variant: parsed.clearVariant ? null : parsed.variant,
89
+ noDependency: parsed.dependency === false,
90
+ commands: list(parsed.commands ?? parsed.skillCommands),
91
+ agents: list(parsed.agents),
92
+ plugins: list(parsed.plugins),
93
+ configTargets: list(parsed.targets),
94
+ configFragments: list(parsed.fragments),
95
+ selectionFlag: [
96
+ "commands",
97
+ "skillCommands",
98
+ "agents",
99
+ "plugins",
100
+ "targets",
101
+ "fragments",
102
+ ].some((key) => parsed[key] !== undefined),
103
+ };
104
+ }
105
+ catch (error) {
106
+ throw new InstallerError("invalid_input", error instanceof Error ? error.message : String(error));
122
107
  }
123
- return options;
124
108
  }
125
109
  function helpRows(rows) {
126
110
  const width = Math.max(...rows.map(([label]) => label.length));
127
111
  return rows.map(([label, description]) => ` ${label.padEnd(width)} ${description}`);
128
112
  }
113
+ function commonRows() {
114
+ const parser = common(new Command("agentomatic"), "AGENTOMATIC");
115
+ const help = parser.createHelp();
116
+ return parser.options
117
+ .filter((option) => option.attributeName() !== "json")
118
+ .map((option) => [help.optionTerm(option), help.optionDescription(option)]);
119
+ }
129
120
  function rootHelp() {
130
121
  return [
131
122
  `agentomatic ${CATALOG.version}`,
@@ -164,6 +155,7 @@ function rootHelp() {
164
155
  ["--json", "Emit stable machine-readable output when supported."],
165
156
  ["--help", "Show this help and exit."],
166
157
  ["--version", "Show the package version and exit."],
158
+ ...commonRows(),
167
159
  ]),
168
160
  "",
169
161
  "Install selection:",
@@ -234,7 +226,19 @@ function commandHelp(topic, summary, usage, options, behavior, examples) {
234
226
  ...usage.map((line) => ` ${line}`),
235
227
  "",
236
228
  "Options:",
237
- ...helpRows(options),
229
+ ...helpRows(options.map(([flag, description]) => {
230
+ const match = /^--([a-z][a-z-]*)/.exec(flag);
231
+ const environment = match &&
232
+ !["yes", "core", "no-core", "no-dependency", "clear-variant", "help"].includes(match[1]);
233
+ return [
234
+ flag,
235
+ environment
236
+ ? `${description} (env: AGENTOMATIC_${match[1].replaceAll("-", "_").toUpperCase()})`
237
+ : description,
238
+ ];
239
+ })),
240
+ "",
241
+ ...helpRows(commonRows()),
238
242
  "",
239
243
  "Behavior:",
240
244
  ...behavior.map((line) => ` ${line}`),
@@ -1227,17 +1231,28 @@ async function run(arguments_) {
1227
1231
  }
1228
1232
  async function main() {
1229
1233
  try {
1230
- await run(process.argv.slice(2));
1234
+ let args = process.argv
1235
+ .slice(2)
1236
+ .map((value) => (value === "-h" ? "--help" : value === "-V" ? "--version" : value));
1237
+ if (args[0] === "help")
1238
+ args = [...args.slice(1), "--help"];
1239
+ await run(args);
1231
1240
  }
1232
1241
  catch (error) {
1233
1242
  const known = error instanceof LifecycleError
1234
1243
  ? error
1235
1244
  : new InstallerError("internal_error", error instanceof Error ? error.message : String(error));
1236
- if (process.argv.includes("--json"))
1245
+ if (process.argv.includes("--json") || machineOutput)
1237
1246
  process.stdout.write(`${JSON.stringify({ status: "error", error: { code: known.code, message: known.message } })}\n`);
1247
+ else if (diagnosticJSON)
1248
+ process.stderr.write(`${JSON.stringify({ level: "error", phase: "agentomatic.error", code: known.code, message: known.message })}\n`);
1238
1249
  else
1239
1250
  process.stderr.write(`Error [${known.code}]: ${terminalSafe(known.message)}\n`);
1240
1251
  process.exitCode = 2;
1241
1252
  }
1253
+ finally {
1254
+ diagnostics?.emit({ phase: "agentomatic.done", message: "Command finished", level: "debug" });
1255
+ diagnostics?.close();
1256
+ }
1242
1257
  }
1243
1258
  void main();
@@ -0,0 +1,28 @@
1
+ import { Command, Option, CommanderError, InvalidArgumentError } from "commander";
2
+ export { Command, Option, CommanderError, InvalidArgumentError };
3
+ export declare function integer(value: string): number;
4
+ export declare function duration(value: string): number;
5
+ export declare function program(name: string, description: string, version: string): Command;
6
+ export declare function common(command: Command, prefix: string): Command;
7
+ export declare function configFile(path: string | undefined): Promise<Record<string, unknown>>;
8
+ export declare function configFileSync(path: string | undefined): Record<string, unknown>;
9
+ export declare function applyConfig(command: Command, config: Record<string, unknown>): void;
10
+ export type Event = {
11
+ phase: string;
12
+ message: string;
13
+ level?: "debug" | "info" | "warn" | "error";
14
+ completed?: number;
15
+ total?: number;
16
+ elapsedMs?: number;
17
+ [key: string]: unknown;
18
+ };
19
+ export type Observe = (event: Event) => void;
20
+ export declare function reporter(options?: Record<string, unknown>): {
21
+ emit: Observe;
22
+ close: () => void;
23
+ };
24
+ export declare function cancellation(): {
25
+ signal: AbortSignal;
26
+ dispose: () => void;
27
+ };
28
+ export declare function fail(name: string, error: unknown, json?: boolean): void;
@@ -0,0 +1,181 @@
1
+ import { Command, Option, CommanderError, InvalidArgumentError } from "commander";
2
+ import { readFile } from "node:fs/promises";
3
+ import { readFileSync } from "node:fs";
4
+ export { Command, Option, CommanderError, InvalidArgumentError };
5
+ export function integer(value) {
6
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)))
7
+ throw new InvalidArgumentError("expected a non-negative integer");
8
+ return Number(value);
9
+ }
10
+ export function duration(value) {
11
+ const match = /^(\d+)(ms|s|m|h)?$/.exec(value);
12
+ if (!match)
13
+ throw new InvalidArgumentError("expected a duration such as 500ms, 30s or 5m");
14
+ const result = Number(match[1]) * ({ ms: 1, s: 1000, m: 60000, h: 3600000 }[match[2] ?? "ms"] ?? 1);
15
+ if (!Number.isSafeInteger(result) || result < 0)
16
+ throw new InvalidArgumentError("duration is out of range");
17
+ return result;
18
+ }
19
+ export function program(name, description, version) {
20
+ return new Command(name)
21
+ .description(description)
22
+ .version(version)
23
+ .exitOverride()
24
+ .configureHelp({ showGlobalOptions: true })
25
+ .showHelpAfterError()
26
+ .configureOutput({ outputError: () => { } });
27
+ }
28
+ export function common(command, prefix) {
29
+ return command
30
+ .addOption(new Option("--config <file>", "JSON configuration file (CLI > env > file > defaults)").env(`${prefix}_CONFIG`))
31
+ .addOption(new Option("--json", "machine-readable result on stdout").env(`${prefix}_JSON`))
32
+ .addOption(new Option("--log-level <level>", "diagnostic verbosity on stderr")
33
+ .choices(["debug", "info", "warn", "error", "silent"])
34
+ .env(`${prefix}_LOG_LEVEL`))
35
+ .addOption(new Option("--log-format <format>", "diagnostic format on stderr")
36
+ .choices(["text", "json"])
37
+ .env(`${prefix}_LOG_FORMAT`))
38
+ .addOption(new Option("--progress <mode>", "terminal progress; never written to stdout")
39
+ .choices(["auto", "always", "never"])
40
+ .env(`${prefix}_PROGRESS`))
41
+ .addOption(new Option("--color <mode>", "color policy (NO_COLOR disables color)")
42
+ .choices(["auto", "always", "never"])
43
+ .env(`${prefix}_COLOR`));
44
+ }
45
+ export async function configFile(path) {
46
+ if (!path)
47
+ return {};
48
+ const value = JSON.parse(await readFile(path, "utf8"));
49
+ if (!value || typeof value !== "object" || Array.isArray(value))
50
+ throw new InvalidArgumentError("configuration must be a JSON object");
51
+ return value;
52
+ }
53
+ export function configFileSync(path) {
54
+ if (!path)
55
+ return {};
56
+ const value = JSON.parse(readFileSync(path, "utf8"));
57
+ if (!value || typeof value !== "object" || Array.isArray(value))
58
+ throw new InvalidArgumentError("configuration must be a JSON object");
59
+ return value;
60
+ }
61
+ export function applyConfig(command, config) {
62
+ for (const option of command.options) {
63
+ const key = option.attributeName();
64
+ if (!(key in config) || ["cli", "env"].includes(command.getOptionValueSource(key) ?? ""))
65
+ continue;
66
+ if (["yes", "config"].includes(key))
67
+ continue;
68
+ const value = config[key];
69
+ if (option.required || option.optional) {
70
+ const raw = String(value);
71
+ if (option.argChoices && !option.argChoices.includes(raw))
72
+ throw new InvalidArgumentError(`invalid configuration value for ${key}`);
73
+ command.setOptionValueWithSource(key, option.parseArg ? option.parseArg(raw, undefined) : raw, "config");
74
+ }
75
+ else {
76
+ if (typeof value !== "boolean")
77
+ throw new InvalidArgumentError(`${key} must be boolean`);
78
+ command.setOptionValueWithSource(key, value, "config");
79
+ }
80
+ }
81
+ }
82
+ export function reporter(options = {}) {
83
+ const started = Date.now();
84
+ const level = String(options.logLevel ?? "info");
85
+ const levels = ["debug", "info", "warn", "error", "silent"];
86
+ const json = options.logFormat === "json";
87
+ const interactive = !json &&
88
+ options.progress !== "never" &&
89
+ (options.progress === "always" || process.stderr.isTTY);
90
+ let current;
91
+ let painted = false;
92
+ let frame = 0;
93
+ let meter;
94
+ const bar = () => {
95
+ if (!meter || meter.total <= 0)
96
+ return "";
97
+ const ratio = Math.min(1, Math.max(0, meter.completed / meter.total));
98
+ const filled = Math.floor(ratio * 16);
99
+ return ` | ${meter.phase} [${"█".repeat(filled)}${"░".repeat(16 - filled)}] ${Math.floor(ratio * 100)}%`;
100
+ };
101
+ const clean = (text) => Array.from(text, (char) => {
102
+ const code = char.codePointAt(0);
103
+ return code < 32 || (code >= 127 && code <= 159) ? " " : char;
104
+ }).join("");
105
+ const line = (event) => `${event.phase}: ${event.message}${event.total !== undefined ? ` [${event.completed ?? 0}/${event.total}]` : ""} (${Math.floor((Date.now() - started) / 1000)}s)`;
106
+ const clear = () => {
107
+ if (painted) {
108
+ process.stderr.write("\r\x1b[2K");
109
+ painted = false;
110
+ }
111
+ };
112
+ const timer = interactive && level !== "silent"
113
+ ? setInterval(() => {
114
+ if (current) {
115
+ const color = process.env.NO_COLOR === undefined &&
116
+ (options.color === "always" || (options.color !== "never" && process.stderr.isTTY));
117
+ process.stderr.write(`\r\x1b[2K${color ? "\x1b[36m" : ""}${["⠋", "⠙", "⠹", "⠸"][frame++ % 4]} ${clean(line(current))}${bar()}${color ? "\x1b[0m" : ""}`);
118
+ painted = true;
119
+ }
120
+ }, 120)
121
+ : undefined;
122
+ timer?.unref();
123
+ const emit = (event) => {
124
+ const severity = event.level ?? "info";
125
+ if (levels.indexOf(severity) < levels.indexOf(level))
126
+ return;
127
+ current = event;
128
+ if (event.total !== undefined)
129
+ meter =
130
+ event.total > 0
131
+ ? {
132
+ phase: event.phase.split(".")[0],
133
+ completed: event.completed ?? 0,
134
+ total: event.total,
135
+ }
136
+ : undefined;
137
+ else if (meter && meter.completed >= meter.total)
138
+ meter = undefined;
139
+ clear();
140
+ process.stderr.write(json
141
+ ? `${JSON.stringify({ at: new Date().toISOString(), elapsedMs: Date.now() - started, ...event })}\n`
142
+ : `${clean(line(event))}\n`);
143
+ };
144
+ return {
145
+ emit,
146
+ close: () => {
147
+ if (timer)
148
+ clearInterval(timer);
149
+ clear();
150
+ },
151
+ };
152
+ }
153
+ export function cancellation() {
154
+ const controller = new AbortController();
155
+ const interrupt = () => controller.abort(new Error("interrupted"));
156
+ process.on("SIGINT", interrupt);
157
+ process.on("SIGTERM", interrupt);
158
+ process.on("SIGHUP", interrupt);
159
+ return {
160
+ signal: controller.signal,
161
+ dispose: () => {
162
+ process.off("SIGINT", interrupt);
163
+ process.off("SIGTERM", interrupt);
164
+ process.off("SIGHUP", interrupt);
165
+ },
166
+ };
167
+ }
168
+ export function fail(name, error, json = false) {
169
+ if (error instanceof CommanderError && error.exitCode === 0)
170
+ return;
171
+ const message = error instanceof Error ? error.message : String(error);
172
+ const interrupted = /interrupted|aborted/i.test(message);
173
+ process.stderr.write(json ? `${JSON.stringify({ status: "error", message })}\n` : `${name}: ${message}\n`);
174
+ process.exitCode = /timeout|timed out|deadline/i.test(message)
175
+ ? 124
176
+ : interrupted
177
+ ? 130
178
+ : error instanceof CommanderError
179
+ ? 2
180
+ : 1;
181
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kisev/agentomatic",
3
- "version": "11.0.0-dev.69.gd644a007a7ee",
3
+ "version": "11.0.0-dev.71.gee4e37a5bb61",
4
4
  "description": "OpenCode integration and opt-in installer for portable Agent Skills.",
5
5
  "homepage": "https://github.com/kisev/skills#readme",
6
6
  "bugs": {
@@ -46,7 +46,8 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@clack/prompts": "^1.8.1",
49
- "@kisev/safe-fs": "1.0.0-dev.69.gd644a007a7ee"
49
+ "@kisev/safe-fs": "1.0.0-dev.71.gee4e37a5bb61",
50
+ "commander": "15.0.0"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@opencode-ai/plugin": "1.18.29",
@@ -62,7 +63,7 @@
62
63
  }
63
64
  },
64
65
  "engines": {
65
- "node": ">=22"
66
+ "node": ">=22.13"
66
67
  },
67
68
  "skillsInstallerVersion": "1.7.0"
68
69
  }