@agents-at-scale/ark 0.1.46 → 0.1.49

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 (65) hide show
  1. package/.arkrc.template.yaml +51 -0
  2. package/README.md +4 -0
  3. package/dist/arkServices.js +22 -8
  4. package/dist/arkServices.spec.js +6 -0
  5. package/dist/commands/agents/index.d.ts +1 -1
  6. package/dist/commands/agents/index.js +4 -2
  7. package/dist/commands/chat/index.js +1 -1
  8. package/dist/commands/completion/index.js +19 -5
  9. package/dist/commands/dashboard/index.d.ts +2 -2
  10. package/dist/commands/dashboard/index.js +5 -4
  11. package/dist/commands/export/index.d.ts +3 -0
  12. package/dist/commands/export/index.js +73 -0
  13. package/dist/commands/export/index.spec.d.ts +1 -0
  14. package/dist/commands/export/index.spec.js +145 -0
  15. package/dist/commands/import/index.d.ts +3 -0
  16. package/dist/commands/import/index.js +27 -0
  17. package/dist/commands/import/index.spec.d.ts +1 -0
  18. package/dist/commands/import/index.spec.js +46 -0
  19. package/dist/commands/install/index.js +20 -10
  20. package/dist/commands/marketplace/index.js +51 -23
  21. package/dist/commands/marketplace/index.spec.d.ts +1 -0
  22. package/dist/commands/marketplace/index.spec.js +88 -0
  23. package/dist/commands/memory/index.js +9 -4
  24. package/dist/commands/models/index.d.ts +1 -1
  25. package/dist/commands/models/index.js +4 -2
  26. package/dist/commands/query/index.d.ts +1 -1
  27. package/dist/commands/query/index.js +6 -2
  28. package/dist/commands/status/index.js +7 -2
  29. package/dist/commands/teams/index.d.ts +1 -1
  30. package/dist/commands/teams/index.js +4 -2
  31. package/dist/commands/uninstall/index.js +20 -10
  32. package/dist/index.js +4 -0
  33. package/dist/lib/arkApiProxy.d.ts +1 -1
  34. package/dist/lib/arkApiProxy.js +2 -2
  35. package/dist/lib/arkServiceProxy.d.ts +3 -1
  36. package/dist/lib/arkServiceProxy.js +34 -1
  37. package/dist/lib/arkServiceProxy.spec.d.ts +1 -0
  38. package/dist/lib/arkServiceProxy.spec.js +100 -0
  39. package/dist/lib/chatClient.d.ts +2 -0
  40. package/dist/lib/chatClient.js +10 -2
  41. package/dist/lib/config.d.ts +17 -1
  42. package/dist/lib/config.js +62 -7
  43. package/dist/lib/config.spec.js +103 -0
  44. package/dist/lib/constants.d.ts +3 -0
  45. package/dist/lib/constants.js +5 -0
  46. package/dist/lib/executeQuery.d.ts +1 -0
  47. package/dist/lib/executeQuery.js +21 -4
  48. package/dist/lib/executeQuery.spec.js +4 -1
  49. package/dist/lib/kubectl.d.ts +3 -0
  50. package/dist/lib/kubectl.js +68 -0
  51. package/dist/lib/kubectl.spec.js +16 -0
  52. package/dist/lib/marketplaceFetcher.d.ts +6 -0
  53. package/dist/lib/marketplaceFetcher.js +80 -0
  54. package/dist/lib/marketplaceFetcher.spec.d.ts +1 -0
  55. package/dist/lib/marketplaceFetcher.spec.js +225 -0
  56. package/dist/lib/types.d.ts +1 -0
  57. package/dist/marketplaceServices.d.ts +15 -6
  58. package/dist/marketplaceServices.js +38 -40
  59. package/dist/marketplaceServices.spec.d.ts +1 -0
  60. package/dist/marketplaceServices.spec.js +74 -0
  61. package/dist/types/marketplace.d.ts +37 -0
  62. package/dist/types/marketplace.js +1 -0
  63. package/dist/ui/MainMenu.js +6 -2
  64. package/package.json +4 -2
  65. package/templates/marketplace/marketplace.json.example +59 -0
