aavegotchi-cli 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/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # aavegotchi-cli
2
+
3
+ Agent-first CLI for automating Aavegotchi app and onchain workflows on Base.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g aavegotchi-cli
9
+ ```
10
+
11
+ For local development:
12
+
13
+ ```bash
14
+ npm install
15
+ ```
16
+
17
+ ## First command an agent should run
18
+
19
+ ```bash
20
+ AGCLI_PRIVATE_KEY=0x... npm run ag -- bootstrap \
21
+ --mode agent \
22
+ --profile prod \
23
+ --chain base \
24
+ --signer env:AGCLI_PRIVATE_KEY \
25
+ --policy default \
26
+ --json
27
+ ```
28
+
29
+ For read-only automation:
30
+
31
+ ```bash
32
+ npm run ag -- bootstrap --mode agent --profile prod --chain base --signer readonly --json
33
+ ```
34
+
35
+ ## Command surface (current)
36
+
37
+ - `bootstrap`
38
+ - `profile list|show|use|export`
39
+ - `signer check`
40
+ - `signer keychain list|import|remove`
41
+ - `policy list|show|upsert`
42
+ - `rpc check`
43
+ - `tx send|status|resume|watch`
44
+ - `batch run --file plan.yaml`
45
+ - `onchain call|send`
46
+ - `<domain> read` (routes to generic onchain call for that domain)
47
+
48
+ Planned domain namespaces are stubbed for parity tracking:
49
+
50
+ - `gotchi`, `portal`, `wearables`, `items`, `inventory`, `baazaar`, `auction`, `lending`, `staking`, `gotchi-points`, `realm`, `alchemica`, `forge`, `token`
51
+
52
+ Many Base-era write flows are already executable as mapped aliases in those namespaces (internally routed through `onchain send`).
53
+ Example: `ag lending create --abi-file ./abis/GotchiLendingFacet.json --address 0x... --args-json '[...]' --json`
54
+
55
+ ## Signer backends
56
+
57
+ - `readonly` (read-only mode)
58
+ - `env:ENV_VAR` (private key from env var)
59
+ - `keychain:ACCOUNT_ID` (encrypted local key store; requires `AGCLI_KEYCHAIN_PASSPHRASE`)
60
+ - `remote:URL|ADDRESS|AUTH_ENV` (HTTP signer service)
61
+ - `ledger:DERIVATION_PATH|ADDRESS|BRIDGE_ENV` (external bridge command signer)
62
+
63
+ Remote signer contract:
64
+
65
+ - `GET /address` -> `{ "address": "0x..." }` (optional if address configured)
66
+ - `POST /sign-transaction` -> `{ "rawTransaction": "0x..." }` or `{ "txHash": "0x..." }`
67
+
68
+ Ledger bridge contract:
69
+
70
+ - Set `AGCLI_LEDGER_BRIDGE_CMD` (or custom env var in signer config) to a command that reads tx payload JSON from stdin and outputs JSON containing either `rawTransaction` or `txHash`.
71
+
72
+ Keychain import example:
73
+
74
+ ```bash
75
+ AGCLI_KEYCHAIN_PASSPHRASE=your-passphrase \
76
+ AGCLI_PRIVATE_KEY=0x... \
77
+ npm run ag -- signer keychain import --account-id bot --private-key-env AGCLI_PRIVATE_KEY --json
78
+ ```
79
+
80
+ ## Agent-mode behavior
81
+
82
+ `--mode agent` implies:
83
+
84
+ - `--json`
85
+ - `--yes`
86
+
87
+ All successful/error responses use a stable envelope:
88
+
89
+ ```json
90
+ {
91
+ "schemaVersion": "1.0.0",
92
+ "command": "tx send",
93
+ "status": "ok",
94
+ "data": {},
95
+ "meta": { "timestamp": "...", "mode": "agent" }
96
+ }
97
+ ```
98
+
99
+ ## Config and journal
100
+
101
+ - Config default path: `~/.aavegotchi-cli/config.json`
102
+ - Journal default path: `~/.aavegotchi-cli/journal.sqlite`
103
+ - Override both via `AGCLI_HOME=/custom/path`
104
+
105
+ ## Parity artifacts
106
+
107
+ - Method inventory: [`docs/parity/base-method-inventory.md`](docs/parity/base-method-inventory.md)
108
+ - Command mapping: [`docs/parity/base-command-matrix.md`](docs/parity/base-command-matrix.md)
109
+
110
+ Raffle/ticket flows are intentionally excluded for Base-era scope.
111
+
112
+ ## Development
113
+
114
+ ```bash
115
+ npm run typecheck
116
+ npm test
117
+ npm run build
118
+ npm run parity:check
119
+ npm run ag -- help
120
+ ```
package/dist/args.js ADDED
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseArgv = parseArgv;
4
+ exports.getFlagString = getFlagString;
5
+ exports.getFlagBoolean = getFlagBoolean;
6
+ exports.normalizeGlobals = normalizeGlobals;
7
+ exports.normalizeMode = normalizeMode;
8
+ const errors_1 = require("./errors");
9
+ function parseArgv(argv) {
10
+ const positionals = [];
11
+ const flags = {};
12
+ for (let i = 0; i < argv.length; i++) {
13
+ const token = argv[i];
14
+ if (!token.startsWith("-")) {
15
+ positionals.push(token);
16
+ continue;
17
+ }
18
+ if (token.startsWith("--")) {
19
+ const body = token.slice(2);
20
+ const eqIndex = body.indexOf("=");
21
+ if (eqIndex >= 0) {
22
+ const key = body.slice(0, eqIndex);
23
+ const value = body.slice(eqIndex + 1);
24
+ flags[key] = value;
25
+ continue;
26
+ }
27
+ const key = body;
28
+ const next = argv[i + 1];
29
+ if (next && !next.startsWith("-")) {
30
+ flags[key] = next;
31
+ i++;
32
+ continue;
33
+ }
34
+ flags[key] = true;
35
+ continue;
36
+ }
37
+ // Short flags can be grouped, e.g. -jy
38
+ const shorts = token.slice(1).split("");
39
+ for (const shortFlag of shorts) {
40
+ flags[shortFlag] = true;
41
+ }
42
+ }
43
+ return { positionals, flags };
44
+ }
45
+ function getFlagString(flags, ...keys) {
46
+ for (const key of keys) {
47
+ const value = flags[key];
48
+ if (typeof value === "string") {
49
+ return value;
50
+ }
51
+ }
52
+ return undefined;
53
+ }
54
+ function getFlagBoolean(flags, ...keys) {
55
+ for (const key of keys) {
56
+ const value = flags[key];
57
+ if (typeof value === "boolean") {
58
+ return value;
59
+ }
60
+ if (typeof value === "string") {
61
+ const normalized = value.toLowerCase();
62
+ if (normalized === "true") {
63
+ return true;
64
+ }
65
+ if (normalized === "false") {
66
+ return false;
67
+ }
68
+ }
69
+ }
70
+ return false;
71
+ }
72
+ function normalizeGlobals(args) {
73
+ const modeValue = getFlagString(args.flags, "mode");
74
+ const mode = normalizeMode(modeValue);
75
+ const explicitJson = getFlagBoolean(args.flags, "json", "j");
76
+ const explicitYes = getFlagBoolean(args.flags, "yes", "y");
77
+ // Agent mode defaults to machine-first behavior.
78
+ const json = explicitJson || mode === "agent";
79
+ const yes = explicitYes || mode === "agent";
80
+ const profile = getFlagString(args.flags, "profile");
81
+ return {
82
+ mode,
83
+ json,
84
+ yes,
85
+ profile,
86
+ };
87
+ }
88
+ function normalizeMode(value) {
89
+ if (!value) {
90
+ return "human";
91
+ }
92
+ const normalized = value.toLowerCase();
93
+ if (normalized === "human" || normalized === "agent") {
94
+ return normalized;
95
+ }
96
+ throw new errors_1.CliError("INVALID_MODE", `Unsupported mode '${value}'. Expected 'human' or 'agent'.`, 2);
97
+ }
package/dist/chains.js ADDED
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveChain = resolveChain;
4
+ exports.resolveRpcUrl = resolveRpcUrl;
5
+ exports.toViemChain = toViemChain;
6
+ const viem_1 = require("viem");
7
+ const chains_1 = require("viem/chains");
8
+ const errors_1 = require("./errors");
9
+ const CHAIN_PRESETS = {
10
+ base: {
11
+ key: "base",
12
+ chainId: 8453,
13
+ defaultRpcUrl: "https://mainnet.base.org",
14
+ viemChain: chains_1.base,
15
+ },
16
+ "base-sepolia": {
17
+ key: "base-sepolia",
18
+ chainId: 84532,
19
+ defaultRpcUrl: "https://sepolia.base.org",
20
+ viemChain: chains_1.baseSepolia,
21
+ },
22
+ };
23
+ function resolveChain(value) {
24
+ if (!value) {
25
+ return CHAIN_PRESETS.base;
26
+ }
27
+ const normalized = value.trim().toLowerCase();
28
+ if (CHAIN_PRESETS[normalized]) {
29
+ return CHAIN_PRESETS[normalized];
30
+ }
31
+ if (/^\d+$/.test(normalized)) {
32
+ return {
33
+ key: `chain-${normalized}`,
34
+ chainId: Number(normalized),
35
+ defaultRpcUrl: undefined,
36
+ };
37
+ }
38
+ throw new errors_1.CliError("INVALID_CHAIN", `Unsupported chain '${value}'. Use 'base', 'base-sepolia', or a numeric chain id.`, 2);
39
+ }
40
+ function resolveRpcUrl(chain, rpcFlag) {
41
+ if (rpcFlag) {
42
+ return rpcFlag;
43
+ }
44
+ if (chain.key === "base" && process.env.BASE_RPC_URL) {
45
+ return process.env.BASE_RPC_URL;
46
+ }
47
+ if (process.env.AGCLI_RPC_URL) {
48
+ return process.env.AGCLI_RPC_URL;
49
+ }
50
+ if (chain.defaultRpcUrl) {
51
+ return chain.defaultRpcUrl;
52
+ }
53
+ throw new errors_1.CliError("MISSING_RPC_URL", "RPC URL is required for custom chain IDs.", 2);
54
+ }
55
+ function toViemChain(chain, rpcUrl) {
56
+ const preset = CHAIN_PRESETS[chain.key];
57
+ if (preset) {
58
+ return preset.viemChain;
59
+ }
60
+ return (0, viem_1.defineChain)({
61
+ id: chain.chainId,
62
+ name: chain.key,
63
+ nativeCurrency: {
64
+ name: "Ether",
65
+ symbol: "ETH",
66
+ decimals: 18,
67
+ },
68
+ rpcUrls: {
69
+ default: {
70
+ http: [rpcUrl],
71
+ },
72
+ },
73
+ });
74
+ }
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeCommandPath = normalizeCommandPath;
4
+ exports.executeCommand = executeCommand;
5
+ const errors_1 = require("./errors");
6
+ const batch_1 = require("./commands/batch");
7
+ const bootstrap_1 = require("./commands/bootstrap");
8
+ const mapped_1 = require("./commands/mapped");
9
+ const onchain_1 = require("./commands/onchain");
10
+ const policy_1 = require("./commands/policy");
11
+ const profile_1 = require("./commands/profile");
12
+ const rpc_1 = require("./commands/rpc");
13
+ const signer_1 = require("./commands/signer");
14
+ const stubs_1 = require("./commands/stubs");
15
+ const tx_1 = require("./commands/tx");
16
+ function normalizeCommandPath(positionals) {
17
+ if (positionals.length === 0 || positionals[0] === "help") {
18
+ return ["help"];
19
+ }
20
+ return positionals;
21
+ }
22
+ async function executeCommand(ctx) {
23
+ const [root, sub] = ctx.commandPath;
24
+ if (root === "help") {
25
+ return {
26
+ commandName: "help",
27
+ data: { help: true },
28
+ };
29
+ }
30
+ if (root === "bootstrap") {
31
+ return {
32
+ commandName: "bootstrap",
33
+ data: await (0, bootstrap_1.runBootstrapCommand)(ctx),
34
+ };
35
+ }
36
+ if (root === "profile") {
37
+ if (!sub || sub === "show") {
38
+ return {
39
+ commandName: "profile show",
40
+ data: await (0, profile_1.runProfileShowCommand)(ctx),
41
+ };
42
+ }
43
+ if (sub === "list") {
44
+ return {
45
+ commandName: "profile list",
46
+ data: await (0, profile_1.runProfileListCommand)(),
47
+ };
48
+ }
49
+ if (sub === "use") {
50
+ return {
51
+ commandName: "profile use",
52
+ data: await (0, profile_1.runProfileUseCommand)(ctx),
53
+ };
54
+ }
55
+ if (sub === "export") {
56
+ return {
57
+ commandName: "profile export",
58
+ data: await (0, profile_1.runProfileExportCommand)(ctx),
59
+ };
60
+ }
61
+ }
62
+ if (root === "policy") {
63
+ if (!sub || sub === "list") {
64
+ return {
65
+ commandName: "policy list",
66
+ data: await (0, policy_1.runPolicyListCommand)(),
67
+ };
68
+ }
69
+ if (sub === "show") {
70
+ return {
71
+ commandName: "policy show",
72
+ data: await (0, policy_1.runPolicyShowCommand)(ctx),
73
+ };
74
+ }
75
+ if (sub === "upsert" || sub === "set") {
76
+ return {
77
+ commandName: "policy upsert",
78
+ data: await (0, policy_1.runPolicyUpsertCommand)(ctx),
79
+ };
80
+ }
81
+ }
82
+ if (root === "rpc") {
83
+ if (!sub || sub === "check") {
84
+ return {
85
+ commandName: "rpc check",
86
+ data: await (0, rpc_1.runRpcCheckCommand)(ctx),
87
+ };
88
+ }
89
+ }
90
+ if (root === "signer") {
91
+ if (!sub || sub === "check") {
92
+ return {
93
+ commandName: "signer check",
94
+ data: await (0, signer_1.runSignerCheckCommand)(ctx),
95
+ };
96
+ }
97
+ if (sub === "keychain") {
98
+ const action = ctx.commandPath[2];
99
+ if (!action || action === "list") {
100
+ return {
101
+ commandName: "signer keychain list",
102
+ data: await (0, signer_1.runSignerKeychainListCommand)(),
103
+ };
104
+ }
105
+ if (action === "import") {
106
+ return {
107
+ commandName: "signer keychain import",
108
+ data: await (0, signer_1.runSignerKeychainImportCommand)(ctx),
109
+ };
110
+ }
111
+ if (action === "remove") {
112
+ return {
113
+ commandName: "signer keychain remove",
114
+ data: await (0, signer_1.runSignerKeychainRemoveCommand)(ctx),
115
+ };
116
+ }
117
+ }
118
+ }
119
+ if (root === "tx") {
120
+ if (!sub || sub === "status") {
121
+ return {
122
+ commandName: "tx status",
123
+ data: await (0, tx_1.runTxStatusCommand)(ctx),
124
+ };
125
+ }
126
+ if (sub === "send") {
127
+ return {
128
+ commandName: "tx send",
129
+ data: await (0, tx_1.runTxSendCommand)(ctx),
130
+ };
131
+ }
132
+ if (sub === "resume") {
133
+ return {
134
+ commandName: "tx resume",
135
+ data: await (0, tx_1.runTxResumeCommand)(ctx),
136
+ };
137
+ }
138
+ if (sub === "watch") {
139
+ return {
140
+ commandName: "tx watch",
141
+ data: await (0, tx_1.runTxWatchCommand)(ctx),
142
+ };
143
+ }
144
+ }
145
+ if (root === "onchain") {
146
+ if (!sub || sub === "call") {
147
+ return {
148
+ commandName: "onchain call",
149
+ data: await (0, onchain_1.runOnchainCallCommand)(ctx),
150
+ };
151
+ }
152
+ if (sub === "send") {
153
+ return {
154
+ commandName: "onchain send",
155
+ data: await (0, onchain_1.runOnchainSendCommand)(ctx),
156
+ };
157
+ }
158
+ }
159
+ if (root === "batch" && (!sub || sub === "run")) {
160
+ return {
161
+ commandName: "batch run",
162
+ data: await (0, batch_1.runBatchRunCommand)(ctx, executeCommand),
163
+ };
164
+ }
165
+ if ((0, mapped_1.findMappedFunction)(ctx.commandPath)) {
166
+ return {
167
+ commandName: ctx.commandPath.join(" "),
168
+ data: await (0, mapped_1.runMappedDomainCommand)(ctx),
169
+ };
170
+ }
171
+ if ((0, stubs_1.isDomainStubRoot)(root) && sub === "read") {
172
+ return {
173
+ commandName: ctx.commandPath.join(" "),
174
+ data: await (0, onchain_1.runOnchainCallCommand)(ctx),
175
+ };
176
+ }
177
+ if ((0, stubs_1.isDomainStubRoot)(root)) {
178
+ return {
179
+ commandName: ctx.commandPath.join(" "),
180
+ data: await (0, stubs_1.runDomainStubCommand)(ctx),
181
+ };
182
+ }
183
+ throw new errors_1.CliError("UNKNOWN_COMMAND", `Unknown command '${ctx.commandPath.join(" ")}'.`, 2);
184
+ }
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.runBatchRunCommand = runBatchRunCommand;
40
+ const fs = __importStar(require("fs"));
41
+ const yaml_1 = __importDefault(require("yaml"));
42
+ const args_1 = require("../args");
43
+ const errors_1 = require("../errors");
44
+ const schemas_1 = require("../schemas");
45
+ function parseBatchFile(filePath) {
46
+ if (!fs.existsSync(filePath)) {
47
+ throw new errors_1.CliError("BATCH_FILE_NOT_FOUND", `Batch file not found: ${filePath}`, 2);
48
+ }
49
+ const source = fs.readFileSync(filePath, "utf8");
50
+ const raw = yaml_1.default.parse(source);
51
+ const parsed = schemas_1.batchPlanSchema.safeParse(raw);
52
+ if (!parsed.success) {
53
+ throw new errors_1.CliError("INVALID_BATCH_PLAN", "Batch plan validation failed.", 2, {
54
+ issues: parsed.error.issues,
55
+ });
56
+ }
57
+ return parsed.data;
58
+ }
59
+ function createStepArgs(stepCommand, args) {
60
+ const positionals = stepCommand.split(" ").map((entry) => entry.trim()).filter(Boolean);
61
+ const flags = {};
62
+ for (const [key, value] of Object.entries(args || {})) {
63
+ flags[key] = typeof value === "number" ? String(value) : value;
64
+ }
65
+ return {
66
+ positionals,
67
+ flags,
68
+ };
69
+ }
70
+ function mergeGlobals(parent, plan) {
71
+ return {
72
+ ...parent,
73
+ mode: plan.mode || parent.mode,
74
+ profile: plan.profile || parent.profile,
75
+ json: true,
76
+ yes: true,
77
+ };
78
+ }
79
+ function dependenciesDone(stepDependsOn, completed) {
80
+ if (!stepDependsOn || stepDependsOn.length === 0) {
81
+ return true;
82
+ }
83
+ return stepDependsOn.every((dep) => completed.has(dep));
84
+ }
85
+ async function runBatchRunCommand(ctx, executor) {
86
+ const filePath = (0, args_1.getFlagString)(ctx.args.flags, "file") || (0, args_1.getFlagString)(ctx.args.flags, "f");
87
+ if (!filePath) {
88
+ throw new errors_1.CliError("MISSING_ARGUMENT", "batch run requires --file <plan.yaml>", 2);
89
+ }
90
+ const plan = parseBatchFile(filePath);
91
+ const globals = mergeGlobals(ctx.globals, plan);
92
+ const completed = new Set();
93
+ const failed = new Set();
94
+ const results = [];
95
+ const pending = [...plan.steps];
96
+ while (pending.length > 0) {
97
+ let progressed = false;
98
+ for (let index = 0; index < pending.length; index++) {
99
+ const step = pending[index];
100
+ if (!dependenciesDone(step.dependsOn, completed)) {
101
+ continue;
102
+ }
103
+ progressed = true;
104
+ pending.splice(index, 1);
105
+ index--;
106
+ const startedAt = new Date().toISOString();
107
+ if (step.dependsOn && step.dependsOn.some((dep) => failed.has(dep))) {
108
+ results.push({
109
+ id: step.id,
110
+ command: step.command,
111
+ status: "skipped",
112
+ startedAt,
113
+ finishedAt: new Date().toISOString(),
114
+ error: {
115
+ code: "DEPENDENCY_FAILED",
116
+ message: "One or more dependency steps failed.",
117
+ },
118
+ });
119
+ continue;
120
+ }
121
+ const stepArgs = createStepArgs(step.command, step.args);
122
+ try {
123
+ const childCtx = {
124
+ commandPath: stepArgs.positionals,
125
+ args: {
126
+ positionals: stepArgs.positionals,
127
+ flags: {
128
+ ...stepArgs.flags,
129
+ ...(globals.profile ? { profile: globals.profile } : {}),
130
+ },
131
+ },
132
+ globals,
133
+ };
134
+ const executed = await executor(childCtx);
135
+ completed.add(step.id);
136
+ results.push({
137
+ id: step.id,
138
+ command: step.command,
139
+ status: "ok",
140
+ startedAt,
141
+ finishedAt: new Date().toISOString(),
142
+ data: {
143
+ commandName: executed.commandName,
144
+ data: executed.data,
145
+ },
146
+ });
147
+ }
148
+ catch (error) {
149
+ failed.add(step.id);
150
+ const normalized = error instanceof errors_1.CliError ? error : new errors_1.CliError("STEP_FAILED", String(error));
151
+ results.push({
152
+ id: step.id,
153
+ command: step.command,
154
+ status: "error",
155
+ startedAt,
156
+ finishedAt: new Date().toISOString(),
157
+ error: {
158
+ code: normalized.code,
159
+ message: normalized.message,
160
+ },
161
+ });
162
+ const shouldContinue = step.continueOnError ?? plan.continueOnError ?? false;
163
+ if (!shouldContinue) {
164
+ return {
165
+ filePath,
166
+ aborted: true,
167
+ results,
168
+ };
169
+ }
170
+ }
171
+ }
172
+ if (!progressed) {
173
+ throw new errors_1.CliError("BATCH_DEPENDENCY_CYCLE", "Batch plan has unmet/cyclic dependencies.", 2, {
174
+ pending: pending.map((step) => ({
175
+ id: step.id,
176
+ dependsOn: step.dependsOn || [],
177
+ })),
178
+ completed: [...completed],
179
+ failed: [...failed],
180
+ });
181
+ }
182
+ }
183
+ return {
184
+ filePath,
185
+ completed: [...completed],
186
+ failed: [...failed],
187
+ results,
188
+ };
189
+ }