@agents-at-scale/ark 0.1.61 → 0.1.63

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.
@@ -13,6 +13,7 @@ export declare const arkDependencies: DependencyCollection;
13
13
  */
14
14
  export declare const arkServices: ServiceCollection;
15
15
  /**
16
- * Get services that can be installed via Helm charts (only enabled services)
16
+ * Get services that can be installed via Helm charts (only enabled services).
17
+ * When a backend is specified, services with a non-matching requiresBackend are excluded.
17
18
  */
18
- export declare function getInstallableServices(): ServiceCollection;
19
+ export declare function getInstallableServices(backend?: 'etcd' | 'postgresql'): ServiceCollection;
@@ -221,14 +221,17 @@ function applyConfigOverrides(defaults) {
221
221
  */
222
222
  export const arkServices = applyConfigOverrides(defaultArkServices);
223
223
  /**
224
- * Get services that can be installed via Helm charts (only enabled services)
224
+ * Get services that can be installed via Helm charts (only enabled services).
225
+ * When a backend is specified, services with a non-matching requiresBackend are excluded.
225
226
  */
226
- export function getInstallableServices() {
227
+ export function getInstallableServices(backend = 'etcd') {
227
228
  const installable = {};
228
229
  for (const [key, service] of Object.entries(arkServices)) {
229
- if (service.enabled && service.chartPath) {
230
- installable[key] = service;
231
- }
230
+ if (!service.enabled || !service.chartPath)
231
+ continue;
232
+ if (service.requiresBackend && service.requiresBackend !== backend)
233
+ continue;
234
+ installable[key] = service;
232
235
  }
233
236
  return installable;
234
237
  }
@@ -6,5 +6,6 @@ export declare function installArk(config: ArkConfig, serviceNames?: string[], o
6
6
  verbose?: boolean;
7
7
  arkVersion?: string;
8
8
  marketplaceVersion?: string;
9
+ backend?: string;
9
10
  }): Promise<void>;
10
11
  export declare function createInstallCommand(config: ArkConfig): Command;
@@ -10,6 +10,43 @@ import { printNextSteps } from '../../lib/nextSteps.js';
10
10
  import ora from 'ora';
11
11
  import { waitForServicesReady, } from '../../lib/waitForReady.js';
12
12
  import { parseTimeoutToSeconds } from '../../lib/timeout.js';
13
+ import { runReadinessChecks } from '../../lib/readinessChecks.js';
14
+ function validatePostgresConfig(pg) {
15
+ if (!pg) {
16
+ throw new Error("missing 'storage.postgresql' block in .arkrc.yaml");
17
+ }
18
+ for (const key of ['host', 'user', 'passwordSecretName']) {
19
+ if (!pg[key]) {
20
+ throw new Error(`missing required field storage.postgresql.${key} in .arkrc.yaml`);
21
+ }
22
+ }
23
+ return pg;
24
+ }
25
+ function backendInstallArgs(service, backend, values) {
26
+ if (backend === 'etcd')
27
+ return [];
28
+ if (!values)
29
+ return [];
30
+ if (service.helmReleaseName === 'ark-controller') {
31
+ return ['--set', 'storage.backend=postgresql'];
32
+ }
33
+ if (service.helmReleaseName === 'ark-apiserver') {
34
+ const args = [];
35
+ args.push('--set', `postgresql.host=${values.host}`);
36
+ if (values.port !== undefined)
37
+ args.push('--set', `postgresql.port=${values.port}`);
38
+ if (values.database)
39
+ args.push('--set', `postgresql.database=${values.database}`);
40
+ args.push('--set', `postgresql.user=${values.user}`);
41
+ args.push('--set', `postgresql.passwordSecretName=${values.passwordSecretName}`);
42
+ if (values.passwordSecretKey)
43
+ args.push('--set', `postgresql.passwordSecretKey=${values.passwordSecretKey}`);
44
+ if (values.sslMode)
45
+ args.push('--set', `postgresql.sslMode=${values.sslMode}`);
46
+ return args;
47
+ }
48
+ return [];
49
+ }
13
50
  function isValidVersion(version) {
14
51
  return /^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/.test(version);
15
52
  }
@@ -75,7 +112,7 @@ async function checkAndCleanFailedRelease(releaseName, namespace, verbose = fals
75
112
  // Ignore errors - prerequisite may not exist
76
113
  }
77
114
  }
