@altf4llc/vorpal-sdk 0.1.0-alpha.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.
Files changed (61) hide show
  1. package/dist/api/agent/agent.d.ts +68 -0
  2. package/dist/api/agent/agent.d.ts.map +1 -0
  3. package/dist/api/agent/agent.js +246 -0
  4. package/dist/api/agent/agent.js.map +1 -0
  5. package/dist/api/archive/archive.d.ts +98 -0
  6. package/dist/api/archive/archive.d.ts.map +1 -0
  7. package/dist/api/archive/archive.js +288 -0
  8. package/dist/api/archive/archive.js.map +1 -0
  9. package/dist/api/artifact/artifact.d.ts +169 -0
  10. package/dist/api/artifact/artifact.d.ts.map +1 -0
  11. package/dist/api/artifact/artifact.js +1041 -0
  12. package/dist/api/artifact/artifact.js.map +1 -0
  13. package/dist/api/context/context.d.ts +42 -0
  14. package/dist/api/context/context.d.ts.map +1 -0
  15. package/dist/api/context/context.js +31 -0
  16. package/dist/api/context/context.js.map +1 -0
  17. package/dist/api/worker/worker.d.ts +65 -0
  18. package/dist/api/worker/worker.d.ts.map +1 -0
  19. package/dist/api/worker/worker.js +185 -0
  20. package/dist/api/worker/worker.js.map +1 -0
  21. package/dist/artifact/language/go.d.ts +165 -0
  22. package/dist/artifact/language/go.d.ts.map +1 -0
  23. package/dist/artifact/language/go.js +361 -0
  24. package/dist/artifact/language/go.js.map +1 -0
  25. package/dist/artifact/language/rust.d.ts +136 -0
  26. package/dist/artifact/language/rust.d.ts.map +1 -0
  27. package/dist/artifact/language/rust.js +576 -0
  28. package/dist/artifact/language/rust.js.map +1 -0
  29. package/dist/artifact/language/typescript.d.ts +112 -0
  30. package/dist/artifact/language/typescript.d.ts.map +1 -0
  31. package/dist/artifact/language/typescript.js +232 -0
  32. package/dist/artifact/language/typescript.js.map +1 -0
  33. package/dist/artifact/step.d.ts +28 -0
  34. package/dist/artifact/step.d.ts.map +1 -0
  35. package/dist/artifact/step.js +214 -0
  36. package/dist/artifact/step.js.map +1 -0
  37. package/dist/artifact.d.ts +392 -0
  38. package/dist/artifact.d.ts.map +1 -0
  39. package/dist/artifact.js +938 -0
  40. package/dist/artifact.js.map +1 -0
  41. package/dist/cli.d.ts +42 -0
  42. package/dist/cli.d.ts.map +1 -0
  43. package/dist/cli.js +112 -0
  44. package/dist/cli.js.map +1 -0
  45. package/dist/context.d.ts +169 -0
  46. package/dist/context.d.ts.map +1 -0
  47. package/dist/context.js +779 -0
  48. package/dist/context.js.map +1 -0
  49. package/dist/index.d.ts +16 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +22 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/system.d.ts +20 -0
  54. package/dist/system.d.ts.map +1 -0
  55. package/dist/system.js +76 -0
  56. package/dist/system.js.map +1 -0
  57. package/dist/vorpal.d.ts +2 -0
  58. package/dist/vorpal.d.ts.map +1 -0
  59. package/dist/vorpal.js +148 -0
  60. package/dist/vorpal.js.map +1 -0
  61. package/package.json +55 -0
