@agents-at-scale/ark 0.1.36 → 0.1.37

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.
@@ -0,0 +1,20 @@
1
+ export function parseTimeoutToSeconds(value) {
2
+ const match = value.match(/^(\d+)([smh])?$/);
3
+ if (!match) {
4
+ throw new Error('Invalid timeout format. Use format like 30s, 2m, or 1h');
5
+ }
6
+ const num = parseInt(match[1], 10);
7
+ const unit = match[2];
8
+ if (!unit)
9
+ return num;
10
+ switch (unit) {
11
+ case 's':
12
+ return num;
13
+ case 'm':
14
+ return num * 60;
15
+ case 'h':
16
+ return num * 3600;
17
+ default:
18
+ return num;
19
+ }
20
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { parseTimeoutToSeconds } from './timeout.js';
3
+ describe('parseTimeoutToSeconds', () => {
4
+ it('should parse time units correctly', () => {
5
+ expect(parseTimeoutToSeconds('30s')).toBe(30);
6
+ expect(parseTimeoutToSeconds('2m')).toBe(120);
7
+ expect(parseTimeoutToSeconds('1h')).toBe(3600);
8
+ expect(parseTimeoutToSeconds('60')).toBe(60);
9
+ });
10
+ it('should throw error for invalid formats', () => {
11
+ expect(() => parseTimeoutToSeconds('abc')).toThrow('Invalid timeout format');
12
+ expect(() => parseTimeoutToSeconds('-5s')).toThrow('Invalid timeout format');
13
+ });
14
+ });
@@ -0,0 +1,8 @@
1
+ import type { ArkService } from '../types/arkService.js';
2
+ export interface WaitProgress {
3
+ serviceName: string;
4
+ ready: boolean;
5
+ error?: string;
6
+ }
7
+ export declare function waitForDeploymentReady(deploymentName: string, namespace: string, timeoutSeconds: number): Promise<boolean>;
8
+ export declare function waitForServicesReady(services: ArkService[], timeoutSeconds: number, onProgress?: (progress: WaitProgress) => void): Promise<boolean>;
@@ -0,0 +1,32 @@
1
+ import { execa } from 'execa';
2
+ export async function waitForDeploymentReady(deploymentName, namespace, timeoutSeconds) {
3
+ try {
4
+ await execa('kubectl', [
5
+ 'wait',
6
+ '--for=condition=available',
7
+ `deployment/${deploymentName}`,
8
+ '-n',
9
+ namespace,
10
+ `--timeout=${timeoutSeconds}s`,
11
+ ], { timeout: timeoutSeconds * 1000 });
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ export async function waitForServicesReady(services, timeoutSeconds, onProgress) {
19
+ const validServices = services.filter((s) => s.k8sDeploymentName && s.namespace);
20
+ const checkPromises = validServices.map(async (service) => {
21
+ const isReady = await waitForDeploymentReady(service.k8sDeploymentName, service.namespace, timeoutSeconds);
22
+ if (onProgress) {
23
+ onProgress({
24
+ serviceName: service.name,
25
+ ready: isReady,
26
+ });
27
+ }
28
+ return isReady;
29
+ });
30
+ const results = await Promise.all(checkPromises);
31
+ return results.every((ready) => ready);
32
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,104 @@
1
+ import { describe, it, expect, jest, beforeEach } from '@jest/globals';
2
+ jest.unstable_mockModule('execa', () => ({
3
+ execa: jest.fn(),
4
+ }));
5
+ const { execa } = await import('execa');
6
+ const { waitForDeploymentReady, waitForServicesReady } = await import('./waitForReady.js');
7
+ const mockedExeca = execa;
8
+ describe('waitForDeploymentReady', () => {
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ });
12
+ it('returns true when deployment is ready', async () => {
13
+ mockedExeca.mockResolvedValueOnce({
14
+ stdout: 'deployment.apps/ark-controller condition met',
15
+ stderr: '',
16
+ exitCode: 0,
17
+ });
18
+ const result = await waitForDeploymentReady('ark-controller', 'ark-system', 30);
19
+ expect(result).toBe(true);
20
+ expect(mockedExeca).toHaveBeenCalledWith('kubectl', [
21
+ 'wait',
22
+ '--for=condition=available',
23
+ 'deployment/ark-controller',
24
+ '-n',
25
+ 'ark-system',
26
+ '--timeout=30s',
27
+ ], { timeout: 30000 });
28
+ });
29
+ it('returns false on error', async () => {
30
+ mockedExeca.mockRejectedValueOnce(new Error('kubectl error'));
31
+ const result = await waitForDeploymentReady('ark-api', 'default', 10);
32
+ expect(result).toBe(false);
33
+ });
34
+ });
35
+ describe('waitForServicesReady', () => {
36
+ beforeEach(() => {
37
+ jest.clearAllMocks();
38
+ });
39
+ const service1 = {
40
+ name: 'ark-controller',
41
+ helmReleaseName: 'ark-controller',
42
+ description: 'Core controller',
43
+ enabled: true,
44
+ category: 'core',
45
+ namespace: 'ark-system',
46
+ k8sDeploymentName: 'ark-controller',
47
+ };
48
+ const service2 = {
49
+ name: 'ark-api',
50
+ helmReleaseName: 'ark-api',
51
+ description: 'API service',
52
+ enabled: true,
53
+ category: 'service',
54
+ namespace: 'default',
55
+ k8sDeploymentName: 'ark-api',
56
+ };
57
+ it('returns true when all services are ready', async () => {
58
+ mockedExeca.mockResolvedValue({
59
+ stdout: 'condition met',
60
+ stderr: '',
61
+ exitCode: 0,
62
+ });
63
+ const result = await waitForServicesReady([service1, service2], 30);
64
+ expect(result).toBe(true);
65
+ expect(mockedExeca).toHaveBeenCalledTimes(2);
66
+ });
67
+ it('returns false when any service fails', async () => {
68
+ mockedExeca
69
+ .mockResolvedValueOnce({ stdout: 'ok', stderr: '', exitCode: 0 })
70
+ .mockRejectedValueOnce(new Error('timeout'));
71
+ const result = await waitForServicesReady([service1, service2], 30);
72
+ expect(result).toBe(false);
73
+ });
74
+ it('calls progress callback', async () => {
75
+ mockedExeca.mockResolvedValue({
76
+ stdout: 'ok',
77
+ stderr: '',
78
+ exitCode: 0,
79
+ });
80
+ const onProgress = jest.fn();
81
+ await waitForServicesReady([service1], 30, onProgress);
82
+ expect(onProgress).toHaveBeenCalledWith({
83
+ serviceName: 'ark-controller',
84
+ ready: true,
85
+ });
86
+ });
87
+ it('skips services without deployment info', async () => {
88
+ const incompleteService = {
89
+ name: 'incomplete',
90
+ helmReleaseName: 'incomplete',
91
+ description: 'No deployment info',
92
+ enabled: true,
93
+ category: 'service',
94
+ };
95
+ mockedExeca.mockResolvedValue({
96
+ stdout: 'ok',
97
+ stderr: '',
98
+ exitCode: 0,
99
+ });
100
+ const result = await waitForServicesReady([service1, incompleteService], 30);
101
+ expect(result).toBe(true);
102
+ expect(mockedExeca).toHaveBeenCalledTimes(1);
103
+ });
104
+ });
@@ -0,0 +1,27 @@
1
+ export interface ArkService {
2
+ name: string;
3
+ helmReleaseName: string;
4
+ description: string;
5
+ enabled: boolean;
6
+ category: string;
7
+ namespace?: string;
8
+ chartPath?: string;
9
+ installArgs?: string[];
10
+ k8sServiceName?: string;
11
+ k8sServicePort?: number;
12
+ k8sPortForwardLocalPort?: number;
13
+ k8sDeploymentName?: string;
14
+ k8sDevDeploymentName?: string;
15
+ }
16
+ export interface ServiceCollection {
17
+ [key: string]: ArkService;
18
+ }
19
+ export interface ArkDependency {
20
+ name: string;
21
+ command: string;
22
+ args: string[];
23
+ description: string;
24
+ }
25
+ export interface DependencyCollection {
26
+ [key: string]: ArkDependency;
27
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agents-at-scale/ark",
3
- "version": "0.1.36",
4
- "description": "ARK CLI - Interactive terminal interface for ARK agents",
3
+ "version": "0.1.37",
4
+ "description": "Ark CLI - Interactive terminal interface for ARK agents",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
@@ -18,7 +18,7 @@
18
18
  "lint": "eslint src/ --fix && prettier --write src/",
19
19
  "lint:check": "eslint src/ && prettier --check src/",
20
20
  "test": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --coverage --coverageDirectory=./artifacts/coverage --coverageReporters=text --coverageReporters=lcov --coverageReporters=text-summary",
21
- "postinstall": "echo \"ARK CLI installed! Run 'ark' to try it out.\""
21
+ "postinstall": "echo \"Ark CLI installed. Run 'ark' to try it out.\""
22
22
  },
23
23
  "keywords": [
24
24
  "ark",