@nyxa/automation 0.1.1 → 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 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
- ## Install
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
- npm install --save-dev @nyxa/automation
28
+ npx @nyxa/automation init first.ts
24
29
  ```
25
30
 
26
- The package includes its TypeScript runtime loader. A project does not need a
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
- ```sh
36
- npx @nyxa/automation init workflows/first.ts
33
+ ```text
34
+ .automation/
35
+ ├── .gitignore
36
+ ├── package.json
37
+ ├── tsconfig.json
38
+ └── workflows/
39
+ └── first.ts
37
40
  ```
38
41
 
39
- Choose the Harness explicitly for non-interactive use. `--no-install` only
40
- generates the Workflow; `--yes` accepts installation and, when `--harness` is
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 workflows/codex.ts --harness codex --yes
45
- npx @nyxa/automation init workflows/claude.ts --harness claude-code --no-install
46
- npx @nyxa/automation init workflows/kimi.ts --harness kimi-code --no-install
47
- npx @nyxa/automation init workflows/opencode.ts --harness opencode --no-install
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 project-local Automation
57
- package, and the installed Codex, Claude Code, Kimi Code, and OpenCode
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
- // workflow.ts
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 "./workflow.js";
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 workflow.ts --concurrency 2
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 workflow.ts --no-record
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 workflow.ts
511
- npx automation run workflow.ts
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 workflow.ts --input '{"name":"Nyxa"}'
519
- npx automation workflow.ts --input-file input.json
520
- printf '%s' '{"name":"Nyxa"}' | npx automation workflow.ts --input -
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 workflow.ts --json
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 Automation in an existing npm project
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 a Workflow without installing the package
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
- const localPackage = resolveProjectLocalPackage(process.cwd());
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. Install it as a development dependency before running this command.`);
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 = parseArguments(arguments_);
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(path.resolve(process.cwd(), parsedArguments.workflowPath));
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
- const projectManifest = readPackageManifest(projectManifestPath);
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 manifestPath = path.join(directory, "node_modules", "@nyxa", "automation", "package.json");
251
- if (dependencyDeclared) {
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 !== undefined) {
262
- if (manifest.name !== PACKAGE_NAME) {
263
- throw new Error(`The project-local package at ${manifestPath} is not ${PACKAGE_NAME}.`);
264
- }
265
- const binary = typeof manifest.bin === "string"
266
- ? manifest.bin
267
- : manifest.bin?.automation;
268
- if (typeof binary !== "string") {
269
- throw new Error(`The project-local ${PACKAGE_NAME} package does not expose the automation binary.`);
270
- }
271
- if (typeof manifest.version !== "string") {
272
- throw new Error(`The project-local ${PACKAGE_NAME} package has no valid version.`);
273
- }
274
- const cliPath = path.resolve(path.dirname(manifestPath), binary);
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 workflowPath = path.resolve(invocationDirectory, parsed.workflowPath);
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
- const packageManager = install
29
- ? await detectPackageManager(invocationDirectory)
30
- : undefined;
23
+ await initializeAutomationEnvironment(environmentDirectory);
24
+ const packageManager = install ? PACKAGE_MANAGER : undefined;
31
25
  if (packageManager !== undefined) {
32
- await installAutomationDependency(packageManager, invocationDirectory);
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
- let output = `Created ${parsed.workflowPath}.\n`;
41
+ const displayedWorkflowPath = path.relative(invocationDirectory, workflowPath);
42
+ let output = `Created ${displayedWorkflowPath}.\n`;
48
43
  if (packageManager !== undefined) {
49
- output += `Installed @nyxa/automation with ${packageManager.displayName}.\n`;
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 @nyxa/automation as a development dependency? [Y/n] ");
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 PACKAGE_MANAGERS = [
149
- {
150
- displayName: "npm",
151
- executable: "npm",
152
- installArguments: ["install", "--save-dev", "@nyxa/automation"],
153
- lockfile: "package-lock.json",
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: invocationDirectory,
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 @nyxa/automation (exit ${String(exitCode)}).`);
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.1" },
191
+ clientInfo: { name: "@nyxa/automation", version: "0.1.2" },
192
192
  protocolVersion: PROTOCOL_VERSION,
193
193
  });
194
194
  if (initialized.protocolVersion !== PROTOCOL_VERSION) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nyxa/automation",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Execute trusted TypeScript Workflows with local Harness CLIs.",
5
5
  "repository": {
6
6
  "type": "git",