@@ -0,0 +1,938 @@
1
+ import { ArtifactSystem } from "./api/artifact/artifact.js";
2
+ import { shell } from "./artifact/step.js";
3
+ /**
4
+ * Returns the environment variable key for an artifact digest.
5
+ * Matches Rust get_env_key() and Go GetEnvKey().
6
+ */
7
+ export function getEnvKey(digest) {
8
+ return `$VORPAL_ARTIFACT_${digest}`;
9
+ }
10
+ /**
11
+ * Converts a Map of secrets to a sorted array of proto objects.
12
+ * Matches Go SDK's SecretsToProto function.
13
+ */
14
+ export function secretsToProto(secrets) {
15
+ const keys = Array.from(secrets.keys()).sort();
16
+ return keys.map((name) => ({ name, value: secrets.get(name) }));
17
+ }
18
+ // ---------------------------------------------------------------------------
19
+ // ArtifactSource
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Builder for ArtifactSource messages.
23
+ * Matches Rust sdk/rust/src/artifact.rs ArtifactSource impl.
24
+ */
25
+ export class ArtifactSource {
26
+ _digest = undefined;
27
+ _excludes = [];
28
+ _includes = [];
29
+ _name;
30
+ _path;
31
+ /**
32
+ * @param name - Source name (used as a key in the artifact's source map)
33
+ * @param path - Filesystem path to the source directory or file
34
+ */
35
+ constructor(name, path) {
36
+ this._name = name;
37
+ this._path = path;
38
+ }
39
+ /**
40
+ * Sets a pre-computed digest for this source.
41
+ * When set, the agent skips re-hashing the source contents.
42
+ */
43
+ withDigest(digest) {
44
+ this._digest = digest;
45
+ return this;
46
+ }
47
+ /**
48
+ * Sets glob patterns to exclude from the source.
49
+ * Patterns are matched relative to the source path.
50
+ *
51
+ * @param excludes - Array of glob patterns (e.g., `["node_modules", "*.log"]`)
52
+ */
53
+ withExcludes(excludes) {
54
+ this._excludes = excludes;
55
+ return this;
56
+ }
57
+ /**
58
+ * Sets glob patterns to include in the source.
59
+ * Only matching files will be included. Patterns are matched relative to the source path.
60
+ *
61
+ * @param includes - Array of glob patterns (e.g., `["src/**", "package.json"]`)
62
+ */
63
+ withIncludes(includes) {
64
+ this._includes = includes;
65
+ return this;
66
+ }
67
+ /** Builds the {@link ArtifactSource} message. */
68
+ build() {
69
+ return {
70
+ digest: this._digest,
71
+ excludes: this._excludes,
72
+ includes: this._includes,
73
+ name: this._name,
74
+ path: this._path,
75
+ };
76
+ }
77
+ }
78
+ // ---------------------------------------------------------------------------
79
+ // ArtifactStep
80
+ // ---------------------------------------------------------------------------
81
+ /**
82
+ * Builder for ArtifactStep messages.
83
+ * Matches Rust sdk/rust/src/artifact.rs ArtifactStep impl.
84
+ */
85
+ export class ArtifactStep {
86
+ _arguments = [];
87
+ _artifacts = [];
88
+ _entrypoint;
89
+ _environments = [];
90
+ _secrets = [];
91
+ _script = undefined;
92
+ /**
93
+ * @param entrypoint - The executable entrypoint (e.g., `"bash"`, `"bwrap"`, `"docker"`)
94
+ */
95
+ constructor(entrypoint) {
96
+ this._entrypoint = entrypoint;
97
+ }
98
+ /**
99
+ * Sets command-line arguments passed to the entrypoint.
100
+ *
101
+ * @param args - Array of argument strings
102
+ */
103
+ withArguments(args) {
104
+ this._arguments = args;
105
+ return this;
106
+ }
107
+ /**
108
+ * Sets artifact digests whose outputs are available during this step.
109
+ * Each artifact's `$VORPAL_ARTIFACT_{digest}` directory will be mounted.
110
+ *
111
+ * @param artifacts - Array of artifact digest strings
112
+ */
113
+ withArtifacts(artifacts) {
114
+ this._artifacts = artifacts;
115
+ return this;
116
+ }
117
+ /**
118
+ * Sets environment variables for the step execution.
119
+ * Format: `"KEY=VALUE"`.
120
+ *
121
+ * @param environments - Array of environment variable strings
122
+ */
123
+ withEnvironments(environments) {
124
+ this._environments = environments;
125
+ return this;
126
+ }
127
+ /**
128
+ * Adds secrets available during the step. Secrets are deduplicated by name.
129
+ *
130
+ * @param secrets - Array of {@link ArtifactStepSecret} objects
131
+ */
132
+ withSecrets(secrets) {
133
+ for (const secret of secrets) {
134
+ if (!this._secrets.some((s) => s.name === secret.name)) {
135
+ this._secrets.push(secret);
136
+ }
137
+ }
138
+ return this;
139
+ }
140
+ /**
141
+ * Sets the shell script to execute in this step.
142
+ * A bash shebang and `set -euo pipefail` are **not** prepended automatically;
143
+ * use the {@link bash} or {@link shell} helpers for that behavior.
144
+ */
145
+ withScript(script) {
146
+ this._script = script;
147
+ return this;
148
+ }
149
+ /** Builds the {@link ArtifactStep} message. */
150
+ build() {
151
+ return {
152
+ entrypoint: this._entrypoint,
153
+ script: this._script,
154
+ secrets: this._secrets,
155
+ arguments: this._arguments,
156
+ artifacts: this._artifacts,
157
+ environments: this._environments,
158
+ };
159
+ }
160
+ }
161
+ // ---------------------------------------------------------------------------
162
+ // Artifact
163
+ // ---------------------------------------------------------------------------
164
+ /**
165
+ * Builder for Artifact messages.
166
+ * Matches Rust sdk/rust/src/artifact.rs Artifact impl (lines 211-256).
167
+ */
168
+ export class Artifact {
169
+ _aliases = [];
170
+ _name;
171
+ _sources = [];
172
+ _steps;
173
+ _systems;
174
+ /**
175
+ * @param name - Artifact name (must be unique within a namespace)
176
+ * @param steps - Build steps that produce the artifact output
177
+ * @param systems - Target systems this artifact supports (e.g., `[ArtifactSystem.AARCH64_DARWIN]`)
178
+ */
179
+ constructor(name, steps, systems) {
180
+ this._name = name;
181
+ this._steps = steps;
182
+ this._systems = systems;
183
+ }
184
+ /**
185
+ * Adds human-readable aliases for this artifact (e.g., `"my-tool:latest"`).
186
+ * Duplicates are ignored.
187
+ *
188
+ * @param aliases - Array of alias strings in `[namespace/]name[:tag]` format
189
+ */
190
+ withAliases(aliases) {
191
+ for (const alias of aliases) {
192
+ if (!this._aliases.includes(alias)) {
193
+ this._aliases.push(alias);
194
+ }
195
+ }
196
+ return this;
197
+ }
198
+ /**
199
+ * Adds source definitions for this artifact. Sources are deduplicated by name.
200
+ *
201
+ * @param sources - Array of {@link ArtifactSource} messages
202
+ */
203
+ withSources(sources) {
204
+ for (const source of sources) {
205
+ if (!this._sources.some((s) => s.name === source.name)) {
206
+ this._sources.push(source);
207
+ }
208
+ }
209
+ return this;
210
+ }
211
+ /**
212
+ * Builds the artifact, computes its SHA-256 digest, and registers it
213
+ * with the agent service via the provided {@link ConfigContext}.
214
+ *
215
+ * @returns The hex-encoded SHA-256 digest of the artifact
216
+ */
217
+ async build(context) {
218
+ const artifact = {
219
+ target: context.getSystem(),
220
+ sources: this._sources,
221
+ steps: this._steps,
222
+ systems: this._systems,
223
+ aliases: this._aliases,
224
+ name: this._name,
225
+ };
226
+ return context.addArtifact(artifact);
227
+ }
228
+ }
229
+ // ---------------------------------------------------------------------------
230
+ // Job
231
+ // ---------------------------------------------------------------------------
232
+ /**
233
+ * Builder for Job artifacts (simple script execution).
234
+ * Matches Rust sdk/rust/src/artifact.rs Job impl (lines 259-297).
235
+ *
236
+ * CRITICAL: Secrets are sorted by name before building (Rust line 290).
237
+ */
238
+ export class Job {
239
+ _artifacts = [];
240
+ _name;
241
+ _secrets = new Map();
242
+ _script;
243
+ _systems;
244
+ /**
245
+ * @param name - Job artifact name
246
+ * @param script - Shell script to execute
247
+ * @param systems - Target systems this job supports
248
+ */
249
+ constructor(name, script, systems) {
250
+ this._name = name;
251
+ this._script = script;
252
+ this._systems = systems;
253
+ }
254
+ /**
255
+ * Sets artifact digests whose outputs are available during the job's build step.
256
+ *
257
+ * @param artifacts - Array of artifact digest strings
258
+ */
259
+ withArtifacts(artifacts) {
260
+ this._artifacts = artifacts;
261
+ return this;
262
+ }
263
+ /**
264
+ * Adds secrets available during the job's build step.
265
+ *
266
+ * @param secrets - Map of secret name to value
267
+ */
268
+ withSecrets(secrets) {
269
+ for (const [k, v] of secrets) {
270
+ if (!this._secrets.has(k)) {
271
+ this._secrets.set(k, v);
272
+ }
273
+ }
274
+ return this;
275
+ }
276
+ /**
277
+ * Builds the job artifact and registers it with the agent service.
278
+ *
279
+ * @returns The hex-encoded SHA-256 digest of the artifact
280
+ */
281
+ async build(context) {
282
+ const step = await shell(context, this._artifacts, [], this._script, secretsToProto(this._secrets));
283
+ return new Artifact(this._name, [step], this._systems).build(context);
284
+ }
285
+ }
286
+ // ---------------------------------------------------------------------------
287
+ // Process
288
+ // ---------------------------------------------------------------------------
289
+ /**
290
+ * Builder for Process artifacts.
291
+ * Matches Rust sdk/rust/src/artifact.rs Process impl (lines 432-553).
292
+ *
293
+ * CRITICAL: Shell script template must be character-for-character identical.
294
+ * Secrets sorted by name (Rust line 477).
295
+ */
296
+ export class Process {
297
+ _arguments = [];
298
+ _artifacts = [];
299
+ _entrypoint;
300
+ _name;
301
+ _secrets = new Map();
302
+ _systems;
303
+ /**
304
+ * @param name - Process artifact name (used for start/stop/logs scripts)
305
+ * @param entrypoint - Path to the executable to run as a background process
306
+ * @param systems - Target systems this process supports
307
+ */
308
+ constructor(name, entrypoint, systems) {
309
+ this._name = name;
310
+ this._entrypoint = entrypoint;
311
+ this._systems = systems;
312
+ }
313
+ /**
314
+ * Sets command-line arguments passed to the process entrypoint.
315
+ *
316
+ * @param args - Array of argument strings
317
+ */
318
+ withArguments(args) {
319
+ this._arguments = args;
320
+ return this;
321
+ }
322
+ /**
323
+ * Adds artifact dependencies whose bin directories are added to PATH.
324
+ * Duplicates are ignored.
325
+ *
326
+ * @param artifacts - Array of artifact digest strings
327
+ */
328
+ withArtifacts(artifacts) {
329
+ for (const artifact of artifacts) {
330
+ if (!this._artifacts.includes(artifact)) {
331
+ this._artifacts.push(artifact);
332
+ }
333
+ }
334
+ return this;
335
+ }
336
+ /**
337
+ * Adds secrets available during the process build step.
338
+ *
339
+ * @param secrets - Map of secret name to value
340
+ */
341
+ withSecrets(secrets) {
342
+ for (const [k, v] of secrets) {
343
+ if (!this._secrets.has(k)) {
344
+ this._secrets.set(k, v);
345
+ }
346
+ }
347
+ return this;
348
+ }
349
+ /**
350
+ * Builds the process artifact, which includes start/stop/logs helper scripts.
351
+ *
352
+ * @returns The hex-encoded SHA-256 digest of the artifact
353
+ */
354
+ async build(context) {
355
+ const argumentsStr = this._arguments.join(" ");
356
+ const artifactsStr = this._artifacts
357
+ .map((v) => `$VORPAL_ARTIFACT_${v}/bin`)
358
+ .join(":");
359
+ // Script template matches Rust formatdoc! in Process::build()
360
+ const script = `mkdir -p $VORPAL_OUTPUT/bin
361
+
362
+ cat > $VORPAL_OUTPUT/bin/${this._name}-logs << "EOF"
363
+ #!/bin/bash
364
+ set -euo pipefail
365
+
366
+ if [ -f $VORPAL_OUTPUT/logs.txt ]; then
367
+ tail -f $VORPAL_OUTPUT/logs.txt
368
+ else
369
+ echo "No logs found"
370
+ fi
371
+ EOF
372
+
373
+ chmod +x $VORPAL_OUTPUT/bin/${this._name}-logs
374
+
375
+ cat > $VORPAL_OUTPUT/bin/${this._name}-stop << "EOF"
376
+ #!/bin/bash
377
+ set -euo pipefail
378
+
379
+ if [ -f $VORPAL_OUTPUT/pid ]; then
380
+ kill $(cat $VORPAL_OUTPUT/pid)
381
+ rm -rf $VORPAL_OUTPUT/pid
382
+ fi
383
+ EOF
384
+
385
+ chmod +x $VORPAL_OUTPUT/bin/${this._name}-stop
386
+
387
+ cat > $VORPAL_OUTPUT/bin/${this._name}-start << "EOF"
388
+ #!/bin/bash
389
+ set -euo pipefail
390
+
391
+ export PATH=${artifactsStr}:$PATH
392
+
393
+ $VORPAL_OUTPUT/bin/${this._name}-stop
394
+
395
+ echo "Process: ${this._entrypoint} ${argumentsStr}"
396
+
397
+ nohup ${this._entrypoint} ${argumentsStr} > $VORPAL_OUTPUT/logs.txt 2>&1 &
398
+
399
+ PROCESS_PID=$!
400
+
401
+ echo "Process ID: $PROCESS_PID"
402
+
403
+ echo $PROCESS_PID > $VORPAL_OUTPUT/pid
404
+
405
+ echo "Process commands:"
406
+ echo "- ${this._name}-logs (tail logs)"
407
+ echo "- ${this._name}-stop (stop process)"
408
+ echo "- ${this._name}-start (start process)"
409
+ EOF
410
+
411
+ chmod +x $VORPAL_OUTPUT/bin/${this._name}-start`;
412
+ const step = await shell(context, this._artifacts, [], script, secretsToProto(this._secrets));
413
+ return new Artifact(this._name, [step], this._systems).build(context);
414
+ }
415
+ }
416
+ // ---------------------------------------------------------------------------
417
+ // DevelopmentEnvironment
418
+ // ---------------------------------------------------------------------------
419
+ /**
420
+ * Builder for DevelopmentEnvironment artifacts.
421
+ * Matches Rust sdk/rust/src/artifact.rs DevelopmentEnvironment impl (lines 300-429).
422
+ *
423
+ * CRITICAL: Shell script template must match exactly. Secrets sorted by name.
424
+ */
425
+ export class DevelopmentEnvironment {
426
+ _artifacts = [];
427
+ _environments = [];
428
+ _name;
429
+ _secrets = new Map();
430
+ _systems;
431
+ /**
432
+ * @param name - Environment name (shown in shell prompt as `(name)`)
433
+ * @param systems - Target systems this environment supports
434
+ */
435
+ constructor(name, systems) {
436
+ this._name = name;
437
+ this._systems = systems;
438
+ }
439
+ /**
440
+ * Sets artifact dependencies whose bin directories are added to PATH.
441
+ *
442
+ * @param artifacts - Array of artifact digest strings
443
+ */
444
+ withArtifacts(artifacts) {
445
+ this._artifacts = artifacts;
446
+ return this;
447
+ }
448
+ /**
449
+ * Sets environment variables exported when the environment is activated.
450
+ * Format: `"KEY=VALUE"`. PATH entries are handled specially and merged
451
+ * with artifact bin paths.
452
+ *
453
+ * @param environments - Array of environment variable strings
454
+ */
455
+ withEnvironments(environments) {
456
+ this._environments = environments;
457
+ return this;
458
+ }
459
+ /**
460
+ * Adds secrets available during the environment build step.
461
+ *
462
+ * @param secrets - Map of secret name to value
463
+ */
464
+ withSecrets(secrets) {
465
+ for (const [k, v] of secrets) {
466
+ if (!this._secrets.has(k)) {
467
+ this._secrets.set(k, v);
468
+ }
469
+ }
470
+ return this;
471
+ }
472
+ /**
473
+ * Builds the development environment artifact, which includes activate/deactivate
474
+ * scripts for shell integration.
475
+ *
476
+ * @returns The hex-encoded SHA-256 digest of the artifact
477
+ */
478
+ async build(context) {
479
+ const envsBackup = [
480
+ 'export VORPAL_SHELL_BACKUP_PATH="$PATH"',
481
+ 'export VORPAL_SHELL_BACKUP_PS1="$PS1"',
482
+ 'export VORPAL_SHELL_BACKUP_VORPAL_SHELL="$VORPAL_SHELL"',
483
+ ];
484
+ const envsExport = [
485
+ `export PS1="(${this._name}) $PS1"`,
486
+ 'export VORPAL_SHELL="1"',
487
+ ];
488
+ const envsRestore = [
489
+ 'export PATH="$VORPAL_SHELL_BACKUP_PATH"',
490
+ 'export PS1="$VORPAL_SHELL_BACKUP_PS1"',
491
+ 'export VORPAL_SHELL="$VORPAL_SHELL_BACKUP_VORPAL_SHELL"',
492
+ ];
493
+ const envsUnset = [
494
+ "unset VORPAL_SHELL_BACKUP_PATH",
495
+ "unset VORPAL_SHELL_BACKUP_PS1",
496
+ "unset VORPAL_SHELL_BACKUP_VORPAL_SHELL",
497
+ ];
498
+ for (const env of this._environments) {
499
+ const key = env.split("=")[0];
500
+ if (key === "PATH") {
501
+ continue;
502
+ }
503
+ envsBackup.push(`export VORPAL_SHELL_BACKUP_${key}="\$${key}"`);
504
+ envsExport.push(`export ${env}`);
505
+ envsRestore.push(`export ${key}="\$VORPAL_SHELL_BACKUP_${key}"`);
506
+ envsUnset.push(`unset VORPAL_SHELL_BACKUP_${key}`);
507
+ }
508
+ // Setup path
509
+ const stepPathArtifacts = this._artifacts
510
+ .map((artifact) => `${getEnvKey(artifact)}/bin`)
511
+ .join(":");
512
+ let stepPath = stepPathArtifacts;
513
+ const pathEnv = this._environments.find((x) => x.startsWith("PATH="));
514
+ if (pathEnv) {
515
+ const pathValue = pathEnv.split("=").slice(1).join("=");
516
+ if (pathValue) {
517
+ stepPath = `${pathValue}:${stepPath}`;
518
+ }
519
+ }
520
+ envsExport.push(`export PATH=${stepPath}:$PATH`);
521
+ // Setup script - matches Rust formatdoc!
522
+ const stepScript = `mkdir -p $VORPAL_WORKSPACE/bin
523
+
524
+ cat > bin/activate << "EOF"
525
+ #!/bin/bash
526
+
527
+ ${envsBackup.join("\n")}
528
+ ${envsExport.join("\n")}
529
+
530
+ deactivate(){
531
+ ${envsRestore.join("\n")}
532
+ ${envsUnset.join("\n")}
533
+ }
534
+
535
+ exec "$@"
536
+ EOF
537
+
538
+ chmod +x $VORPAL_WORKSPACE/bin/activate
539
+
540
+ mkdir -p $VORPAL_OUTPUT/bin
541
+
542
+ cp -pr bin "$VORPAL_OUTPUT"`;
543
+ const steps = [
544
+ await shell(context, this._artifacts, [], stepScript, secretsToProto(this._secrets)),
545
+ ];
546
+ return new Artifact(this._name, steps, this._systems).build(context);
547
+ }
548
+ }
549
+ // ---------------------------------------------------------------------------
550
+ // UserEnvironment
551
+ // ---------------------------------------------------------------------------
552
+ /**
553
+ * Builder for UserEnvironment artifacts.
554
+ * Matches Rust sdk/rust/src/artifact.rs UserEnvironment impl (lines 556-682).
555
+ *
556
+ * CRITICAL: Symlinks MUST be sorted by source path (Rust line 586).
557
+ */
558
+ export class UserEnvironment {
559
+ _artifacts = [];
560
+ _environments = [];
561
+ _name;
562
+ _symlinks = [];
563
+ _systems;
564
+ /**
565
+ * @param name - User environment name
566
+ * @param systems - Target systems this environment supports
567
+ */
568
+ constructor(name, systems) {
569
+ this._name = name;
570
+ this._systems = systems;
571
+ }
572
+ /**
573
+ * Sets artifact dependencies whose bin directories are added to PATH.
574
+ *
575
+ * @param artifacts - Array of artifact digest strings
576
+ */
577
+ withArtifacts(artifacts) {
578
+ this._artifacts = artifacts;
579
+ return this;
580
+ }
581
+ /**
582
+ * Sets environment variables exported when the user environment is activated.
583
+ * Format: `"KEY=VALUE"`. PATH entries are handled specially and merged
584
+ * with artifact bin paths.
585
+ *
586
+ * @param environments - Array of environment variable strings
587
+ */
588
+ withEnvironments(environments) {
589
+ this._environments = environments;
590
+ return this;
591
+ }
592
+ /**
593
+ * Adds symlinks created when the user environment is activated.
594
+ * Symlinks are sorted by source path before building for deterministic output.
595
+ *
596
+ * @param symlinks - Array of `[source, target]` path tuples
597
+ */
598
+ withSymlinks(symlinks) {
599
+ for (const [source, target] of symlinks) {
600
+ this._symlinks.push([source, target]);
601
+ }
602
+ return this;
603
+ }
604
+ /**
605
+ * Builds the user environment artifact, which includes activate/deactivate
606
+ * scripts and symlink management.
607
+ *
608
+ * @returns The hex-encoded SHA-256 digest of the artifact
609
+ */
610
+ async build(context) {
611
+ // Sort for deterministic output -- sorted by source path (index 0)
612
+ this._symlinks.sort((a, b) => a[0].localeCompare(b[0]));
613
+ // Setup path
614
+ const stepPathArtifacts = this._artifacts
615
+ .map((artifact) => `${getEnvKey(artifact)}/bin`)
616
+ .join(":");
617
+ let stepPath = stepPathArtifacts;
618
+ const pathEnv = this._environments.find((x) => x.startsWith("PATH="));
619
+ if (pathEnv) {
620
+ const pathValue = pathEnv.split("=").slice(1).join("=");
621
+ if (pathValue) {
622
+ stepPath = `${pathValue}:${stepPath}`;
623
+ }
624
+ }
625
+ // Setup environments for script (filter PATH)
626
+ const stepEnvironments = this._environments
627
+ .filter((e) => !e.startsWith("PATH="))
628
+ .map((e) => `export ${e}`)
629
+ .join("\n");
630
+ const symlinksDeactivate = this._symlinks
631
+ .map(([_, target]) => `rm -f ${target}`)
632
+ .join("\n");
633
+ const symlinksCheck = this._symlinks
634
+ .map(([_, target]) => `if [ -f ${target} ]; then echo "ERROR: Symlink target exists -> ${target}" && exit 1; fi`)
635
+ .join("\n");
636
+ const symlinksActivate = this._symlinks
637
+ .map(([source, target]) => `ln -s ${source} ${target}`)
638
+ .join("\n");
639
+ // Script template matches Rust formatdoc! in UserEnvironment::build()
640
+ const stepScript = `mkdir -p $VORPAL_OUTPUT/bin
641
+
642
+ cat > $VORPAL_OUTPUT/bin/vorpal-activate-shell << "EOF"
643
+ ${stepEnvironments}
644
+ export PATH="$VORPAL_OUTPUT/bin:${stepPath}:$PATH"
645
+ EOF
646
+
647
+ cat > $VORPAL_OUTPUT/bin/vorpal-deactivate-symlinks << "EOF"
648
+ #!/bin/bash
649
+ set -euo pipefail
650
+ ${symlinksDeactivate}
651
+ EOF
652
+
653
+ cat > $VORPAL_OUTPUT/bin/vorpal-activate-symlinks << "EOF"
654
+ #!/bin/bash
655
+ set -euo pipefail
656
+ ${symlinksCheck}
657
+ ${symlinksActivate}
658
+ EOF
659
+
660
+ cat > $VORPAL_OUTPUT/bin/vorpal-activate << "EOF"
661
+ #!/bin/bash
662
+ set -euo pipefail
663
+
664
+ echo "Deactivating previous symlinks..."
665
+
666
+ if [ -f $HOME/.vorpal/bin/vorpal-deactivate-symlinks ]; then
667
+ $HOME/.vorpal/bin/vorpal-deactivate-symlinks
668
+ fi
669
+
670
+ echo "Activating symlinks..."
671
+
672
+ $VORPAL_OUTPUT/bin/vorpal-activate-symlinks
673
+
674
+ echo "Vorpal userenv installed. Run 'source vorpal-activate-shell' to activate."
675
+
676
+ ln -sf $VORPAL_OUTPUT/bin/vorpal-activate-shell $HOME/.vorpal/bin/vorpal-activate-shell
677
+ ln -sf $VORPAL_OUTPUT/bin/vorpal-activate-symlinks $HOME/.vorpal/bin/vorpal-activate-symlinks
678
+ ln -sf $VORPAL_OUTPUT/bin/vorpal-deactivate-symlinks $HOME/.vorpal/bin/vorpal-deactivate-symlinks
679
+ EOF
680
+
681
+
682
+ chmod +x $VORPAL_OUTPUT/bin/vorpal-activate-shell
683
+ chmod +x $VORPAL_OUTPUT/bin/vorpal-deactivate-symlinks
684
+ chmod +x $VORPAL_OUTPUT/bin/vorpal-activate-symlinks
685
+ chmod +x $VORPAL_OUTPUT/bin/vorpal-activate`;
686
+ const steps = [
687
+ await shell(context, this._artifacts, [], stepScript, []),
688
+ ];
689
+ return new Artifact(this._name, steps, this._systems).build(context);
690
+ }
691
+ }
692
+ // ---------------------------------------------------------------------------
693
+ // OciImage
694
+ // ---------------------------------------------------------------------------
695
+ /**
696
+ * Builder for OCI container image artifacts.
697
+ * Matches Rust sdk/rust/src/artifact/oci_image.rs OciImage impl.
698
+ *
699
+ * Produces a container image tarball using crane, rsync, and a rootfs artifact.
700
+ * Only supports Linux systems (AARCH64_LINUX, X8664_LINUX).
701
+ */
702
+ export class OciImage {
703
+ _aliases = [];
704
+ _artifacts = [];
705
+ _crane = undefined;
706
+ _name;
707
+ _rootfs;
708
+ _rsync = undefined;
709
+ /**
710
+ * @param name - OCI image name (must be lowercase, valid chars: a-z, 0-9, / : - . _)
711
+ * @param rootfs - Artifact digest for the rootfs base layer
712
+ */
713
+ constructor(name, rootfs) {
714
+ this._name = name;
715
+ this._rootfs = rootfs;
716
+ }
717
+ /**
718
+ * Adds human-readable aliases for this image artifact.
719
+ *
720
+ * @param aliases - Array of alias strings
721
+ */
722
+ withAliases(aliases) {
723
+ this._aliases = aliases;
724
+ return this;
725
+ }
726
+ /**
727
+ * Sets artifact digests to include as layers in the container image.
728
+ * Each artifact's bin directory is symlinked into /usr/local/bin.
729
+ *
730
+ * @param artifacts - Array of artifact digest strings
731
+ */
732
+ withArtifacts(artifacts) {
733
+ this._artifacts = artifacts;
734
+ return this;
735
+ }
736
+ /**
737
+ * Overrides the default crane artifact used for building the OCI image.
738
+ * When not set, crane is fetched from the registry alias "crane:0.21.1".
739
+ *
740
+ * @param crane - Artifact digest for crane
741
+ */
742
+ withCrane(crane) {
743
+ this._crane = crane;
744
+ return this;
745
+ }
746
+ /**
747
+ * Overrides the default rsync artifact used for building the OCI image.
748
+ * When not set, rsync is fetched from the registry alias "rsync:3.4.1".
749
+ *
750
+ * @param rsync - Artifact digest for rsync
751
+ */
752
+ withRsync(rsync) {
753
+ this._rsync = rsync;
754
+ return this;
755
+ }
756
+ /**
757
+ * Builds the OCI image artifact and registers it with the agent service.
758
+ *
759
+ * Fetches crane and rsync as pre-built aliases from the registry,
760
+ * generates the build script, and creates the artifact.
761
+ *
762
+ * @returns The hex-encoded SHA-256 digest of the artifact
763
+ */
764
+ async build(context) {
765
+ // Validate image name (matches Rust validation)
766
+ if (this._name !== this._name.toLowerCase()) {
767
+ throw new Error(`container image name must be lowercase: '${this._name}'`);
768
+ }
769
+ for (const c of this._name) {
770
+ if (!((c >= "a" && c <= "z") ||
771
+ (c >= "0" && c <= "9") ||
772
+ c === "/" ||
773
+ c === ":" ||
774
+ c === "-" ||
775
+ c === "." ||
776
+ c === "_")) {
777
+ throw new Error(`container image name invalid character '${c}': '${this._name}'. ` +
778
+ `Allowed: lowercase letters, digits, and / : - . _`);
779
+ }
780
+ }
781
+ const crane = this._crane ?? await context.fetchArtifactAlias("crane:0.21.1");
782
+ const rsync = this._rsync ?? await context.fetchArtifactAlias("rsync:3.4.1");
783
+ const artifactsList = this._artifacts.join(" ");
784
+ const namespace = context.getArtifactNamespace();
785
+ const stepScript = `OCI_IMAGE_ARTIFACTS="${artifactsList}"
786
+ OCI_IMAGE_CRANE="${getEnvKey(crane)}"
787
+ OCI_IMAGE_NAME="${this._name}"
788
+ OCI_IMAGE_ROOTFS="${getEnvKey(this._rootfs)}"
789
+ OCI_IMAGE_RSYNC="${getEnvKey(rsync)}"
790
+ OUTPUT_TAR=\${PWD}/rootfs.tar
791
+ ROOTFS_DIR=\${PWD}/rootfs
792
+ STORE_PREFIX=var/lib/vorpal/store/artifact/output/${namespace}
793
+
794
+ # Detect platform based on build architecture
795
+ case "$(uname -m)" in
796
+ x86_64) OCI_PLATFORM="linux/amd64" ;;
797
+ aarch64) OCI_PLATFORM="linux/arm64" ;;
798
+ *) OCI_PLATFORM="linux/$(uname -m)" ;;
799
+ esac
800
+
801
+ mkdir -p \${ROOTFS_DIR}
802
+
803
+ for artifact in \${OCI_IMAGE_ARTIFACTS}; do
804
+ SOURCE_DIR=/\${STORE_PREFIX}/\${artifact}
805
+ TARGET_PATH=\${STORE_PREFIX}/\${artifact}
806
+
807
+ mkdir -p \${ROOTFS_DIR}/\${TARGET_PATH}
808
+
809
+ echo "Copying artifact layer \${artifact}..."
810
+
811
+ \${OCI_IMAGE_RSYNC}/bin/rsync -aW \${SOURCE_DIR}/ \${ROOTFS_DIR}/\${TARGET_PATH}
812
+
813
+ echo "Copied artifact layer \${artifact}"
814
+
815
+ # Symlink bin files to /usr/local/bin
816
+ if [ -d "\${SOURCE_DIR}/bin" ]; then
817
+ mkdir -p \${ROOTFS_DIR}/usr/local/bin
818
+ for bin_file in \${SOURCE_DIR}/bin/*; do
819
+ if [ -f "\${bin_file}" ]; then
820
+ bin_name=$(basename "\${bin_file}")
821
+ ln -sf /\${TARGET_PATH}/bin/\${bin_name} \${ROOTFS_DIR}/usr/local/bin/\${bin_name}
822
+ echo "Symlinked \${bin_name} to /usr/local/bin"
823
+ fi
824
+ done
825
+ fi
826
+ done
827
+
828
+ echo "Copying Vorpal operating system files..."
829
+
830
+ \${OCI_IMAGE_RSYNC}/bin/rsync -aW \${OCI_IMAGE_ROOTFS}/ \${ROOTFS_DIR}
831
+
832
+ echo "Copied Vorpal operating system files"
833
+
834
+ echo "Creating output tarball..."
835
+
836
+ tar -cf \${OUTPUT_TAR} -C \${ROOTFS_DIR} .
837
+
838
+ echo "Created output tarball"
839
+
840
+ mkdir -p \${VORPAL_OUTPUT}
841
+
842
+ echo "Creating OCI image \${OCI_IMAGE_NAME}:latest"
843
+
844
+ \${OCI_IMAGE_CRANE}/bin/crane append \\
845
+ --new_layer \${OUTPUT_TAR} \\
846
+ --new_tag \${OCI_IMAGE_NAME}:latest \\
847
+ --oci-empty-base \\
848
+ --output \${VORPAL_OUTPUT}/image.tar \\
849
+ --platform \${OCI_PLATFORM}
850
+
851
+ echo "Setting platform metadata in image config..."
852
+
853
+ # Extract tarball to modify config (crane mutate cannot work with local files)
854
+ WORK_DIR=\${PWD}/image-work
855
+ mkdir -p \${WORK_DIR}
856
+ tar -xf \${VORPAL_OUTPUT}/image.tar -C \${WORK_DIR}
857
+
858
+ # Get config filename from manifest
859
+ CONFIG_FILE=$(sed -n 's/.*"Config":"\\([^"]*\\)".*/\\1/p' \${WORK_DIR}/manifest.json)
860
+
861
+ # Detect architecture for config metadata
862
+ case "$(uname -m)" in
863
+ x86_64) CONFIG_ARCH="amd64" ;;
864
+ aarch64) CONFIG_ARCH="arm64" ;;
865
+ *) CONFIG_ARCH="$(uname -m)" ;;
866
+ esac
867
+
868
+ # Modify config to set platform (crane append leaves these empty)
869
+ sed -i "s/\\"architecture\\":\\"\\"/\\"architecture\\":\\"\${CONFIG_ARCH}\\"/" \${WORK_DIR}/\${CONFIG_FILE}
870
+ sed -i "s/\\"os\\":\\"\\"/\\"os\\":\\"linux\\"/" \${WORK_DIR}/\${CONFIG_FILE}
871
+
872
+ # Compute new hash and rename config file
873
+ NEW_HASH=$(sha256sum \${WORK_DIR}/\${CONFIG_FILE} | awk '{print $1}')
874
+ NEW_CONFIG="sha256:\${NEW_HASH}"
875
+ mv \${WORK_DIR}/\${CONFIG_FILE} \${WORK_DIR}/\${NEW_CONFIG}
876
+
877
+ # Update manifest with new config reference
878
+ sed -i "s|\${CONFIG_FILE}|\${NEW_CONFIG}|" \${WORK_DIR}/manifest.json
879
+
880
+ # Repackage tarball
881
+ pushd \${WORK_DIR}
882
+ tar -cf \${VORPAL_OUTPUT}/image.tar manifest.json \${NEW_CONFIG} *.tar.gz
883
+ popd
884
+
885
+ # Cleanup
886
+ rm -rf \${WORK_DIR}
887
+
888
+ echo "Created OCI image \${OCI_IMAGE_NAME}:latest"`;
889
+ const stepArtifacts = [crane, rsync, this._rootfs, ...this._artifacts];
890
+ const step = await shell(context, stepArtifacts, [], stepScript, []);
891
+ const systems = [
892
+ ArtifactSystem.AARCH64_LINUX,
893
+ ArtifactSystem.X8664_LINUX,
894
+ ];
895
+ return new Artifact(this._name, [step], systems)
896
+ .withAliases(this._aliases)
897
+ .build(context);
898
+ }
899
+ }
900
+ // ---------------------------------------------------------------------------
901
+ // Argument
902
+ // ---------------------------------------------------------------------------
903
+ /**
904
+ * Argument builder for artifact variables.
905
+ * Matches Rust sdk/rust/src/artifact.rs Argument impl.
906
+ */
907
+ export class Argument {
908
+ _name;
909
+ _require = false;
910
+ /**
911
+ * @param name - The variable name to look up in the context
912
+ */
913
+ constructor(name) {
914
+ this._name = name;
915
+ }
916
+ /**
917
+ * Marks this argument as required. If the variable is not set in the
918
+ * context when {@link Argument.build} is called, an error is thrown.
919
+ */
920
+ withRequire() {
921
+ this._require = true;
922
+ return this;
923
+ }
924
+ /**
925
+ * Resolves the argument value from the {@link ConfigContext}.
926
+ *
927
+ * @returns The variable value, or `undefined` if not set and not required
928
+ * @throws If the variable is required but not set
929
+ */
930
+ build(context) {
931
+ const variable = context.getVariable(this._name);
932
+ if (this._require && variable === undefined) {
933
+ throw new Error(`variable '${this._name}' is required`);
934
+ }
935
+ return variable;
936
+ }
937
+ }
938
+ //# sourceMappingURL=artifact.js.map