@kylecheng3146/agent-ops 0.1.3 → 0.1.4
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.
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
import { CliArgumentError, parseArgs } from "./args.js";
|
|
2
2
|
import { errorEnvelope, okEnvelope, writeEnvelope } from "./output.js";
|
|
3
3
|
import { completeInitChoices } from "./wizard.js";
|
|
4
|
-
|
|
5
|
-
"+--------------------------------------------------+",
|
|
6
|
-
"| LOOP ENGINEERING TOOLKIT |",
|
|
7
|
-
"| Safe setup for Codex + Claude Code |",
|
|
8
|
-
"+--------------------------------------------------+"
|
|
9
|
-
].join("\n");
|
|
4
|
+
import { BANNER } from "./ui.js";
|
|
10
5
|
export function renderWelcome(color) {
|
|
11
6
|
const cyan = color ? "\u001b[36m" : "";
|
|
12
7
|
const bold = color ? "\u001b[1m" : "";
|
|
13
8
|
const reset = color ? "\u001b[0m" : "";
|
|
14
|
-
return `${cyan}${bold}${
|
|
9
|
+
return `${cyan}${bold}${BANNER}${reset}\n\n`;
|
|
15
10
|
}
|
|
16
11
|
export const HELP_TEXT = `Usage: agent-ops <command> [options]
|
|
17
12
|
|
|
@@ -4,16 +4,34 @@ import { applyInstallPlan } from "../../../../runtime/src/install/apply.js";
|
|
|
4
4
|
import { okEnvelope } from "../output.js";
|
|
5
5
|
import { formatOperationPlan } from "../plan-output.js";
|
|
6
6
|
export function formatInstallPlan(plan) {
|
|
7
|
+
const hooks = plan.manifest.hooks ?? [];
|
|
7
8
|
return formatOperationPlan({
|
|
8
9
|
title: "Installation plan",
|
|
9
10
|
metadata: [
|
|
10
11
|
`Scope: ${plan.scope}`,
|
|
11
12
|
`Harness: ${plan.harness}`,
|
|
12
|
-
`Profiles: ${plan.profiles.join(", ")}
|
|
13
|
+
`Profiles: ${plan.profiles.join(", ")}`,
|
|
14
|
+
...(hooks.length === 0
|
|
15
|
+
? ["Hooks: none selected"]
|
|
16
|
+
: [
|
|
17
|
+
"Hooks:",
|
|
18
|
+
...hooks.map((hook) => ` - ${hook.harness}: ${hook.path} (${hook.events.join(", ")})`)
|
|
19
|
+
])
|
|
13
20
|
],
|
|
14
21
|
operations: plan.operations
|
|
15
22
|
});
|
|
16
23
|
}
|
|
24
|
+
function appliedMessage(plan) {
|
|
25
|
+
const hooks = plan.manifest.hooks ?? [];
|
|
26
|
+
if (hooks.length === 0) {
|
|
27
|
+
return "Loop Engineering Toolkit installation applied.\nHooks: none selected.";
|
|
28
|
+
}
|
|
29
|
+
return [
|
|
30
|
+
"Loop Engineering Toolkit installation applied.",
|
|
31
|
+
"Hooks configured:",
|
|
32
|
+
...hooks.map((hook) => `- ${hook.harness}: ${hook.path} (${hook.events.join(", ")})`)
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
17
35
|
function initError(code, message, plan) {
|
|
18
36
|
return {
|
|
19
37
|
code,
|
|
@@ -63,6 +81,6 @@ export async function runInitCommand(options) {
|
|
|
63
81
|
return okEnvelope("INIT_APPLIED", {
|
|
64
82
|
applied: true,
|
|
65
83
|
plan,
|
|
66
|
-
message:
|
|
84
|
+
message: appliedMessage(plan)
|
|
67
85
|
});
|
|
68
86
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// figlet "agent-ops" -f Standard, trimmed of trailing blank lines/columns.
|
|
2
|
-
const BANNER = [
|
|
2
|
+
export const BANNER = [
|
|
3
3
|
" _",
|
|
4
4
|
" __ _ __ _ ___ _ __ | |_ ___ _ __ ___ ",
|
|
5
5
|
" / _\` |/ _\` |/ _ \\ '_ \\| __|____ / _ \\| '_ \\/ __|",
|
|
@@ -75,6 +75,247 @@ async function typedFallback(question, io, defaultValue) {
|
|
|
75
75
|
readline.close();
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
+
async function typedChoice(question, choices, io, defaultIndex) {
|
|
79
|
+
const { createInterface } = await import("node:readline/promises");
|
|
80
|
+
const readline = createInterface({ input: io.input, output: io.output });
|
|
81
|
+
try {
|
|
82
|
+
const options = choices
|
|
83
|
+
.map((choice, index) => `${index + 1}: ${choice.label}`)
|
|
84
|
+
.join(", ");
|
|
85
|
+
const answer = (await readline.question(`${question} [${options}]: `)).trim();
|
|
86
|
+
if (answer === "") {
|
|
87
|
+
return choices[defaultIndex].value;
|
|
88
|
+
}
|
|
89
|
+
const index = Number.parseInt(answer, 10) - 1;
|
|
90
|
+
if (Number.isInteger(index) && choices[index] !== undefined) {
|
|
91
|
+
return choices[index].value;
|
|
92
|
+
}
|
|
93
|
+
const match = choices.find((choice) => choice.label.toLowerCase() === answer.toLowerCase());
|
|
94
|
+
return match?.value ?? choices[defaultIndex].value;
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
readline.close();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function renderChoices(outputLike, question, choices, selected, vertical = false, selectAll = false, selectAllLabel = "Select all", selectAllDescription, focusedIndex = 0) {
|
|
101
|
+
const renderChoice = (choice, active, focused) => {
|
|
102
|
+
const label = `${active ? DOT_ON : DOT_OFF} ${choice.label}`;
|
|
103
|
+
const cursor = focused ? "❯ " : " ";
|
|
104
|
+
const renderedLabel = active
|
|
105
|
+
? bold(outputLike, green(outputLike, label))
|
|
106
|
+
: dim(outputLike, label);
|
|
107
|
+
const lines = [
|
|
108
|
+
vertical
|
|
109
|
+
? `${dim(outputLike, RAIL)} ${cursor}${renderedLabel}`
|
|
110
|
+
: `${cursor}${renderedLabel}`
|
|
111
|
+
];
|
|
112
|
+
if (vertical && choice.description !== undefined) {
|
|
113
|
+
lines.push(`${dim(outputLike, RAIL)} ${dim(outputLike, choice.description)}`);
|
|
114
|
+
}
|
|
115
|
+
return lines;
|
|
116
|
+
};
|
|
117
|
+
const allActive = selectAll && selected.size === choices.length;
|
|
118
|
+
const renderedChoices = [
|
|
119
|
+
...(selectAll
|
|
120
|
+
? renderChoice({
|
|
121
|
+
label: selectAllLabel,
|
|
122
|
+
value: undefined,
|
|
123
|
+
description: selectAllDescription
|
|
124
|
+
}, allActive, selectAll && focusedIndex === 0)
|
|
125
|
+
: []),
|
|
126
|
+
...choices.flatMap((choice, index) => {
|
|
127
|
+
const active = selected.has(index);
|
|
128
|
+
const focused = (selectAll ? index + 1 : index) === focusedIndex;
|
|
129
|
+
return renderChoice(choice, active, focused);
|
|
130
|
+
})
|
|
131
|
+
];
|
|
132
|
+
const rendered = vertical
|
|
133
|
+
? renderedChoices
|
|
134
|
+
: [`${dim(outputLike, RAIL)} ${renderedChoices.join(" / ")}`];
|
|
135
|
+
return [
|
|
136
|
+
`${bold(outputLike, DIAMOND)} ${question}`,
|
|
137
|
+
...rendered
|
|
138
|
+
].join("\n");
|
|
139
|
+
}
|
|
140
|
+
function assertChoices(choices) {
|
|
141
|
+
if (choices.length === 0) {
|
|
142
|
+
throw new Error("At least one selector choice is required.");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/** Single-value selector for interactive wizard choices. */
|
|
146
|
+
export async function selectOption(question, choices, io, defaultIndex = 0) {
|
|
147
|
+
assertChoices(choices);
|
|
148
|
+
const initialIndex = Math.min(Math.max(defaultIndex, 0), choices.length - 1);
|
|
149
|
+
if (typeof io.input.setRawMode !== "function" ||
|
|
150
|
+
io.input.isTTY !== true) {
|
|
151
|
+
return await typedChoice(question, choices, io, initialIndex);
|
|
152
|
+
}
|
|
153
|
+
const outputLike = {
|
|
154
|
+
isTTY: true,
|
|
155
|
+
columns: io.output.columns,
|
|
156
|
+
write: (value) => io.output.write(value)
|
|
157
|
+
};
|
|
158
|
+
const { emitKeypressEvents } = await import("node:readline");
|
|
159
|
+
emitKeypressEvents(io.input);
|
|
160
|
+
io.input.setRawMode(true);
|
|
161
|
+
io.input.resume();
|
|
162
|
+
let index = initialIndex;
|
|
163
|
+
let rendered = renderChoices(outputLike, question, choices, new Set([index]), false, false, "Select all", undefined, index);
|
|
164
|
+
io.output.write(`${rendered}\n`);
|
|
165
|
+
return await new Promise((resolve) => {
|
|
166
|
+
const cleanup = () => {
|
|
167
|
+
io.input.setRawMode?.(false);
|
|
168
|
+
io.input.pause();
|
|
169
|
+
io.input.removeListener("keypress", onKeypress);
|
|
170
|
+
};
|
|
171
|
+
const redraw = () => {
|
|
172
|
+
eraseLines((value) => io.output.write(value), rendered.split("\n").length);
|
|
173
|
+
rendered = renderChoices(outputLike, question, choices, new Set([index]), false, false, "Select all", undefined, index);
|
|
174
|
+
io.output.write(`${rendered}\n`);
|
|
175
|
+
};
|
|
176
|
+
const move = (delta) => {
|
|
177
|
+
index = (index + delta + choices.length) % choices.length;
|
|
178
|
+
redraw();
|
|
179
|
+
};
|
|
180
|
+
const onKeypress = (_chunk, key) => {
|
|
181
|
+
if (key?.ctrl === true && key.name === "c") {
|
|
182
|
+
cleanup();
|
|
183
|
+
process.exit(130);
|
|
184
|
+
}
|
|
185
|
+
if (key?.name === "up" ||
|
|
186
|
+
key?.name === "left" ||
|
|
187
|
+
key?.name === "h") {
|
|
188
|
+
move(-1);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (key?.name === "down" ||
|
|
192
|
+
key?.name === "right" ||
|
|
193
|
+
key?.name === "tab" ||
|
|
194
|
+
key?.name === "l") {
|
|
195
|
+
move(1);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (key?.name === "home") {
|
|
199
|
+
index = 0;
|
|
200
|
+
redraw();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (key?.name === "end") {
|
|
204
|
+
index = choices.length - 1;
|
|
205
|
+
redraw();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (key?.name === "return" || key?.name === "space") {
|
|
209
|
+
cleanup();
|
|
210
|
+
resolve(choices[index].value);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
io.input.on("keypress", onKeypress);
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/** Multi-value selector. Space toggles a choice; Enter confirms. */
|
|
217
|
+
export async function selectOptions(question, choices, io, defaultValues = [], options = {}) {
|
|
218
|
+
assertChoices(choices);
|
|
219
|
+
const defaultIndexes = new Set(choices.flatMap((choice, index) => defaultValues.includes(choice.value) ? [index] : []));
|
|
220
|
+
if (defaultIndexes.size === 0 && options.selectAll !== true) {
|
|
221
|
+
defaultIndexes.add(0);
|
|
222
|
+
}
|
|
223
|
+
if (typeof io.input.setRawMode !== "function" ||
|
|
224
|
+
io.input.isTTY !== true) {
|
|
225
|
+
const value = await typedChoice(question, choices, io, 0);
|
|
226
|
+
return [value];
|
|
227
|
+
}
|
|
228
|
+
const selectAll = options.selectAll === true;
|
|
229
|
+
const choiceCount = choices.length + (selectAll ? 1 : 0);
|
|
230
|
+
const outputLike = {
|
|
231
|
+
isTTY: true,
|
|
232
|
+
columns: io.output.columns,
|
|
233
|
+
write: (value) => io.output.write(value)
|
|
234
|
+
};
|
|
235
|
+
const { emitKeypressEvents } = await import("node:readline");
|
|
236
|
+
emitKeypressEvents(io.input);
|
|
237
|
+
io.input.setRawMode(true);
|
|
238
|
+
io.input.resume();
|
|
239
|
+
let index = 0;
|
|
240
|
+
const selected = new Set(defaultIndexes);
|
|
241
|
+
let selectionHintShown = false;
|
|
242
|
+
let rendered = renderChoices(outputLike, question, choices, selected, true, selectAll, options.selectAllLabel, options.selectAllDescription, index);
|
|
243
|
+
io.output.write(`${rendered}\n`);
|
|
244
|
+
return await new Promise((resolve) => {
|
|
245
|
+
const cleanup = () => {
|
|
246
|
+
io.input.setRawMode?.(false);
|
|
247
|
+
io.input.pause();
|
|
248
|
+
io.input.removeListener("keypress", onKeypress);
|
|
249
|
+
};
|
|
250
|
+
const redraw = () => {
|
|
251
|
+
eraseLines((value) => io.output.write(value), rendered.split("\n").length);
|
|
252
|
+
selectionHintShown = false;
|
|
253
|
+
rendered = renderChoices(outputLike, question, choices, selected, true, selectAll, options.selectAllLabel, options.selectAllDescription, index);
|
|
254
|
+
io.output.write(`${rendered}\n`);
|
|
255
|
+
};
|
|
256
|
+
const move = (delta) => {
|
|
257
|
+
index = (index + delta + choiceCount) % choiceCount;
|
|
258
|
+
redraw();
|
|
259
|
+
};
|
|
260
|
+
const onKeypress = (_chunk, key) => {
|
|
261
|
+
if (key?.ctrl === true && key.name === "c") {
|
|
262
|
+
cleanup();
|
|
263
|
+
process.exit(130);
|
|
264
|
+
}
|
|
265
|
+
if (key?.name === "up" || key?.name === "left") {
|
|
266
|
+
move(-1);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (key?.name === "down" ||
|
|
270
|
+
key?.name === "right" ||
|
|
271
|
+
key?.name === "tab") {
|
|
272
|
+
move(1);
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (key?.name === "space") {
|
|
276
|
+
if (selectAll && index === 0) {
|
|
277
|
+
if (selected.size === choices.length) {
|
|
278
|
+
selected.clear();
|
|
279
|
+
selected.add(0);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
choices.forEach((_choice, choiceIndex) => {
|
|
283
|
+
selected.add(choiceIndex);
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
const choiceIndex = selectAll ? index - 1 : index;
|
|
289
|
+
if (selected.has(choiceIndex)) {
|
|
290
|
+
if (selected.size > 1) {
|
|
291
|
+
selected.delete(choiceIndex);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
selected.add(choiceIndex);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
redraw();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (key?.name === "return") {
|
|
302
|
+
if (selected.size === 0) {
|
|
303
|
+
if (!selectionHintShown) {
|
|
304
|
+
rendered = `${rendered}\n${dim(outputLike, `${RAIL} Choose at least one option with Space.`)}`;
|
|
305
|
+
selectionHintShown = true;
|
|
306
|
+
io.output.write(`${rendered}\n`);
|
|
307
|
+
}
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
cleanup();
|
|
311
|
+
resolve(choices
|
|
312
|
+
.filter((_choice, choiceIndex) => selected.has(choiceIndex))
|
|
313
|
+
.map((choice) => choice.value));
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
io.input.on("keypress", onKeypress);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
78
319
|
/**
|
|
79
320
|
* Arrow-key Yes/No selector. Falls back to a typed y/N prompt when stdin
|
|
80
321
|
* cannot enter raw mode (piped input, or a stub stream in tests), so the
|
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
import { CliArgumentError } from "./args.js";
|
|
2
|
+
import { selectOption, selectOptions } from "./ui.js";
|
|
2
3
|
const SCOPES = new Set(["project", "user"]);
|
|
3
4
|
const HARNESSES = new Set(["both", "claude", "codex"]);
|
|
4
5
|
const PROFILES = new Set(["advisory", "core", "guardrails"]);
|
|
6
|
+
const SCOPE_CHOICES = [
|
|
7
|
+
{ label: "project", value: "project" },
|
|
8
|
+
{ label: "user", value: "user" }
|
|
9
|
+
];
|
|
10
|
+
const HARNESS_CHOICES = [
|
|
11
|
+
{ label: "both", value: "both" },
|
|
12
|
+
{ label: "claude", value: "claude" },
|
|
13
|
+
{ label: "codex", value: "codex" }
|
|
14
|
+
];
|
|
15
|
+
const PROFILE_CHOICES = [
|
|
16
|
+
{
|
|
17
|
+
label: "core",
|
|
18
|
+
value: "core",
|
|
19
|
+
description: "Base rules, task tracking, verification, and review guidance."
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
label: "advisory",
|
|
23
|
+
value: "advisory",
|
|
24
|
+
description: "Adds informational SessionStart summaries and local logs; never blocks."
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
label: "guardrails",
|
|
28
|
+
value: "guardrails",
|
|
29
|
+
description: "Blocks high-confidence unsafe commands and enables optional Stop verification."
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
const WIZARD_SUBTITLE = "Safe setup for Codex + Claude Code with profile-driven rules, verification, and hooks.";
|
|
5
33
|
async function createPromptSession(io) {
|
|
6
34
|
if (!io.isTTY) {
|
|
7
35
|
throw new CliArgumentError("CLI_INTERACTIVE_REQUIRED", "Missing init choices require an interactive terminal.");
|
|
@@ -53,6 +81,28 @@ export async function completeInitChoices(args, io) {
|
|
|
53
81
|
if (!io.isTTY) {
|
|
54
82
|
throw new CliArgumentError("CLI_INTERACTIVE_REQUIRED", "Non-interactive init requires --scope, --harness, and at least one --profile.");
|
|
55
83
|
}
|
|
84
|
+
if (io.input !== undefined && io.output !== undefined) {
|
|
85
|
+
const selectorIo = {
|
|
86
|
+
input: io.input,
|
|
87
|
+
output: io.output
|
|
88
|
+
};
|
|
89
|
+
selectorIo.output.write(`${WIZARD_SUBTITLE}\n\n`);
|
|
90
|
+
const scope = args.scope ?? await selectOption("Scope", SCOPE_CHOICES, selectorIo);
|
|
91
|
+
const harness = args.harness ?? await selectOption("Harness", HARNESS_CHOICES, selectorIo);
|
|
92
|
+
const profiles = args.profiles.length > 0
|
|
93
|
+
? args.profiles
|
|
94
|
+
: await selectOptions("Profiles (multi-select: ↑↓ move, Space toggle, Enter confirm)", PROFILE_CHOICES, selectorIo, [], {
|
|
95
|
+
selectAll: true,
|
|
96
|
+
selectAllLabel: "Select all",
|
|
97
|
+
selectAllDescription: "Enable core, advisory, and guardrails together."
|
|
98
|
+
});
|
|
99
|
+
return {
|
|
100
|
+
...args,
|
|
101
|
+
scope,
|
|
102
|
+
harness,
|
|
103
|
+
profiles
|
|
104
|
+
};
|
|
105
|
+
}
|
|
56
106
|
const session = await createPromptSession(io);
|
|
57
107
|
try {
|
|
58
108
|
const scope = args.scope ??
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kylecheng3146/agent-ops",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Evidence-driven development loops for Codex and Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"docs/zh-TW/spec/",
|
|
20
20
|
"README.md",
|
|
21
21
|
"LICENSE",
|
|
22
|
-
"SECURITY.md"
|
|
22
|
+
"SECURITY.md",
|
|
23
|
+
"postinstall.cjs"
|
|
23
24
|
],
|
|
24
25
|
"publishConfig": {
|
|
25
26
|
"access": "public"
|
|
@@ -30,7 +31,8 @@
|
|
|
30
31
|
"test:compile": "node scripts/clean.mjs .tmp && tsc -p tsconfig.test.json",
|
|
31
32
|
"test": "npm run test:compile && node scripts/run-tests.mjs .tmp/test-dist/tests",
|
|
32
33
|
"build": "node scripts/clean.mjs dist && tsc -p tsconfig.build.json",
|
|
33
|
-
"package:check": "node scripts/package-check.mjs"
|
|
34
|
+
"package:check": "node scripts/package-check.mjs",
|
|
35
|
+
"postinstall": "node postinstall.cjs"
|
|
34
36
|
},
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@types/node": "26.1.1",
|
package/postinstall.cjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { closeSync, existsSync, openSync, readFileSync } = require("node:fs");
|
|
4
|
+
const { join, resolve } = require("node:path");
|
|
5
|
+
const { spawnSync } = require("node:child_process");
|
|
6
|
+
const { isatty } = require("node:tty");
|
|
7
|
+
|
|
8
|
+
const PACKAGE_NAME = "@kylecheng3146/agent-ops";
|
|
9
|
+
const packageRoot = __dirname;
|
|
10
|
+
const installRoot = resolve(process.env.INIT_CWD ?? process.cwd());
|
|
11
|
+
|
|
12
|
+
function isDirectDependency() {
|
|
13
|
+
try {
|
|
14
|
+
const packageJson = JSON.parse(
|
|
15
|
+
readFileSync(join(installRoot, "package.json"), "utf8")
|
|
16
|
+
);
|
|
17
|
+
return [
|
|
18
|
+
packageJson.dependencies,
|
|
19
|
+
packageJson.devDependencies,
|
|
20
|
+
packageJson.optionalDependencies,
|
|
21
|
+
packageJson.peerDependencies
|
|
22
|
+
].some(
|
|
23
|
+
(dependencies) =>
|
|
24
|
+
dependencies !== null &&
|
|
25
|
+
typeof dependencies === "object" &&
|
|
26
|
+
dependencies[PACKAGE_NAME] !== undefined
|
|
27
|
+
);
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function openTerminal() {
|
|
34
|
+
if (process.stdin.isTTY === true && process.stdout.isTTY === true) {
|
|
35
|
+
return { stdio: "inherit", close() {} };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const inputPath = process.platform === "win32" ? "CONIN$" : "/dev/tty";
|
|
39
|
+
const outputPath = process.platform === "win32" ? "CONOUT$" : "/dev/tty";
|
|
40
|
+
let inputFd;
|
|
41
|
+
let outputFd;
|
|
42
|
+
try {
|
|
43
|
+
inputFd = openSync(inputPath, "r");
|
|
44
|
+
outputFd = process.platform === "win32" ? openSync(outputPath, "a") : inputFd;
|
|
45
|
+
if (!isatty(inputFd) || !isatty(outputFd)) {
|
|
46
|
+
throw new Error("Interactive terminal is unavailable.");
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
stdio: [inputFd, outputFd, outputFd],
|
|
50
|
+
close() {
|
|
51
|
+
if (outputFd !== inputFd) {
|
|
52
|
+
closeSync(outputFd);
|
|
53
|
+
}
|
|
54
|
+
closeSync(inputFd);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
} catch {
|
|
58
|
+
if (outputFd !== undefined && outputFd !== inputFd) {
|
|
59
|
+
closeSync(outputFd);
|
|
60
|
+
}
|
|
61
|
+
if (inputFd !== undefined) {
|
|
62
|
+
closeSync(inputFd);
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (
|
|
69
|
+
installRoot === resolve(packageRoot) ||
|
|
70
|
+
process.env.CI !== undefined ||
|
|
71
|
+
!isDirectDependency()
|
|
72
|
+
) {
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const terminal = openTerminal();
|
|
77
|
+
if (terminal === null) {
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const cliPath = join(packageRoot, "dist", "packages", "cli", "src", "bin.js");
|
|
82
|
+
if (!existsSync(cliPath)) {
|
|
83
|
+
process.stderr.write(
|
|
84
|
+
"agent-ops init was skipped because the installed CLI is unavailable.\n"
|
|
85
|
+
);
|
|
86
|
+
terminal.close();
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result = spawnSync(process.execPath, [cliPath], {
|
|
91
|
+
cwd: installRoot,
|
|
92
|
+
env: process.env,
|
|
93
|
+
stdio: terminal.stdio
|
|
94
|
+
});
|
|
95
|
+
terminal.close();
|
|
96
|
+
|
|
97
|
+
if (result.error !== undefined || result.status !== 0) {
|
|
98
|
+
process.stderr.write(
|
|
99
|
+
"agent-ops init was not applied; package installation completed. " +
|
|
100
|
+
"Run `agent-ops` to retry.\n"
|
|
101
|
+
);
|
|
102
|
+
}
|