@@ -4,11 +4,19 @@ export interface ChatConfig {
4
4
  streaming?: boolean;
5
5
  outputFormat?: 'text' | 'markdown';
6
6
  }
7
+ export interface MarketplaceConfig {
8
+ repoUrl?: string;
9
+ registry?: string;
10
+ }
7
11
  export interface ArkConfig {
8
12
  chat?: ChatConfig;
13
+ marketplace?: MarketplaceConfig;
9
14
  services?: {
10
- [serviceName: string]: Partial<ArkService>;
15
+ reusePortForwards?: boolean;
16
+ [serviceName: string]: Partial<ArkService> | boolean | undefined;
11
17
  };
18
+ queryTimeout?: string;
19
+ defaultExportTypes?: string[];
12
20
  clusterInfo?: ClusterInfo;
13
21
  }
14
22
  /**
@@ -30,3 +38,11 @@ export declare function getConfigPaths(): {
30
38
  * Format config as YAML for display
31
39
  */
32
40
  export declare function formatConfig(config: ArkConfig): string;
41
+ /**
42
+ * Get marketplace repository URL from config
43
+ */
44
+ export declare function getMarketplaceRepoUrl(): string;
45
+ /**
46
+ * Get marketplace registry from config
47
+ */
48
+ export declare function getMarketplaceRegistry(): string;
@@ -16,6 +16,13 @@ export function loadConfig() {
16
16
  streaming: true,
17
17
  outputFormat: 'text',
18
18
  },
19
+ marketplace: {
20
+ repoUrl: 'https://github.com/mckinsey/agents-at-scale-marketplace',
21
+ registry: 'oci://ghcr.io/mckinsey/agents-at-scale-marketplace/charts',
22
+ },
23
+ services: {
24
+ reusePortForwards: false,
25
+ },
19
26
  };
20
27
  // Load user config from home directory
21
28
  const userConfigPath = path.join(os.homedir(), '.arkrc.yaml');
@@ -44,9 +51,7 @@ export function loadConfig() {
44
51
  // Apply environment variable overrides
45
52
  if (process.env.ARK_CHAT_STREAMING !== undefined) {
46
53
  config.chat = config.chat || {};
47
- config.chat.streaming =
48
- process.env.ARK_CHAT_STREAMING === '1' ||
49
- process.env.ARK_CHAT_STREAMING === 'true';
54
+ config.chat.streaming = process.env.ARK_CHAT_STREAMING === '1';
50
55
  }
51
56
  if (process.env.ARK_CHAT_OUTPUT_FORMAT !== undefined) {
52
57
  config.chat = config.chat || {};
@@ -55,6 +60,22 @@ export function loadConfig() {
55
60
  config.chat.outputFormat = format;
56
61
  }
57
62
  }
63
+ if (process.env.ARK_QUERY_TIMEOUT !== undefined) {
64
+ config.queryTimeout = process.env.ARK_QUERY_TIMEOUT;
65
+ }
66
+ if (process.env.ARK_MARKETPLACE_REPO_URL !== undefined) {
67
+ config.marketplace = config.marketplace || {};
68
+ config.marketplace.repoUrl = process.env.ARK_MARKETPLACE_REPO_URL;
69
+ }
70
+ if (process.env.ARK_MARKETPLACE_REGISTRY !== undefined) {
71
+ config.marketplace = config.marketplace || {};
72
+ config.marketplace.registry = process.env.ARK_MARKETPLACE_REGISTRY;
73
+ }
74
+ if (process.env.ARK_SERVICES_REUSE_PORT_FORWARDS !== undefined) {
75
+ config.services = config.services || {};
76
+ config.services.reusePortForwards =
77
+ process.env.ARK_SERVICES_REUSE_PORT_FORWARDS === '1';
78
+ }
58
79
  return config;
59
80
  }
