@nyxa/automation 0.1.0 → 0.1.2
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 +75 -38
- package/dist/cli.js +69 -35
- package/dist/init.js +106 -49
- package/dist/kimi-code.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,52 +17,75 @@ Their authentication, subscriptions, and quotas remain the user's
|
|
|
17
17
|
responsibility; Automation does not manage provider accounts, credentials,
|
|
18
18
|
billing, or limits.
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Initialize a project
|
|
21
|
+
|
|
22
|
+
Run `init` from any project directory. The host project does not need to be a
|
|
23
|
+
Node.js package and its root files are not modified. Automation creates a
|
|
24
|
+
private environment under `.automation/` and refuses to overwrite an existing
|
|
25
|
+
Workflow:
|
|
21
26
|
|
|
22
27
|
```sh
|
|
23
|
-
|
|
28
|
+
npx @nyxa/automation init first.ts
|
|
24
29
|
```
|
|
25
30
|
|
|
26
|
-
The
|
|
27
|
-
global TypeScript or `tsx` installation to run a Workflow.
|
|
28
|
-
|
|
29
|
-
## Initialize an existing project
|
|
30
|
-
|
|
31
|
-
Run `init` from a project that already has a `package.json`. It refuses to
|
|
32
|
-
overwrite the requested Workflow and detects npm, pnpm, or Yarn from the
|
|
33
|
-
project lockfile before offering to add Automation as a development dependency:
|
|
31
|
+
The generated layout is:
|
|
34
32
|
|
|
35
|
-
```
|
|
36
|
-
|
|
33
|
+
```text
|
|
34
|
+
.automation/
|
|
35
|
+
├── .gitignore
|
|
36
|
+
├── package.json
|
|
37
|
+
├── tsconfig.json
|
|
38
|
+
└── workflows/
|
|
39
|
+
└── first.ts
|
|
37
40
|
```
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
The private package declares `@nyxa/automation`, TypeScript, and the Node.js
|
|
43
|
+
types. Together with the generated `tsconfig.json`, this lets editors resolve
|
|
44
|
+
Workflow imports and provide completion and inferred types without turning the
|
|
45
|
+
host project into a Node.js package. Automation always uses npm inside this
|
|
46
|
+
private environment, independently of any npm, pnpm, Yarn, or workspace
|
|
47
|
+
configuration in the host project. Dependencies and `package-lock.json` stay
|
|
48
|
+
inside `.automation/`; its `.gitignore` keeps `node_modules/` out of the host
|
|
49
|
+
repository.
|
|
50
|
+
|
|
51
|
+
Choose the Harness explicitly for non-interactive use. `--no-install` generates
|
|
52
|
+
the private package, TypeScript configuration, and Workflow without installing
|
|
53
|
+
their dependencies. `--yes` accepts installation and, when `--harness` is
|
|
41
54
|
omitted, selects a Harness only if exactly one local Harness is operational.
|
|
42
55
|
|
|
43
56
|
```sh
|
|
44
|
-
npx @nyxa/automation init
|
|
45
|
-
npx @nyxa/automation init
|
|
46
|
-
npx @nyxa/automation init
|
|
47
|
-
npx @nyxa/automation init
|
|
57
|
+
npx @nyxa/automation init codex.ts --harness codex --yes
|
|
58
|
+
npx @nyxa/automation init claude.ts --harness claude-code --no-install
|
|
59
|
+
npx @nyxa/automation init kimi.ts --harness kimi-code --no-install
|
|
60
|
+
npx @nyxa/automation init opencode.ts --harness opencode --no-install
|
|
48
61
|
```
|
|
49
62
|
|
|
50
63
|
The generated Workflow imports only the selected Harness, requests a documented
|
|
51
64
|
structured result, and handles a `needs_input` result with ordinary TypeScript
|
|
52
|
-
control flow.
|
|
65
|
+
control flow. The package includes its TypeScript runtime loader, so no global
|
|
66
|
+
TypeScript or `tsx` installation is required to run a Workflow.
|
|
67
|
+
|
|
68
|
+
The Automation Environment pins the exact Automation version that created it.
|
|
69
|
+
Later `init` commands delegate to that installed version instead of silently
|
|
70
|
+
changing ownership. Upgrade the environment explicitly before using a newer
|
|
71
|
+
`init` contract:
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
npm --prefix .automation install --save-dev --save-exact @nyxa/automation@latest
|
|
75
|
+
```
|
|
53
76
|
|
|
54
77
|
## Diagnose the local environment
|
|
55
78
|
|
|
56
|
-
Run `doctor` before a Workflow to check Node.js, the
|
|
57
|
-
|
|
58
|
-
Harnesses:
|
|
79
|
+
Run `doctor` before a Workflow to check Node.js, the Automation package installed
|
|
80
|
+
inside `.automation/`, and the installed Codex, Claude Code, Kimi Code, and
|
|
81
|
+
OpenCode Harnesses:
|
|
59
82
|
|
|
60
83
|
```sh
|
|
61
|
-
npx automation doctor
|
|
62
|
-
npx automation doctor --harness codex
|
|
63
|
-
npx automation doctor --harness claude-code
|
|
64
|
-
npx automation doctor --harness kimi-code
|
|
65
|
-
npx automation doctor --harness opencode
|
|
84
|
+
npx @nyxa/automation doctor
|
|
85
|
+
npx @nyxa/automation doctor --harness codex
|
|
86
|
+
npx @nyxa/automation doctor --harness claude-code
|
|
87
|
+
npx @nyxa/automation doctor --harness kimi-code
|
|
88
|
+
npx @nyxa/automation doctor --harness opencode
|
|
66
89
|
```
|
|
67
90
|
|
|
68
91
|
The command checks Harness versions and verifies authentication headlessly.
|
|
@@ -75,10 +98,20 @@ Without a filter, one fully operational Harness is sufficient and missing
|
|
|
75
98
|
Harnesses are reported as warnings. With `--harness`, the requested Harness
|
|
76
99
|
must be fully operational.
|
|
77
100
|
|
|
101
|
+
## Install in a Node.js package
|
|
102
|
+
|
|
103
|
+
The isolated Automation Environment is sufficient for CLI Workflows. Install
|
|
104
|
+
the package in the host project as well only when that project's own Node.js
|
|
105
|
+
modules import the programmatic interface directly:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
npm install --save-dev @nyxa/automation
|
|
109
|
+
```
|
|
110
|
+
|
|
78
111
|
## Define a Workflow
|
|
79
112
|
|
|
80
113
|
```ts
|
|
81
|
-
//
|
|
114
|
+
// .automation/workflows/first.ts
|
|
82
115
|
import { defineWorkflow } from "@nyxa/automation";
|
|
83
116
|
|
|
84
117
|
const workflow = defineWorkflow({
|
|
@@ -95,7 +128,7 @@ always explicit:
|
|
|
95
128
|
|
|
96
129
|
```ts
|
|
97
130
|
import { executeWorkflow } from "@nyxa/automation";
|
|
98
|
-
import workflow from "
|
|
131
|
+
import workflow from "./.automation/workflows/first.js";
|
|
99
132
|
|
|
100
133
|
const result = await executeWorkflow(workflow);
|
|
101
134
|
// result is inferred as { status: string; count: number }
|
|
@@ -311,7 +344,7 @@ await executeWorkflow(workflow, { concurrency: 2 });
|
|
|
311
344
|
```
|
|
312
345
|
|
|
313
346
|
```sh
|
|
314
|
-
npx automation
|
|
347
|
+
npx @nyxa/automation first.ts --concurrency 2
|
|
315
348
|
```
|
|
316
349
|
|
|
317
350
|
Several `read` Runs can share a Workspace within that limit. A
|
|
@@ -466,7 +499,7 @@ await executeWorkflow(workflow, { record: false });
|
|
|
466
499
|
```
|
|
467
500
|
|
|
468
501
|
```sh
|
|
469
|
-
npx automation
|
|
502
|
+
npx @nyxa/automation first.ts --no-record
|
|
470
503
|
```
|
|
471
504
|
|
|
472
505
|
On Linux, journals use `$XDG_STATE_HOME/automation/run-journals` when that
|
|
@@ -504,27 +537,31 @@ const result = await executeWorkflow(workflow, {
|
|
|
504
537
|
|
|
505
538
|
## Run from the CLI
|
|
506
539
|
|
|
507
|
-
The direct and explicit forms are equivalent
|
|
540
|
+
The direct and explicit forms are equivalent. `npx @nyxa/automation` bootstraps
|
|
541
|
+
the command and delegates Workflow execution to the version installed inside
|
|
542
|
+
`.automation/`. A simple name resolves from `.automation/workflows/`; an
|
|
543
|
+
existing explicit path remains usable:
|
|
508
544
|
|
|
509
545
|
```sh
|
|
510
|
-
npx automation
|
|
511
|
-
npx automation run
|
|
546
|
+
npx @nyxa/automation first.ts
|
|
547
|
+
npx @nyxa/automation run first.ts
|
|
548
|
+
npx @nyxa/automation .automation/workflows/first.ts
|
|
512
549
|
```
|
|
513
550
|
|
|
514
551
|
Provide a declared Workflow Input as inline JSON, from a file, or from standard
|
|
515
552
|
input. These sources are mutually exclusive.
|
|
516
553
|
|
|
517
554
|
```sh
|
|
518
|
-
npx automation
|
|
519
|
-
npx automation
|
|
520
|
-
printf '%s' '{"name":"Nyxa"}' | npx automation
|
|
555
|
+
npx @nyxa/automation first.ts --input '{"name":"Nyxa"}'
|
|
556
|
+
npx @nyxa/automation first.ts --input-file input.json
|
|
557
|
+
printf '%s' '{"name":"Nyxa"}' | npx @nyxa/automation first.ts --input -
|
|
521
558
|
```
|
|
522
559
|
|
|
523
560
|
Use `--json` for a JSONL stream containing only Automation Events on `stdout`.
|
|
524
561
|
The terminal `workflow.completed` event carries the Workflow Result.
|
|
525
562
|
|
|
526
563
|
```sh
|
|
527
|
-
npx automation
|
|
564
|
+
npx @nyxa/automation first.ts --json
|
|
528
565
|
```
|
|
529
566
|
|
|
530
567
|
Strings are written to `stdout` as-is, other JSON values are serialized, and
|
package/dist/cli.js
CHANGED
|
@@ -22,12 +22,12 @@ const HELP = `Usage: automation <workflow> [options]
|
|
|
22
22
|
automation <command>
|
|
23
23
|
|
|
24
24
|
Commands:
|
|
25
|
-
init Initialize
|
|
25
|
+
init Initialize a project-local .automation environment
|
|
26
26
|
doctor Diagnose the local Automation environment
|
|
27
27
|
|
|
28
28
|
Options:
|
|
29
29
|
--harness <name> Select codex, claude-code, kimi-code, or opencode for init or doctor
|
|
30
|
-
--no-install Generate
|
|
30
|
+
--no-install Generate the environment without installing dependencies
|
|
31
31
|
--yes Accept init installation and auto-select one ready Harness
|
|
32
32
|
--input <json|-> Read Workflow Input from inline JSON or stdin
|
|
33
33
|
--input-file <path> Read Workflow Input from a JSON file
|
|
@@ -39,6 +39,8 @@ Options:
|
|
|
39
39
|
`;
|
|
40
40
|
async function main(arguments_) {
|
|
41
41
|
const invocation = classifyInvocation(arguments_);
|
|
42
|
+
let parsedArguments;
|
|
43
|
+
let resolvedWorkflowPath;
|
|
42
44
|
if (invocation === "help") {
|
|
43
45
|
writeSync(process.stdout.fd, HELP);
|
|
44
46
|
return SUCCESS;
|
|
@@ -56,7 +58,7 @@ async function main(arguments_) {
|
|
|
56
58
|
if (invocation === "doctor") {
|
|
57
59
|
try {
|
|
58
60
|
const harness = parseDoctorArguments(arguments_.slice(1));
|
|
59
|
-
const localPackage = resolveProjectLocalPackage(process.cwd());
|
|
61
|
+
const localPackage = resolveProjectLocalPackage(path.join(process.cwd(), ".automation"));
|
|
60
62
|
const result = await diagnoseEnvironment({
|
|
61
63
|
environment: process.env,
|
|
62
64
|
...(harness === undefined ? {} : { harness }),
|
|
@@ -79,6 +81,15 @@ async function main(arguments_) {
|
|
|
79
81
|
}
|
|
80
82
|
if (invocation === "init") {
|
|
81
83
|
try {
|
|
84
|
+
const localPackage = resolveProjectLocalPackage(path.join(process.cwd(), ".automation"));
|
|
85
|
+
if (localPackage !== undefined) {
|
|
86
|
+
if (!localPackage.binaryAvailable) {
|
|
87
|
+
throw new Error(`The project-local ${PACKAGE_NAME} package is missing its automation binary. Reinstall the .automation environment before running init.`);
|
|
88
|
+
}
|
|
89
|
+
if (!isCurrentCli(localPackage.cliPath)) {
|
|
90
|
+
return await delegateToLocalCli(localPackage.cliPath, arguments_);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
82
93
|
const output = await initializeProject(arguments_.slice(1));
|
|
83
94
|
writeSync(process.stdout.fd, output);
|
|
84
95
|
return SUCCESS;
|
|
@@ -90,9 +101,11 @@ async function main(arguments_) {
|
|
|
90
101
|
}
|
|
91
102
|
if (invocation === "workflow") {
|
|
92
103
|
try {
|
|
93
|
-
|
|
104
|
+
parsedArguments = parseArguments(arguments_);
|
|
105
|
+
resolvedWorkflowPath = resolveWorkflowModulePath(process.cwd(), parsedArguments.workflowPath);
|
|
106
|
+
const localPackage = resolveProjectLocalPackage(path.dirname(resolvedWorkflowPath));
|
|
94
107
|
if (localPackage === undefined) {
|
|
95
|
-
throw new Error(`A project-local installation of ${PACKAGE_NAME} is required to execute a Workflow.
|
|
108
|
+
throw new Error(`A project-local installation of ${PACKAGE_NAME} is required to execute a Workflow. Run npx @nyxa/automation init <workflow> and install the .automation environment dependencies first.`);
|
|
96
109
|
}
|
|
97
110
|
if (!localPackage.binaryAvailable) {
|
|
98
111
|
throw new Error(`The project-local ${PACKAGE_NAME} package is missing its automation binary. Reinstall it before running a Workflow.`);
|
|
@@ -106,10 +119,9 @@ async function main(arguments_) {
|
|
|
106
119
|
return PRE_EXECUTION_ERROR;
|
|
107
120
|
}
|
|
108
121
|
}
|
|
109
|
-
let parsedArguments;
|
|
110
122
|
let loadedInput;
|
|
111
123
|
try {
|
|
112
|
-
parsedArguments
|
|
124
|
+
parsedArguments ??= parseArguments(arguments_);
|
|
113
125
|
loadedInput = await loadWorkflowInput(parsedArguments.inputSource);
|
|
114
126
|
}
|
|
115
127
|
catch (error) {
|
|
@@ -118,7 +130,8 @@ async function main(arguments_) {
|
|
|
118
130
|
}
|
|
119
131
|
let workflowModule;
|
|
120
132
|
try {
|
|
121
|
-
const workflowUrl = pathToFileURL(
|
|
133
|
+
const workflowUrl = pathToFileURL(resolvedWorkflowPath ??
|
|
134
|
+
resolveWorkflowModulePath(process.cwd(), parsedArguments.workflowPath));
|
|
122
135
|
workflowModule = (await tsImport(workflowUrl.href, import.meta.url));
|
|
123
136
|
}
|
|
124
137
|
catch (cause) {
|
|
@@ -235,20 +248,26 @@ function ownPackageVersion() {
|
|
|
235
248
|
}
|
|
236
249
|
function resolveProjectLocalPackage(invocationDirectory) {
|
|
237
250
|
let directory = path.resolve(invocationDirectory);
|
|
238
|
-
let dependencyDeclared = false;
|
|
239
251
|
while (true) {
|
|
240
252
|
const projectManifestPath = path.join(directory, "package.json");
|
|
253
|
+
let projectManifest;
|
|
241
254
|
try {
|
|
242
|
-
|
|
243
|
-
dependencyDeclared ||= hasAutomationDevelopmentDependency(projectManifest);
|
|
255
|
+
projectManifest = readPackageManifest(projectManifestPath);
|
|
244
256
|
}
|
|
245
257
|
catch (error) {
|
|
246
258
|
if (!isMissingFileError(error)) {
|
|
247
259
|
throw error;
|
|
248
260
|
}
|
|
249
261
|
}
|
|
250
|
-
const
|
|
251
|
-
|
|
262
|
+
const declaredVersion = projectManifest?.devDependencies?.[PACKAGE_NAME];
|
|
263
|
+
const isAutomationEnvironment = path.basename(directory) === ".automation";
|
|
264
|
+
if (projectManifest !== undefined &&
|
|
265
|
+
isAutomationEnvironment &&
|
|
266
|
+
typeof declaredVersion !== "string") {
|
|
267
|
+
throw new Error(`The Automation Environment at ${projectManifestPath} does not declare ${PACKAGE_NAME} as a development dependency.`);
|
|
268
|
+
}
|
|
269
|
+
if (typeof declaredVersion === "string") {
|
|
270
|
+
const manifestPath = path.join(directory, "node_modules", "@nyxa", "automation", "package.json");
|
|
252
271
|
let manifest;
|
|
253
272
|
try {
|
|
254
273
|
manifest = readPackageManifest(manifestPath);
|
|
@@ -258,26 +277,31 @@ function resolveProjectLocalPackage(invocationDirectory) {
|
|
|
258
277
|
throw error;
|
|
259
278
|
}
|
|
260
279
|
}
|
|
261
|
-
if (manifest
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
binaryAvailable: isRegularFile(cliPath),
|
|
277
|
-
cliPath,
|
|
278
|
-
version: manifest.version,
|
|
279
|
-
};
|
|
280
|
+
if (manifest === undefined) {
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
if (manifest.name !== PACKAGE_NAME) {
|
|
284
|
+
throw new Error(`The project-local package at ${manifestPath} is not ${PACKAGE_NAME}.`);
|
|
285
|
+
}
|
|
286
|
+
const binary = typeof manifest.bin === "string"
|
|
287
|
+
? manifest.bin
|
|
288
|
+
: manifest.bin?.automation;
|
|
289
|
+
if (typeof binary !== "string") {
|
|
290
|
+
throw new Error(`The project-local ${PACKAGE_NAME} package does not expose the automation binary.`);
|
|
291
|
+
}
|
|
292
|
+
if (typeof manifest.version !== "string") {
|
|
293
|
+
throw new Error(`The project-local ${PACKAGE_NAME} package has no valid version.`);
|
|
280
294
|
}
|
|
295
|
+
if (isAutomationEnvironment &&
|
|
296
|
+
manifest.version !== declaredVersion) {
|
|
297
|
+
throw new Error(`The Automation Environment declares ${PACKAGE_NAME} ${declaredVersion}, but its installed package is ${manifest.version}. Run npm install inside .automation before continuing.`);
|
|
298
|
+
}
|
|
299
|
+
const cliPath = path.resolve(path.dirname(manifestPath), binary);
|
|
300
|
+
return {
|
|
301
|
+
binaryAvailable: isRegularFile(cliPath),
|
|
302
|
+
cliPath,
|
|
303
|
+
version: manifest.version,
|
|
304
|
+
};
|
|
281
305
|
}
|
|
282
306
|
const parent = path.dirname(directory);
|
|
283
307
|
if (parent === directory) {
|
|
@@ -286,6 +310,19 @@ function resolveProjectLocalPackage(invocationDirectory) {
|
|
|
286
310
|
directory = parent;
|
|
287
311
|
}
|
|
288
312
|
}
|
|
313
|
+
function resolveWorkflowModulePath(invocationDirectory, requestedPath) {
|
|
314
|
+
const explicitPath = path.resolve(invocationDirectory, requestedPath);
|
|
315
|
+
if (isRegularFile(explicitPath)) {
|
|
316
|
+
return explicitPath;
|
|
317
|
+
}
|
|
318
|
+
const environmentDirectory = path.join(invocationDirectory, ".automation");
|
|
319
|
+
const environmentWorkflowPath = path.resolve(environmentDirectory, "workflows", requestedPath);
|
|
320
|
+
if (isRegularFile(environmentWorkflowPath) ||
|
|
321
|
+
isRegularFile(path.join(environmentDirectory, "package.json"))) {
|
|
322
|
+
return environmentWorkflowPath;
|
|
323
|
+
}
|
|
324
|
+
return explicitPath;
|
|
325
|
+
}
|
|
289
326
|
function isRegularFile(filePath) {
|
|
290
327
|
try {
|
|
291
328
|
return statSync(filePath).isFile();
|
|
@@ -297,9 +334,6 @@ function isRegularFile(filePath) {
|
|
|
297
334
|
throw error;
|
|
298
335
|
}
|
|
299
336
|
}
|
|
300
|
-
function hasAutomationDevelopmentDependency(manifest) {
|
|
301
|
-
return typeof manifest.devDependencies?.[PACKAGE_NAME] === "string";
|
|
302
|
-
}
|
|
303
337
|
function readPackageManifest(manifestPath) {
|
|
304
338
|
const serialized = readFileSync(manifestPath, "utf8");
|
|
305
339
|
try {
|
package/dist/init.js
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
import { access, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
3
4
|
import { diagnoseEnvironment } from "./doctor.js";
|
|
4
5
|
import { isMissingFileError } from "./filesystem.js";
|
|
5
6
|
import { readStandardInput } from "./standard-input.js";
|
|
7
|
+
const AUTOMATION_DIRECTORY = ".automation";
|
|
8
|
+
const WORKFLOW_DIRECTORY = "workflows";
|
|
6
9
|
export async function initializeProject(arguments_, invocationDirectory = process.cwd()) {
|
|
7
10
|
const parsed = parseInitArguments(arguments_);
|
|
8
11
|
const prompt = !parsed.yes && (parsed.harness === undefined || parsed.install)
|
|
9
12
|
? await createPrompt()
|
|
10
13
|
: undefined;
|
|
11
|
-
const manifestPath = path.join(invocationDirectory, "package.json");
|
|
12
14
|
try {
|
|
13
|
-
try {
|
|
14
|
-
JSON.parse(await readFile(manifestPath, "utf8"));
|
|
15
|
-
}
|
|
16
|
-
catch (error) {
|
|
17
|
-
if (isMissingFileError(error)) {
|
|
18
|
-
throw new Error("init requires a package.json from an existing npm project.");
|
|
19
|
-
}
|
|
20
|
-
throw error;
|
|
21
|
-
}
|
|
22
15
|
const harness = await resolveHarness(parsed, invocationDirectory, prompt);
|
|
23
16
|
const install = await resolveInstall(parsed, prompt);
|
|
24
|
-
const
|
|
17
|
+
const environmentDirectory = path.join(invocationDirectory, AUTOMATION_DIRECTORY);
|
|
18
|
+
const workflowRoot = path.join(environmentDirectory, WORKFLOW_DIRECTORY);
|
|
19
|
+
const workflowPath = resolveWorkflowPath(workflowRoot, parsed.workflowPath);
|
|
25
20
|
if (await pathExists(workflowPath)) {
|
|
26
21
|
throw workflowExistsError(parsed.workflowPath);
|
|
27
22
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
: undefined;
|
|
23
|
+
await initializeAutomationEnvironment(environmentDirectory);
|
|
24
|
+
const packageManager = install ? PACKAGE_MANAGER : undefined;
|
|
31
25
|
if (packageManager !== undefined) {
|
|
32
|
-
await
|
|
26
|
+
await installAutomationDependencies(packageManager, environmentDirectory);
|
|
33
27
|
}
|
|
34
28
|
await mkdir(path.dirname(workflowPath), { recursive: true });
|
|
35
29
|
try {
|
|
@@ -44,9 +38,10 @@ export async function initializeProject(arguments_, invocationDirectory = proces
|
|
|
44
38
|
}
|
|
45
39
|
throw error;
|
|
46
40
|
}
|
|
47
|
-
|
|
41
|
+
const displayedWorkflowPath = path.relative(invocationDirectory, workflowPath);
|
|
42
|
+
let output = `Created ${displayedWorkflowPath}.\n`;
|
|
48
43
|
if (packageManager !== undefined) {
|
|
49
|
-
output += `Installed
|
|
44
|
+
output += `Installed the Automation environment with ${packageManager.displayName}.\n`;
|
|
50
45
|
}
|
|
51
46
|
return output;
|
|
52
47
|
}
|
|
@@ -101,7 +96,7 @@ async function resolveInstall(arguments_, prompt) {
|
|
|
101
96
|
if (!arguments_.install || arguments_.yes) {
|
|
102
97
|
return arguments_.install;
|
|
103
98
|
}
|
|
104
|
-
const answer = await requirePrompt(prompt).question("Install
|
|
99
|
+
const answer = await requirePrompt(prompt).question("Install the Automation environment dependencies? [Y/n] ");
|
|
105
100
|
if (answer === "" || answer === "y" || answer === "yes") {
|
|
106
101
|
return true;
|
|
107
102
|
}
|
|
@@ -145,39 +140,16 @@ function requirePrompt(prompt) {
|
|
|
145
140
|
}
|
|
146
141
|
return prompt;
|
|
147
142
|
}
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
displayName: "pnpm",
|
|
157
|
-
executable: "pnpm",
|
|
158
|
-
installArguments: ["add", "--save-dev", "@nyxa/automation"],
|
|
159
|
-
lockfile: "pnpm-lock.yaml",
|
|
160
|
-
},
|
|
161
|
-
{
|
|
162
|
-
displayName: "Yarn",
|
|
163
|
-
executable: "yarn",
|
|
164
|
-
installArguments: ["add", "--dev", "@nyxa/automation"],
|
|
165
|
-
lockfile: "yarn.lock",
|
|
166
|
-
},
|
|
167
|
-
];
|
|
168
|
-
async function detectPackageManager(invocationDirectory) {
|
|
169
|
-
for (const packageManager of PACKAGE_MANAGERS) {
|
|
170
|
-
if (await pathExists(path.join(invocationDirectory, packageManager.lockfile))) {
|
|
171
|
-
return packageManager;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
throw new Error("Could not detect npm, pnpm, or Yarn. Add the package manager lockfile first.");
|
|
175
|
-
}
|
|
176
|
-
async function installAutomationDependency(packageManager, invocationDirectory) {
|
|
143
|
+
const PACKAGE_MANAGER = {
|
|
144
|
+
displayName: "npm",
|
|
145
|
+
executable: "npm",
|
|
146
|
+
installArguments: ["install"],
|
|
147
|
+
};
|
|
148
|
+
async function installAutomationDependencies(packageManager, environmentDirectory) {
|
|
177
149
|
const { spawn } = await import("node:child_process");
|
|
178
150
|
const exitCode = await new Promise((resolve, reject) => {
|
|
179
151
|
const child = spawn(packageManager.executable, packageManager.installArguments, {
|
|
180
|
-
cwd:
|
|
152
|
+
cwd: environmentDirectory,
|
|
181
153
|
env: process.env,
|
|
182
154
|
stdio: "inherit",
|
|
183
155
|
});
|
|
@@ -185,8 +157,93 @@ async function installAutomationDependency(packageManager, invocationDirectory)
|
|
|
185
157
|
child.once("exit", (code) => resolve(code ?? 1));
|
|
186
158
|
});
|
|
187
159
|
if (exitCode !== 0) {
|
|
188
|
-
throw new Error(`${packageManager.displayName} could not install
|
|
160
|
+
throw new Error(`${packageManager.displayName} could not install the Automation environment (exit ${String(exitCode)}).`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
async function initializeAutomationEnvironment(environmentDirectory) {
|
|
164
|
+
await mkdir(path.join(environmentDirectory, WORKFLOW_DIRECTORY), {
|
|
165
|
+
recursive: true,
|
|
166
|
+
});
|
|
167
|
+
const manifestPath = path.join(environmentDirectory, "package.json");
|
|
168
|
+
const ownManifestPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
169
|
+
const ownManifest = parsePackageManifest(await readFile(ownManifestPath, "utf8"), ownManifestPath);
|
|
170
|
+
if (typeof ownManifest.version !== "string") {
|
|
171
|
+
throw new Error("The installed @nyxa/automation package has no version.");
|
|
172
|
+
}
|
|
173
|
+
if (await pathExists(manifestPath)) {
|
|
174
|
+
const manifest = parsePackageManifest(await readFile(manifestPath, "utf8"), manifestPath);
|
|
175
|
+
const declaredVersion = manifest.devDependencies?.["@nyxa/automation"];
|
|
176
|
+
if (typeof declaredVersion !== "string") {
|
|
177
|
+
throw new Error(`${manifestPath} does not declare @nyxa/automation as a development dependency.`);
|
|
178
|
+
}
|
|
179
|
+
if (declaredVersion !== ownManifest.version) {
|
|
180
|
+
throw new Error(`The Automation Environment is owned by @nyxa/automation ${declaredVersion}, but init is running ${ownManifest.version}. Install the environment dependencies or update its @nyxa/automation version explicitly before running init again.`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
await writeFile(manifestPath, `${JSON.stringify({
|
|
185
|
+
name: "automation-workflows",
|
|
186
|
+
private: true,
|
|
187
|
+
type: "module",
|
|
188
|
+
devDependencies: {
|
|
189
|
+
"@nyxa/automation": ownManifest.version,
|
|
190
|
+
"@types/node": dependencyVersion(ownManifest, "@types/node"),
|
|
191
|
+
typescript: dependencyVersion(ownManifest, "typescript"),
|
|
192
|
+
},
|
|
193
|
+
}, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
194
|
+
}
|
|
195
|
+
const tsconfigPath = path.join(environmentDirectory, "tsconfig.json");
|
|
196
|
+
if (!(await pathExists(tsconfigPath))) {
|
|
197
|
+
await writeFile(tsconfigPath, `${JSON.stringify({
|
|
198
|
+
compilerOptions: {
|
|
199
|
+
target: "ES2023",
|
|
200
|
+
module: "NodeNext",
|
|
201
|
+
moduleResolution: "NodeNext",
|
|
202
|
+
types: ["node"],
|
|
203
|
+
strict: true,
|
|
204
|
+
exactOptionalPropertyTypes: true,
|
|
205
|
+
noUncheckedIndexedAccess: true,
|
|
206
|
+
noEmit: true,
|
|
207
|
+
skipLibCheck: true,
|
|
208
|
+
},
|
|
209
|
+
include: ["workflows/**/*.ts"],
|
|
210
|
+
}, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
211
|
+
}
|
|
212
|
+
const gitignorePath = path.join(environmentDirectory, ".gitignore");
|
|
213
|
+
if (!(await pathExists(gitignorePath))) {
|
|
214
|
+
await writeFile(gitignorePath, "node_modules/\n", {
|
|
215
|
+
encoding: "utf8",
|
|
216
|
+
flag: "wx",
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function parsePackageManifest(serialized, manifestPath) {
|
|
221
|
+
try {
|
|
222
|
+
return JSON.parse(serialized);
|
|
223
|
+
}
|
|
224
|
+
catch (cause) {
|
|
225
|
+
throw new Error(`Could not parse package manifest at ${manifestPath}.`, {
|
|
226
|
+
cause,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function dependencyVersion(manifest, dependency) {
|
|
231
|
+
const version = manifest.devDependencies?.[dependency];
|
|
232
|
+
if (typeof version !== "string") {
|
|
233
|
+
throw new Error(`The installed @nyxa/automation package does not declare ${dependency}.`);
|
|
234
|
+
}
|
|
235
|
+
return version;
|
|
236
|
+
}
|
|
237
|
+
function resolveWorkflowPath(workflowRoot, requestedPath) {
|
|
238
|
+
const workflowPath = path.resolve(workflowRoot, requestedPath);
|
|
239
|
+
const relativePath = path.relative(workflowRoot, workflowPath);
|
|
240
|
+
if (relativePath === "" ||
|
|
241
|
+
relativePath === ".." ||
|
|
242
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
243
|
+
path.isAbsolute(relativePath)) {
|
|
244
|
+
throw new Error("The Workflow path must stay inside .automation/workflows.");
|
|
189
245
|
}
|
|
246
|
+
return workflowPath;
|
|
190
247
|
}
|
|
191
248
|
async function pathExists(filePath) {
|
|
192
249
|
try {
|
package/dist/kimi-code.js
CHANGED
|
@@ -188,7 +188,7 @@ function createKimiConnection(input, output, options) {
|
|
|
188
188
|
async function initializeAndAuthenticate(connection) {
|
|
189
189
|
const initialized = await connection.initialize({
|
|
190
190
|
clientCapabilities: {},
|
|
191
|
-
clientInfo: { name: "@nyxa/automation", version: "0.1.
|
|
191
|
+
clientInfo: { name: "@nyxa/automation", version: "0.1.2" },
|
|
192
192
|
protocolVersion: PROTOCOL_VERSION,
|
|
193
193
|
});
|
|
194
194
|
if (initialized.protocolVersion !== PROTOCOL_VERSION) {
|