78
- async function installService(service, verbose = false, arkVersionOverride, marketplaceVersionOverride) {
115
+ async function installService(service, verbose = false, arkVersionOverride, marketplaceVersionOverride, extraArgs) {
79
116
  await uninstallPrerequisites(service, verbose);
80
117
  await checkAndCleanFailedRelease(service.helmReleaseName, service.namespace, verbose);
81
118
  let chartPath = service.chartPath;
@@ -111,6 +148,8 @@ async function installService(service, verbose = false, arkVersionOverride, mark
111
148
  }
112
149
  // Add any additional install args
113
150
  helmArgs.push(...(service.installArgs || []));
151
+ if (extraArgs)
152
+ helmArgs.push(...extraArgs);
114
153
  await execute('helm', helmArgs, {
115
154
  stdout: 'inherit',
116
155
  stderr: 'pipe',
@@ -140,6 +179,22 @@ export async function installArk(config, serviceNames = [], options = {}) {
140
179
  // Show cluster info
141
180
  output.success(`connected to cluster: ${chalk.bold(clusterInfo.context)}`);
142
181
  console.log(); // Add blank line after cluster info
182
+ const requestedBackend = options.backend ?? config.storage?.backend ?? 'etcd';
183
+ if (requestedBackend !== 'etcd' && requestedBackend !== 'postgresql') {
184
+ output.error(`Invalid backend value: ${requestedBackend}. Expected 'etcd' or 'postgresql'.`);
185
+ process.exit(1);
186
+ }
187
+ const backend = requestedBackend;
188
+ let postgresValues;
189
+ if (backend === 'postgresql') {
190
+ try {
191
+ postgresValues = validatePostgresConfig(config.storage?.postgresql);
192
+ }
193
+ catch (err) {
194
+ output.error(`${err instanceof Error ? err.message : String(err)}`);
195
+ process.exit(1);
196
+ }
197
+ }
143
198
  // If specific services are requested, install only those services
144
199
  if (serviceNames.length > 0) {
145
200
  for (const serviceName of serviceNames) {
@@ -174,7 +229,7 @@ export async function installArk(config, serviceNames = [], options = {}) {
174
229
  }
175
230
  output.info(`installing marketplace item ${service.name}...`);
176
231
  try {
177
- await installService(service, options.verbose, options.arkVersion, options.marketplaceVersion);
232
+ await installService(service, options.verbose, options.arkVersion, options.marketplaceVersion, []);
178
233
  output.success(`${service.name} installed successfully`);
179
234
  }
180
235
  catch (error) {
@@ -185,7 +240,7 @@ export async function installArk(config, serviceNames = [], options = {}) {
185
240
  continue;
186
241
  }
187
242
  // Core ARK service
188
- const services = getInstallableServices();
243
+ const services = getInstallableServices(backend);
189
244
  const service = Object.values(services).find((s) => s.name === serviceName);
190
245
  if (!service) {
191
246
  output.error(`service '${serviceName}' not found`);
@@ -197,8 +252,26 @@ export async function installArk(config, serviceNames = [], options = {}) {
197
252
  }
198
253
  output.info(`installing ${service.name}...`);
199
254
  try {
200
- await installService(service, options.verbose, options.arkVersion, options.marketplaceVersion);
255
+ await installService(service, options.verbose, options.arkVersion, options.marketplaceVersion, backendInstallArgs(service, backend, postgresValues));
201
256
  output.success(`${service.name} installed successfully`);
257
+ // Wait for ark-apiserver to be ready before continuing to other services
258
+ if (service.helmReleaseName === 'ark-apiserver' && backend === 'postgresql') {
259
+ const spinner = ora('Waiting for ark-apiserver to be ready...').start();
260
+ try {
261
+ const results = await runReadinessChecks(120); // 2 minute timeout
262
+ const failed = results.find((r) => !r.passed);
263
+ if (failed) {
264
+ spinner.fail(`ark-apiserver readiness check failed: ${failed.message || 'unknown error'}`);
265
+ output.error('ark-apiserver is not ready. Stopping installation.');
266
+ process.exit(1);
267
+ }
268
+ spinner.succeed('ark-apiserver is ready');
269
+ }
270
+ catch (error) {
271
+ spinner.fail('Failed to check ark-apiserver readiness');
272
+ throw error;
273
+ }
274
+ }
202
275
  }
203
276
  catch (error) {
204
277
  if (handleInstallError(error, service, options)) {
@@ -210,11 +283,19 @@ export async function installArk(config, serviceNames = [], options = {}) {
210
283
  }
211
284
  // If not using -y flag, show checklist interface
212
285
  if (!options.yes) {
286
+ const backendMatch = (s) => !s.requiresBackend || s.requiresBackend === backend;
213
287
  const coreServices = Object.values(arkServices)
214
- .filter((s) => s.category === 'core')
215
- .sort((a, b) => a.name.localeCompare(b.name));
288
+ .filter((s) => s.category === 'core' && backendMatch(s))
289
+ .sort((a, b) => {
290
+ // Ensure ark-controller is always first
291
+ if (a.name === 'ark-controller')
292
+ return -1;
293
+ if (b.name === 'ark-controller')
294
+ return 1;
295
+ return a.name.localeCompare(b.name);
296
+ });
216
297
  const otherServices = Object.values(arkServices)
217
- .filter((s) => s.category === 'service')
298
+ .filter((s) => s.category === 'service' && backendMatch(s))
218
299
  .sort((a, b) => a.name.localeCompare(b.name));
219
300
  const mandatoryServiceNames = [...coreServices, ...otherServices]
220
301
  .filter((s) => s.mandatory)
@@ -331,7 +412,25 @@ export async function installArk(config, serviceNames = [], options = {}) {
331
412
  }
332
413
  output.info(`installing ${service.name}...`);
333
414
  try {
334
- await installService(service, options.verbose, options.arkVersion, options.marketplaceVersion);
415
+ await installService(service, options.verbose, options.arkVersion, options.marketplaceVersion, backendInstallArgs(service, backend, postgresValues));
416
+ // Wait for ark-apiserver to be ready before continuing to other services
417
+ if (service.helmReleaseName === 'ark-apiserver' && backend === 'postgresql') {
418
+ const spinner = ora('Waiting for ark-apiserver to be ready...').start();
419
+ try {
420
+ const results = await runReadinessChecks(120); // 2 minute timeout
421
+ const failed = results.find((r) => !r.passed);
422
+ if (failed) {
423
+ spinner.fail(`ark-apiserver readiness check failed: ${failed.message || 'unknown error'}`);
424
+ output.error('ark-apiserver is not ready. Stopping installation.');
425
+ process.exit(1);
426
+ }
427
+ spinner.succeed('ark-apiserver is ready');
428
+ }
429
+ catch (error) {
430
+ spinner.fail('Failed to check ark-apiserver readiness');
431
+ throw error;
432
+ }
433
+ }
335
434
  console.log(); // Add blank line after command output
336
435
  }
337
436
  catch (error) {
@@ -361,11 +460,19 @@ export async function installArk(config, serviceNames = [], options = {}) {
361
460
  }
362
461
  }
363
462
  // Install all services
364
- const services = getInstallableServices();
365
- for (const service of Object.values(services)) {
463
+ const services = getInstallableServices(backend);
464
+ const sortedServices = Object.values(services).sort((a, b) => {
465
+ // Ensure ark-controller is always first
466
+ if (a.name === 'ark-controller')
467
+ return -1;
468
+ if (b.name === 'ark-controller')
469
+ return 1;
470
+ return a.name.localeCompare(b.name);
471
+ });
472
+ for (const service of sortedServices) {
366
473
  output.info(`installing ${service.name}...`);
367
474
  try {
368
- await installService(service, options.verbose, options.arkVersion, options.marketplaceVersion);
475
+ await installService(service, options.verbose, options.arkVersion, options.marketplaceVersion, backendInstallArgs(service, backend, postgresValues));
369
476
  console.log(); // Add blank line after command output
370
477
  }
371
478
  catch (error) {
@@ -388,7 +495,8 @@ export async function installArk(config, serviceNames = [], options = {}) {
388
495
  const servicesToWait = Object.values(arkServices).filter((s) => s.enabled &&
389
496
  s.category === 'core' &&
390
497
  s.k8sDeploymentName &&
391
- s.namespace);
498
+ s.namespace &&
499
+ (!s.requiresBackend || s.requiresBackend === backend));
392
500
  const spinner = ora(`Waiting for Ark to be ready (timeout: ${timeoutSeconds}s)...`).start();
393
501
  const statusMap = new Map();
394
502
  servicesToWait.forEach((s) => statusMap.set(s.name, false));
@@ -428,6 +536,7 @@ export function createInstallCommand(config) {
428
536
  .option('--ark-version <version>', 'ARK version to install (e.g., 0.1.50, defaults to CLI version)')
429
537
  .option('--marketplace-version <version>', 'Marketplace item version to install (e.g., 0.1.5)')
430
538
  .option('--wait-for-ready <timeout>', 'wait for Ark to be ready after installation (e.g., 30s, 2m)')
539
+ .option('--backend <type>', "storage backend: 'etcd' (default) or 'postgresql' (overrides storage.backend in .arkrc.yaml)")
431
540
  .option('-v, --verbose', 'show commands being executed')
432
541
  .action(async (services, options) => {
433
542
  await installArk(config, services, options);
@@ -8,6 +8,19 @@ export interface MarketplaceConfig {
8
8
  repoUrl?: string;
9
9
  registry?: string;
10
10
  }
11
+ export interface PostgresStorageConfig {
12
+ host: string;
13
+ port?: number | string;
14
+ database?: string;
15
+ user: string;
16
+ passwordSecretName: string;
17
+ passwordSecretKey?: string;
18
+ sslMode?: string;
19
+ }
20
+ export interface StorageConfig {
21
+ backend?: 'etcd' | 'postgresql';
22
+ postgresql?: PostgresStorageConfig;
23
+ }
11
24
  export interface ArkConfig {
12
25
  chat?: ChatConfig;
13
26
  marketplace?: MarketplaceConfig;
@@ -15,6 +28,7 @@ export interface ArkConfig {
15
28
  reusePortForwards?: boolean;
16
29
  [serviceName: string]: Partial<ArkService> | boolean | undefined;
17
30
  };
31
+ storage?: StorageConfig;
18
32
  queryTimeout?: string;
19
33
  defaultExportTypes?: string[];
20
34
  clusterInfo?: ClusterInfo;
@@ -80,6 +80,13 @@ export function loadConfig() {
80
80
  config.services.reusePortForwards =
81
81
  process.env.ARK_SERVICES_REUSE_PORT_FORWARDS === '1';
82
82
  }
83
+ if (process.env.ARK_STORAGE_BACKEND !== undefined) {
84
+ const backend = process.env.ARK_STORAGE_BACKEND;
85
+ if (backend === 'etcd' || backend === 'postgresql') {
86
+ config.storage = config.storage || {};
87
+ config.storage.backend = backend;
88
+ }
89
+ }
83
90
  return config;
84
91
  }
85
92
  /**
@@ -119,6 +126,18 @@ function mergeConfig(target, source) {
119
126
  }
120
127
  }
121
128
  }
129
+ if (source.storage) {
130
+ target.storage = target.storage || {};
131
+ if (source.storage.backend !== undefined) {
132
+ target.storage.backend = source.storage.backend;
133
+ }
134
+ if (source.storage.postgresql) {
135
+ target.storage.postgresql = {
136
+ ...target.storage.postgresql,
137
+ ...source.storage.postgresql,
138
+ };
139
+ }
140
+ }
122
141
  if (source.queryTimeout !== undefined) {
123
142
  target.queryTimeout = source.queryTimeout;
124
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agents-at-scale/ark",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
4
4
  "description": "Ark CLI - Interactive terminal interface for ARK agents",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -87,7 +87,7 @@
87
87
  "overrides": {
88
88
  "minimatch": "^10.2.3",
89
89
  "rollup": "4.59.0",
90
- "hono": "^4.12.14",
90
+ "hono": "^4.12.18",
91
91
  "@hono/node-server": "^1.19.13",
92
92
  "flatted": "^3.4.2",
93
93
  "tar": "^7.5.11",
@@ -772,11 +772,11 @@ wheels = [
772
772
 
773
773
  [[package]]
774
774
  name = "python-multipart"
775
- version = "0.0.26"
775
+ version = "0.0.27"
776
776
  source = { registry = "https://pypi.org/simple" }
777
- sdist = { url = "https://files.pythonhosted.org/packages/88/71/b145a380824a960ebd60e1014256dbb7d2253f2316ff2d73dfd8928ec2c3/python_multipart-0.0.26.tar.gz", hash = "sha256:08fadc45918cd615e26846437f50c5d6d23304da32c341f289a617127b081f17", size = 43501, upload-time = "2026-04-10T14:09:59.473Z" }
777
+ sdist = { url = "https://files.pythonhosted.org/packages/69/9b/f23807317a113dc36e74e75eb265a02dd1a4d9082abc3c1064acd22997c4/python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602", size = 44043, upload-time = "2026-04-27T10:51:26.649Z" }
778
778
  wheels = [
779
- { url = "https://files.pythonhosted.org/packages/9a/22/f1925cdda983ab66fc8ec6ec8014b959262747e58bdca26a4e3d1da29d56/python_multipart-0.0.26-py3-none-any.whl", hash = "sha256:c0b169f8c4484c13b0dcf2ef0ec3a4adb255c4b7d18d8e420477d2b1dd03f185", size = 28847, upload-time = "2026-04-10T14:09:58.131Z" },
779
+ { url = "https://files.pythonhosted.org/packages/99/78/4126abcbdbd3c559d43e0db7f7b9173fc6befe45d39a2856cc0b8ec2a5a6/python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645", size = 29254, upload-time = "2026-04-27T10:51:24.997Z" },
780
780
  ]
781
781
 
782
782
  [[package]]