60
81
  /**
@@ -70,15 +91,35 @@ function mergeConfig(target, source) {
70
91
  target.chat.outputFormat = source.chat.outputFormat;
71
92
  }
72
93
  }
94
+ if (source.marketplace) {
95
+ target.marketplace = target.marketplace || {};
96
+ if (source.marketplace.repoUrl !== undefined) {
97
+ target.marketplace.repoUrl = source.marketplace.repoUrl;
98
+ }
99
+ if (source.marketplace.registry !== undefined) {
100
+ target.marketplace.registry = source.marketplace.registry;
101
+ }
102
+ }
73
103
  if (source.services) {
74
104
  target.services = target.services || {};
105
+ if (source.services.reusePortForwards !== undefined) {
106
+ target.services.reusePortForwards = source.services.reusePortForwards;
107
+ }
75
108
  for (const [serviceName, overrides] of Object.entries(source.services)) {
76
- target.services[serviceName] = {
77
- ...target.services[serviceName],
78
- ...overrides,
79
- };
109
+ if (serviceName !== 'reusePortForwards' && typeof overrides === 'object') {
110
+ target.services[serviceName] = {
111
+ ...target.services[serviceName],
112
+ ...overrides,
113
+ };
114
+ }
80
115
  }
81
116
  }
117
+ if (source.queryTimeout !== undefined) {
118
+ target.queryTimeout = source.queryTimeout;
119
+ }
120
+ if (source.defaultExportTypes) {
121
+ target.defaultExportTypes = source.defaultExportTypes;
122
+ }
82
123
  }
83
124
  /**
84
125
  * Get the paths checked for config files
@@ -95,3 +136,17 @@ export function getConfigPaths() {
95
136
  export function formatConfig(config) {
96
137
  return yaml.stringify(config);
97
138
  }
139
+ /**
140
+ * Get marketplace repository URL from config
141
+ */
142
+ export function getMarketplaceRepoUrl() {
143
+ const config = loadConfig();
144
+ return config.marketplace.repoUrl;
145
+ }
146
+ /**
147
+ * Get marketplace registry from config
148
+ */
149
+ export function getMarketplaceRegistry() {
150
+ const config = loadConfig();
151
+ return config.marketplace.registry;
152
+ }
@@ -35,6 +35,13 @@ describe('config', () => {
35
35
  streaming: true,
36
36
  outputFormat: 'text',
37
37
  },
38
+ marketplace: {
39
+ repoUrl: 'https://github.com/mckinsey/agents-at-scale-marketplace',
40
+ registry: 'oci://ghcr.io/mckinsey/agents-at-scale-marketplace/charts',
41
+ },
42
+ services: {
43
+ reusePortForwards: false,
44
+ },
38
45
  });
39
46
  });
40
47
  it('loads and merges configs in order: defaults, user, project', () => {
@@ -66,6 +73,28 @@ describe('config', () => {
66
73
  expect(config.chat?.streaming).toBe(true);
67
74
  expect(config.chat?.outputFormat).toBe('markdown');
68
75
  });
76
+ it('loads queryTimeout from config file', () => {
77
+ mockFs.existsSync.mockReturnValue(true);
78
+ mockFs.readFileSync.mockReturnValue('yaml');
79
+ mockYaml.parse.mockReturnValue({ queryTimeout: '30m' });
80
+ const config = loadConfig();
81
+ expect(config.queryTimeout).toBe('30m');
82
+ });
83
+ it('loads defaultExportTypes from config file', () => {
84
+ mockFs.existsSync.mockReturnValue(true);
85
+ mockFs.readFileSync.mockReturnValue('yaml');
86
+ mockYaml.parse.mockReturnValue({ defaultExportTypes: ['agents', 'teams'] });
87
+ const config = loadConfig();
88
+ expect(config.defaultExportTypes).toEqual(['agents', 'teams']);
89
+ });
90
+ it('ARK_QUERY_TIMEOUT environment variable overrides config', () => {
91
+ mockFs.existsSync.mockReturnValue(true);
92
+ mockFs.readFileSync.mockReturnValue('yaml');
93
+ mockYaml.parse.mockReturnValue({ queryTimeout: '5m' });
94
+ process.env.ARK_QUERY_TIMEOUT = '1h';
95
+ const config = loadConfig();
96
+ expect(config.queryTimeout).toBe('1h');
97
+ });
69
98
  it('throws error for invalid YAML', () => {
70
99
  const userConfigPath = path.join(os.homedir(), '.arkrc.yaml');
71
100
  mockFs.existsSync.mockImplementation((path) => path === userConfigPath);
@@ -96,4 +125,78 @@ describe('config', () => {
96
125
  expect(mockYaml.stringify).toHaveBeenCalledWith(config);
97
126
  expect(result).toBe('formatted');
98
127
  });
128
+ it('loads marketplace config from config file', () => {
129
+ mockFs.existsSync.mockReturnValue(true);
130
+ mockFs.readFileSync.mockReturnValue('yaml');
131
+ mockYaml.parse.mockReturnValue({
132
+ marketplace: {
133
+ repoUrl: 'https://example.com/my-marketplace',
134
+ registry: 'oci://example.com/charts',
135
+ },
136
+ });
137
+ const config = loadConfig();
138
+ expect(config.marketplace?.repoUrl).toBe('https://example.com/my-marketplace');
139
+ expect(config.marketplace?.registry).toBe('oci://example.com/charts');
140
+ });
141
+ it('marketplace environment variables override config', () => {
142
+ mockFs.existsSync.mockReturnValue(true);
143
+ mockFs.readFileSync.mockReturnValue('yaml');
144
+ mockYaml.parse.mockReturnValue({
145
+ marketplace: {
146
+ repoUrl: 'https://example.com/my-marketplace',
147
+ registry: 'oci://example.com/charts',
148
+ },
149
+ });
150
+ process.env.ARK_MARKETPLACE_REPO_URL = 'https://custom.com/marketplace';
151
+ process.env.ARK_MARKETPLACE_REGISTRY = 'oci://custom.com/charts';
152
+ const config = loadConfig();
153
+ expect(config.marketplace?.repoUrl).toBe('https://custom.com/marketplace');
154
+ expect(config.marketplace?.registry).toBe('oci://custom.com/charts');
155
+ });
156
+ });
157
+ describe('marketplace helpers', () => {
158
+ const originalEnv = process.env;
159
+ beforeEach(() => {
160
+ jest.clearAllMocks();
161
+ process.env = { ...originalEnv };
162
+ });
163
+ afterEach(() => {
164
+ process.env = originalEnv;
165
+ });
166
+ it('getMarketplaceRepoUrl returns custom config value', async () => {
167
+ mockFs.existsSync.mockReturnValue(true);
168
+ mockFs.readFileSync.mockReturnValue('yaml');
169
+ mockYaml.parse.mockReturnValue({
170
+ marketplace: {
171
+ repoUrl: 'https://custom-repo.com/marketplace',
172
+ },
173
+ });
174
+ jest.resetModules();
175
+ const { getMarketplaceRepoUrl } = await import('./config.js');
176
+ expect(getMarketplaceRepoUrl()).toBe('https://custom-repo.com/marketplace');
177
+ });
178
+ it('getMarketplaceRepoUrl returns default value', async () => {
179
+ mockFs.existsSync.mockReturnValue(false);
180
+ jest.resetModules();
181
+ const { getMarketplaceRepoUrl } = await import('./config.js');
182
+ expect(getMarketplaceRepoUrl()).toBe('https://github.com/mckinsey/agents-at-scale-marketplace');
183
+ });
184
+ it('getMarketplaceRegistry returns custom config value', async () => {
185
+ mockFs.existsSync.mockReturnValue(true);
186
+ mockFs.readFileSync.mockReturnValue('yaml');
187
+ mockYaml.parse.mockReturnValue({
188
+ marketplace: {
189
+ registry: 'oci://custom-registry.com/charts',
190
+ },
191
+ });
192
+ jest.resetModules();
193
+ const { getMarketplaceRegistry } = await import('./config.js');
194
+ expect(getMarketplaceRegistry()).toBe('oci://custom-registry.com/charts');
195
+ });
196
+ it('getMarketplaceRegistry returns default value', async () => {
197
+ mockFs.existsSync.mockReturnValue(false);
198
+ jest.resetModules();
199
+ const { getMarketplaceRegistry } = await import('./config.js');
200
+ expect(getMarketplaceRegistry()).toBe('oci://ghcr.io/mckinsey/agents-at-scale-marketplace/charts');
201
+ });
99
202
  });
@@ -1,3 +1,6 @@
1
1
  export declare const QUERY_ANNOTATIONS: {
2
2
  readonly A2A_CONTEXT_ID: "ark.mckinsey.com/a2a-context-id";
3
3
  };
4
+ export declare const EVENT_ANNOTATIONS: {
5
+ readonly EVENT_DATA: "ark.mckinsey.com/event-data";
6
+ };
@@ -6,3 +6,8 @@ export const QUERY_ANNOTATIONS = {
6
6
  // A2A context ID annotation (goes to K8s annotations)
7
7
  A2A_CONTEXT_ID: `${ARK_PREFIX}a2a-context-id`,
8
8
  };
9
+ // Event annotation constants
10
+ export const EVENT_ANNOTATIONS = {
11
+ // Event data annotation for structured event data
12
+ EVENT_DATA: `${ARK_PREFIX}event-data`,
13
+ };
@@ -11,6 +11,7 @@ export interface QueryOptions {
11
11
  verbose?: boolean;
12
12
  outputFormat?: string;
13
13
  sessionId?: string;
14
+ conversationId?: string;
14
15
  }
15
16
  export declare function executeQuery(options: QueryOptions): Promise<void>;
16
17
  /**
@@ -7,6 +7,8 @@ import chalk from 'chalk';
7
7
  import { ExitCodes } from './errors.js';
8
8
  import { ArkApiProxy } from './arkApiProxy.js';
9
9
  import { ChatClient } from './chatClient.js';
10
+ import { watchEventsLive } from './kubectl.js';
11
+ import { loadConfig } from './config.js';
10
12
  export async function executeQuery(options) {
11
13
  if (options.outputFormat) {
12
14
  return executeQueryWithFormat(options);
@@ -14,7 +16,8 @@ export async function executeQuery(options) {
14
16
  let arkApiProxy;
15
17
  const spinner = ora('Connecting to Ark API...').start();
16
18
  try {
17
- arkApiProxy = new ArkApiProxy();
19
+ const config = loadConfig();
20
+ arkApiProxy = new ArkApiProxy(undefined, config.services?.reusePortForwards ?? false);
18
21
  const arkApiClient = await arkApiProxy.start();
19
22
  const chatClient = new ChatClient(arkApiClient);
20
23
  spinner.text = 'Executing query...';
@@ -27,9 +30,14 @@ export async function executeQuery(options) {
27
30
  let lastAgentName;
28
31
  let headerShown = false;
29
32
  let firstOutput = true;
30
- // Get sessionId from option or environment variable
31
33
  const sessionId = options.sessionId || process.env.ARK_SESSION_ID;
32
- await chatClient.sendMessage(targetId, messages, { streamingEnabled: true, sessionId }, (chunk, toolCalls, arkMetadata) => {
34
+ const conversationId = options.conversationId || process.env.ARK_CONVERSATION_ID;
35
+ await chatClient.sendMessage(targetId, messages, {
36
+ streamingEnabled: true,
37
+ sessionId,
38
+ conversationId,
39
+ queryTimeout: options.timeout,
40
+ }, (chunk, toolCalls, arkMetadata) => {
33
41
  if (firstOutput) {
34
42
  spinner.stop();
35
43
  firstOutput = false;
@@ -105,6 +113,9 @@ async function executeQueryWithFormat(options) {
105
113
  ...((options.sessionId || process.env.ARK_SESSION_ID) && {
106
114
  sessionId: options.sessionId || process.env.ARK_SESSION_ID,
107
115
  }),
116
+ ...((options.conversationId || process.env.ARK_CONVERSATION_ID) && {
117
+ conversationId: options.conversationId || process.env.ARK_CONVERSATION_ID,
118
+ }),
108
119
  targets: [
109
120
  {
110
121
  type: options.targetType,
@@ -118,6 +129,12 @@ async function executeQueryWithFormat(options) {
118
129
  input: JSON.stringify(queryManifest),
119
130
  stdio: ['pipe', 'pipe', 'pipe'],
120
131
  });
132
+ // Give Kubernetes a moment to process the resource before watching
133
+ await new Promise((resolve) => setTimeout(resolve, 100));
134
+ if (options.outputFormat === 'events') {
135
+ await watchEventsLive(queryName);
136
+ return;
137
+ }
121
138
  const timeoutSeconds = 300;
122
139
  await execa('kubectl', [
123
140
  'wait',
@@ -134,7 +151,7 @@ async function executeQueryWithFormat(options) {
134
151
  console.log(stdout);
135
152
  }
136
153
  else {
137
- console.error(chalk.red(`Invalid output format: ${options.outputFormat}. Use: yaml, json, or name`));
154
+ console.error(chalk.red(`Invalid output format: ${options.outputFormat}. Use: yaml, json, name, or events`));
138
155
  process.exit(ExitCodes.CliError);
139
156
  }
140
157
  }
@@ -206,9 +206,12 @@ describe('executeQuery', () => {
206
206
  expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringMatching(/cli-query-\d+/));
207
207
  });
208
208
  it('should include sessionId in query manifest when outputFormat is specified', async () => {
209
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
209
210
  let appliedManifest = '';
210
211
  mockExeca.mockImplementation(async (command, args) => {
211
- if (args.includes('apply') && args.includes('-f') && args.includes('-')) {
212
+ if (args.includes('apply') &&
213
+ args.includes('-f') &&
214
+ args.includes('-')) {
212
215
  // Capture the stdin input
213
216
  const stdinIndex = args.indexOf('-');
214
217
  if (stdinIndex >= 0 && args[stdinIndex + 1]) {
@@ -6,10 +6,13 @@ interface K8sResource {
6
6
  }
7
7
  export declare function getResource<T extends K8sResource>(resourceType: string, name: string): Promise<T>;
8
8
  export declare function listResources<T extends K8sResource>(resourceType: string, options?: {
9
+ namespace?: string;
10
+ labels?: string;
9
11
  sortBy?: string;
10
12
  }): Promise<T[]>;
11
13
  export declare function deleteResource(resourceType: string, name?: string, options?: {
12
14
  all?: boolean;
13
15
  }): Promise<void>;
14
16
  export declare function replaceResource<T extends K8sResource>(resource: T): Promise<T>;
17
+ export declare function watchEventsLive(queryName: string): Promise<void>;
15
18
  export {};
@@ -1,4 +1,6 @@
1
1
  import { execa } from 'execa';
2
+ import chalk from 'chalk';
3
+ import { EVENT_ANNOTATIONS } from './constants.js';
2
4
  export async function getResource(resourceType, name) {
3
5
  if (name === '@latest') {
4
6
  const result = await execa('kubectl', [
@@ -23,6 +25,12 @@ export async function listResources(resourceType, options) {
23
25
  if (options?.sortBy) {
24
26
  args.push(`--sort-by=${options.sortBy}`);
25
27
  }
28
+ if (options?.namespace) {
29
+ args.push('-n', options.namespace);
30
+ }
31
+ if (options?.labels) {
32
+ args.push('-l', options.labels);
33
+ }
26
34
  args.push('-o', 'json');
27
35
  const result = await execa('kubectl', args, { stdio: 'pipe' });
28
36
  const data = JSON.parse(result.stdout);
@@ -45,3 +53,63 @@ export async function replaceResource(resource) {
45
53
  });
46
54
  return JSON.parse(result.stdout);
47
55
  }
56
+ export async function watchEventsLive(queryName) {
57
+ const seenEvents = new Set();
58
+ const pollEvents = async () => {
59
+ try {
60
+ const { stdout } = await execa('kubectl', [
61
+ 'get',
62
+ 'events',
63
+ '--field-selector',
64
+ `involvedObject.name=${queryName}`,
65
+ '-o',
66
+ 'json',
67
+ ]);
68
+ const eventsData = JSON.parse(stdout);
69
+ for (const event of eventsData.items || []) {
70
+ const eventId = event.metadata?.uid;
71
+ if (eventId && !seenEvents.has(eventId)) {
72
+ seenEvents.add(eventId);
73
+ const annotations = event.metadata?.annotations || {};
74
+ const eventData = annotations[EVENT_ANNOTATIONS.EVENT_DATA];
75
+ if (eventData) {
76
+ const now = new Date();
77
+ const hours = now.getHours().toString().padStart(2, '0');
78
+ const minutes = now.getMinutes().toString().padStart(2, '0');
79
+ const seconds = now.getSeconds().toString().padStart(2, '0');
80
+ const millis = now.getMilliseconds().toString().padStart(3, '0');
81
+ const timestamp = `${hours}:${minutes}:${seconds}.${millis}`;
82
+ const reason = event.reason || 'Unknown';
83
+ const eventType = event.type || 'Normal';
84
+ const colorCode = eventType === 'Normal' ? 32 : eventType === 'Warning' ? 33 : 31;
85
+ console.log(`${timestamp} \x1b[${colorCode}m${reason}\x1b[0m ${eventData}`);
86
+ }
87
+ }
88
+ }
89
+ // eslint-disable-next-line no-empty, @typescript-eslint/no-unused-vars
90
+ }
91
+ catch (error) { }
92
+ };
93
+ const pollInterval = setInterval(pollEvents, 200);
94
+ const timeoutSeconds = 300;
95
+ const waitProcess = execa('kubectl', [
96
+ 'wait',
97
+ '--for=condition=Completed',
98
+ `query/${queryName}`,
99
+ `--timeout=${timeoutSeconds}s`,
100
+ ], {
101
+ timeout: timeoutSeconds * 1000,
102
+ });
103
+ try {
104
+ await waitProcess;
105
+ await pollEvents();
106
+ await new Promise((resolve) => setTimeout(resolve, 200));
107
+ await pollEvents();
108
+ }
109
+ catch (error) {
110
+ console.error(chalk.red('Query wait failed:', error instanceof Error ? error.message : 'Unknown error'));
111
+ }
112
+ finally {
113
+ clearInterval(pollInterval);
114
+ }
115
+ }
@@ -152,6 +152,22 @@ describe('kubectl', () => {
152
152
  'json',
153
153
  ], { stdio: 'pipe' });
154
154
  });
155
+ it('should pass namespace and label filters', async () => {
156
+ const result = await listResources('queries', {
157
+ labels: 'app=test',
158
+ namespace: 'foo'
159
+ });
160
+ expect(mockExeca).toHaveBeenCalledWith('kubectl', [
161
+ 'get',
162
+ 'queries',
163
+ '-n',
164
+ 'foo',
165
+ '-l',
166
+ 'app=test',
167
+ '-o',
168
+ 'json',
169
+ ], { stdio: 'pipe' });
170
+ });
155
171
  it('should handle kubectl errors when listing resources', async () => {
156
172
  mockExeca.mockRejectedValue(new Error('kubectl connection error'));
157
173
  await expect(listResources('queries')).rejects.toThrow('kubectl connection error');
@@ -0,0 +1,6 @@
1
+ import type { ArkService, ServiceCollection } from '../types/arkService.js';
2
+ import type { AnthropicMarketplaceManifest, AnthropicMarketplaceItem } from '../types/marketplace.js';
3
+ export declare function fetchMarketplaceManifest(): Promise<AnthropicMarketplaceManifest | null>;
4
+ export declare function mapMarketplaceItemToArkService(item: AnthropicMarketplaceItem, registry?: string): ArkService;
5
+ export declare function getMarketplaceServicesFromManifest(): Promise<ServiceCollection | null>;
6
+ export declare function getMarketplaceAgentsFromManifest(): Promise<ServiceCollection | null>;
@@ -0,0 +1,80 @@
1
+ import axios from 'axios';
2
+ import { getMarketplaceRepoUrl, getMarketplaceRegistry } from './config.js';
3
+ export async function fetchMarketplaceManifest() {
4
+ const repoUrl = getMarketplaceRepoUrl();
5
+ const manifestUrl = `${repoUrl}/raw/main/marketplace.json`;
6
+ try {
7
+ const response = await axios.get(manifestUrl, {
8
+ timeout: 10000,
9
+ headers: {
10
+ Accept: 'application/json',
11
+ },
12
+ });
13
+ return response.data;
14
+ }
15
+ catch (error) {
16
+ if (axios.isAxiosError(error)) {
17
+ if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
18
+ return null;
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+ }
24
+ export function mapMarketplaceItemToArkService(item, registry) {
25
+ const defaultRegistry = registry || getMarketplaceRegistry();
26
+ const serviceName = item.name
27
+ .toLowerCase()
28
+ .replace(/[^a-z0-9-]/g, '-')
29
+ .replace(/^-+|-+$/g, '');
30
+ const chartPath = item.ark?.chartPath || `${defaultRegistry}/${serviceName}`;
31
+ return {
32
+ name: serviceName,
33
+ helmReleaseName: item.ark?.helmReleaseName || serviceName,
34
+ description: item.description,
35
+ enabled: true,
36
+ category: 'marketplace',
37
+ namespace: item.ark?.namespace || serviceName,
38
+ chartPath,
39
+ installArgs: item.ark?.installArgs || ['--create-namespace'],
40
+ k8sServiceName: item.ark?.k8sServiceName || serviceName,
41
+ k8sServicePort: item.ark?.k8sServicePort,
42
+ k8sPortForwardLocalPort: item.ark?.k8sPortForwardLocalPort,
43
+ k8sDeploymentName: item.ark?.k8sDeploymentName || serviceName,
44
+ k8sDevDeploymentName: item.ark?.k8sDevDeploymentName,
45
+ };
46
+ }
47
+ export async function getMarketplaceServicesFromManifest() {
48
+ const manifest = await fetchMarketplaceManifest();
49
+ if (!manifest || !manifest.items) {
50
+ return null;
51
+ }
52
+ const services = {};
53
+ for (const item of manifest.items) {
54
+ if (item.ark && item.type === 'service') {
55
+ const serviceName = item.name
56
+ .toLowerCase()
57
+ .replace(/[^a-z0-9-]/g, '-')
58
+ .replace(/^-+|-+$/g, '');
59
+ services[serviceName] = mapMarketplaceItemToArkService(item);
60
+ }
61
+ }
62
+ return Object.keys(services).length > 0 ? services : null;
63
+ }
64
+ export async function getMarketplaceAgentsFromManifest() {
65
+ const manifest = await fetchMarketplaceManifest();
66
+ if (!manifest || !manifest.items) {
67
+ return null;
68
+ }
69
+ const agents = {};
70
+ for (const item of manifest.items) {
71
+ if (item.ark && item.type === 'agent') {
72
+ const agentName = item.name
73
+ .toLowerCase()
74
+ .replace(/[^a-z0-9-]/g, '-')
75
+ .replace(/^-+|-+$/g, '');
76
+ agents[agentName] = mapMarketplaceItemToArkService(item);
77
+ }
78
+ }
79
+ return Object.keys(agents).length > 0 ? agents : null;
80
+ }
@@ -0,0 +1 @@
1
+ export {};