@kya-os/create-mcpi-app 1.7.36 → 1.7.38-canary.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 (50) hide show
  1. package/ARCHITECTURE_ANALYSIS.md +392 -0
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/helpers/config-builder.d.ts +1 -1
  4. package/dist/helpers/config-builder.d.ts.map +1 -1
  5. package/dist/helpers/config-builder.js +1 -1
  6. package/dist/helpers/config-builder.js.map +1 -1
  7. package/dist/helpers/create.d.ts +1 -1
  8. package/dist/helpers/create.d.ts.map +1 -1
  9. package/dist/helpers/fetch-cloudflare-mcpi-template.d.ts.map +1 -1
  10. package/dist/helpers/fetch-cloudflare-mcpi-template.js +2 -1
  11. package/dist/helpers/fetch-cloudflare-mcpi-template.js.map +1 -1
  12. package/dist/helpers/generate-config.d.ts.map +1 -1
  13. package/dist/helpers/generate-config.js.map +1 -1
  14. package/dist/index.js +18 -12
  15. package/dist/index.js.map +1 -1
  16. package/dist/utils/fetch-remote-config.d.ts +74 -0
  17. package/dist/utils/fetch-remote-config.d.ts.map +1 -0
  18. package/dist/utils/fetch-remote-config.js +109 -0
  19. package/dist/utils/fetch-remote-config.js.map +1 -0
  20. package/package.json +7 -12
  21. package/src/helpers/config-builder.ts +1 -1
  22. package/src/helpers/create.ts +1 -1
  23. package/src/helpers/fetch-cloudflare-mcpi-template.ts +2 -1
  24. package/src/helpers/generate-config.ts +0 -1
  25. package/src/index.ts +66 -26
  26. package/src/utils/__tests__/fetch-remote-config.test.ts +271 -0
  27. package/src/utils/fetch-remote-config.ts +179 -0
  28. package/.turbo/turbo-build.log +0 -4
  29. package/.turbo/turbo-test$colon$coverage.log +0 -305
  30. package/.turbo/turbo-test.log +0 -483
  31. package/coverage/base.css +0 -224
  32. package/coverage/block-navigation.js +0 -87
  33. package/coverage/clover.xml +0 -268
  34. package/coverage/config-builder.ts.html +0 -580
  35. package/coverage/coverage-final.json +0 -7
  36. package/coverage/favicon.png +0 -0
  37. package/coverage/fetch-cloudflare-mcpi-template.ts.html +0 -7345
  38. package/coverage/generate-config.ts.html +0 -442
  39. package/coverage/generate-identity.ts.html +0 -574
  40. package/coverage/index.html +0 -191
  41. package/coverage/install.ts.html +0 -322
  42. package/coverage/prettify.css +0 -1
  43. package/coverage/prettify.js +0 -2
  44. package/coverage/sort-arrow-sprite.png +0 -0
  45. package/coverage/sorter.js +0 -210
  46. package/coverage/validate-project-structure.ts.html +0 -466
  47. package/dist/helpers/__tests__/config-builder.spec.d.ts +0 -8
  48. package/dist/helpers/__tests__/config-builder.spec.d.ts.map +0 -1
  49. package/dist/helpers/__tests__/config-builder.spec.js +0 -182
  50. package/dist/helpers/__tests__/config-builder.spec.js.map +0 -1
package/src/index.ts CHANGED
@@ -15,8 +15,10 @@ import {
15
15
  validateProjectName,
16
16
  validateDirectoryAvailability,
17
17
  } from "./utils/validate-project-name.js";
18
- import { ScaffolderResult, ScaffolderOptions } from "@kya-os/contracts";
19
- import { resetIdentity, regenerateIdentity } from "./helpers/identity-manager.js";
18
+ import {
19
+ resetIdentity,
20
+ regenerateIdentity,
21
+ } from "./helpers/identity-manager.js";
20
22
 
21
23
  checkNodeVersion();
22
24
 
@@ -44,10 +46,7 @@ const program = new Command()
44
46
  .option("--use-pnpm", "Use pnpm as package manager")
45
47
  .option("--skip-install", "Skip installing dependencies", false)
46
48
  .option("--skip-identity", "Skip identity generation", false)
47
- .option(
48
- "--no-identity",
49
- "Create plain MCP project without identity features"
50
- )
49
+ .option("--no-identity", "Create plain MCP project without identity features")
51
50
  .option("--vercel", "Add Vercel support for deployment", false)
52
51
  .option("--http", "Enable HTTP transport", false)
53
52
  .option("--stdio", "Enable STDIO transport", false)
@@ -55,11 +54,18 @@ const program = new Command()
55
54
  .option("--mcpi-version <version>", "Specify MCP-I version (e.g., ^1.2.7)")
56
55
  .option("--no-animation", "Skip the black hole animation", false)
57
56
  .option("--fast", "Use shorter animation duration", false)
58
- .option("--platform <name>", "Deployment platform: node (default), cloudflare", "node")
57
+ .option(
58
+ "--platform <name>",
59
+ "Deployment platform: node (default), cloudflare",
60
+ "node"
61
+ )
59
62
  .option("--mode <name>", "Server mode: mcp-i (default), verifier", "mcp-i")
60
63
  .option("--template <name>", "[DEPRECATED] Use --platform and --mode instead")
61
64
  .option("--apikey <key>", "AgentShield API key for Cloudflare deployment")
62
- .option("--project <id>", "AgentShield Project ID for Cloudflare deployment (enables project-scoped tool protection)")
65
+ .option(
66
+ "--project <id>",
67
+ "AgentShield Project ID for Cloudflare deployment (enables project-scoped tool protection)"
68
+ )
63
69
  .action(async (projectDir, options) => {
64
70
  console.log(chalk.bold(`\ncreate-mcpi-app@${packageJson.version}`));
65
71
 
@@ -122,7 +128,11 @@ const program = new Command()
122
128
 
123
129
  // Backward compatibility: map --template to --platform/--mode
124
130
  if (options.template) {
125
- console.log(chalk.yellow("⚠️ --template is deprecated. Use --platform and --mode instead."));
131
+ console.log(
132
+ chalk.yellow(
133
+ "⚠️ --template is deprecated. Use --platform and --mode instead."
134
+ )
135
+ );
126
136
  if (options.template === "cloudflare") {
127
137
  platform = "cloudflare";
128
138
  mode = "verifier";
@@ -144,7 +154,9 @@ const program = new Command()
144
154
  );
145
155
 
146
156
  if (!isValid) {
147
- console.error(chalk.red(`\n❌ Invalid platform/mode combination: ${platform}/${mode}`));
157
+ console.error(
158
+ chalk.red(`\n❌ Invalid platform/mode combination: ${platform}/${mode}`)
159
+ );
148
160
  console.log(chalk.yellow("\nSupported combinations:"));
149
161
  console.log(chalk.cyan(" - node + mcp-i (default)"));
150
162
  console.log(chalk.cyan(" - cloudflare + mcp-i"));
@@ -212,7 +224,11 @@ const program = new Command()
212
224
  }
213
225
 
214
226
  // Vercel deployment option (only for Node.js platform)
215
- if (!options.vercel && transports.includes("http") && platform === "node") {
227
+ if (
228
+ !options.vercel &&
229
+ transports.includes("http") &&
230
+ platform === "node"
231
+ ) {
216
232
  const vercelAnswers = await inquirer.prompt([
217
233
  {
218
234
  type: "confirm",
@@ -349,15 +365,27 @@ const program = new Command()
349
365
  }
350
366
  console.log();
351
367
  console.log("2. Create KV namespaces (creates both NONCE and PROOF):");
352
- console.log(` ${chalk.cyan(`${packageManager === "npm" ? "npm run" : packageManager} kv:create`)}`);
353
- console.log(chalk.gray(" Copy both namespace IDs from output and update wrangler.toml"));
368
+ console.log(
369
+ ` ${chalk.cyan(`${packageManager === "npm" ? "npm run" : packageManager} kv:create`)}`
370
+ );
371
+ console.log(
372
+ chalk.gray(
373
+ " Copy both namespace IDs from output and update wrangler.toml"
374
+ )
375
+ );
354
376
  console.log();
355
377
  console.log("3. Test locally:");
356
- console.log(` ${chalk.cyan(`${packageManager === "npm" ? "npm run" : packageManager} dev`)}`);
378
+ console.log(
379
+ ` ${chalk.cyan(`${packageManager === "npm" ? "npm run" : packageManager} dev`)}`
380
+ );
357
381
  console.log();
358
382
  console.log("4. Deploy to Cloudflare:");
359
- console.log(` ${chalk.cyan("npx wrangler login")} ${chalk.gray("(first time only)")}`);
360
- console.log(` ${chalk.cyan(`${packageManager === "npm" ? "npm run" : packageManager} deploy`)}`);
383
+ console.log(
384
+ ` ${chalk.cyan("npx wrangler login")} ${chalk.gray("(first time only)")}`
385
+ );
386
+ console.log(
387
+ ` ${chalk.cyan(`${packageManager === "npm" ? "npm run" : packageManager} deploy`)}`
388
+ );
361
389
  } else {
362
390
  // Default MCP-I server instructions
363
391
  // Show agent management with claim URL first
@@ -449,30 +477,42 @@ const program = new Command()
449
477
 
450
478
  // Add subcommand for resetting identity
451
479
  program
452
- .command('reset-identity')
453
- .description('Reset the current project\'s identity (removes identity files)')
454
- .option('-d, --dir <directory>', 'Project directory (defaults to current directory)')
480
+ .command("reset-identity")
481
+ .description("Reset the current project's identity (removes identity files)")
482
+ .option(
483
+ "-d, --dir <directory>",
484
+ "Project directory (defaults to current directory)"
485
+ )
455
486
  .action(async (options) => {
456
487
  try {
457
- const projectPath = options.dir ? path.resolve(process.cwd(), options.dir) : process.cwd();
488
+ const projectPath = options.dir
489
+ ? path.resolve(process.cwd(), options.dir)
490
+ : process.cwd();
458
491
  await resetIdentity(projectPath);
459
492
  } catch (error) {
460
- console.error(chalk.red('Failed to reset identity:'), error);
493
+ console.error(chalk.red("Failed to reset identity:"), error);
461
494
  process.exit(1);
462
495
  }
463
496
  });
464
497
 
465
498
  // Add subcommand for regenerating identity
466
499
  program
467
- .command('regenerate-identity')
468
- .description('Regenerate the current project\'s identity (creates new DID and keys)')
469
- .option('-d, --dir <directory>', 'Project directory (defaults to current directory)')
500
+ .command("regenerate-identity")
501
+ .description(
502
+ "Regenerate the current project's identity (creates new DID and keys)"
503
+ )
504
+ .option(
505
+ "-d, --dir <directory>",
506
+ "Project directory (defaults to current directory)"
507
+ )
470
508
  .action(async (options) => {
471
509
  try {
472
- const projectPath = options.dir ? path.resolve(process.cwd(), options.dir) : process.cwd();
510
+ const projectPath = options.dir
511
+ ? path.resolve(process.cwd(), options.dir)
512
+ : process.cwd();
473
513
  await regenerateIdentity(projectPath);
474
514
  } catch (error) {
475
- console.error(chalk.red('Failed to regenerate identity:'), error);
515
+ console.error(chalk.red("Failed to regenerate identity:"), error);
476
516
  process.exit(1);
477
517
  }
478
518
  });
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Tests for Remote Configuration Fetching
3
+ *
4
+ * Validates that remote config fetching works correctly with caching
5
+ * and fallback behavior.
6
+ *
7
+ * These tests mirror the tests in @kya-os/mcp-i-core to ensure
8
+ * behavior matches the original implementation.
9
+ */
10
+
11
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
12
+ import {
13
+ fetchRemoteConfig,
14
+ type RemoteConfigOptions,
15
+ type RemoteConfigCache
16
+ } from '../fetch-remote-config.js';
17
+ import type { MCPIConfig } from '@kya-os/contracts/config';
18
+
19
+ describe('fetchRemoteConfig', () => {
20
+ let mockFetch: ReturnType<typeof vi.fn>;
21
+ let mockCache: RemoteConfigCache;
22
+
23
+ beforeEach(() => {
24
+ mockFetch = vi.fn();
25
+ mockCache = {
26
+ get: vi.fn(),
27
+ set: vi.fn()
28
+ };
29
+ });
30
+
31
+ describe('Cache hit scenario', () => {
32
+ it('should return cached config if available and not expired', async () => {
33
+ const cachedConfig: MCPIConfig = {
34
+ environment: 'production',
35
+ identity: { enabled: true, environment: 'production' }
36
+ };
37
+
38
+ vi.mocked(mockCache.get).mockResolvedValue(
39
+ JSON.stringify({
40
+ config: cachedConfig,
41
+ expiresAt: Date.now() + 60000 // 1 minute in future
42
+ })
43
+ );
44
+
45
+ const options: RemoteConfigOptions = {
46
+ apiUrl: 'https://kya.vouched.id',
47
+ apiKey: 'test-key',
48
+ projectId: 'test-project',
49
+ fetchProvider: mockFetch
50
+ };
51
+
52
+ const result = await fetchRemoteConfig(options, mockCache);
53
+
54
+ expect(result).toEqual(cachedConfig);
55
+ expect(mockCache.get).toHaveBeenCalledWith('config:project:test-project');
56
+ expect(mockFetch).not.toHaveBeenCalled();
57
+ });
58
+
59
+ it('should fetch fresh config if cache expired', async () => {
60
+ const cachedConfig: MCPIConfig = {
61
+ environment: 'production'
62
+ };
63
+
64
+ vi.mocked(mockCache.get).mockResolvedValue(
65
+ JSON.stringify({
66
+ config: cachedConfig,
67
+ expiresAt: Date.now() - 1000 // Expired
68
+ })
69
+ );
70
+
71
+ const freshConfig: MCPIConfig = {
72
+ environment: 'production',
73
+ identity: { enabled: true, environment: 'production' }
74
+ };
75
+
76
+ mockFetch.mockResolvedValue({
77
+ ok: true,
78
+ json: async () => ({ success: true, data: freshConfig })
79
+ } as Response);
80
+
81
+ const options: RemoteConfigOptions = {
82
+ apiUrl: 'https://kya.vouched.id',
83
+ apiKey: 'test-key',
84
+ projectId: 'test-project',
85
+ fetchProvider: mockFetch
86
+ };
87
+
88
+ const result = await fetchRemoteConfig(options, mockCache);
89
+
90
+ expect(result).toEqual(freshConfig);
91
+ expect(mockFetch).toHaveBeenCalled();
92
+ });
93
+ });
94
+
95
+ describe('Cache miss scenario', () => {
96
+ it('should fetch from API when cache is empty', async () => {
97
+ vi.mocked(mockCache.get).mockResolvedValue(null);
98
+
99
+ const config: MCPIConfig = {
100
+ environment: 'production',
101
+ identity: { enabled: true, environment: 'production' }
102
+ };
103
+
104
+ mockFetch.mockResolvedValue({
105
+ ok: true,
106
+ json: async () => ({ success: true, data: config })
107
+ } as Response);
108
+
109
+ const options: RemoteConfigOptions = {
110
+ apiUrl: 'https://kya.vouched.id',
111
+ apiKey: 'test-key',
112
+ projectId: 'test-project',
113
+ fetchProvider: mockFetch
114
+ };
115
+
116
+ const result = await fetchRemoteConfig(options, mockCache);
117
+
118
+ expect(result).toEqual(config);
119
+ expect(mockCache.set).toHaveBeenCalled();
120
+ });
121
+
122
+ it('should use agentDid when projectId not available', async () => {
123
+ vi.mocked(mockCache.get).mockResolvedValue(null);
124
+
125
+ const config: MCPIConfig = {
126
+ environment: 'production'
127
+ };
128
+
129
+ mockFetch.mockResolvedValue({
130
+ ok: true,
131
+ json: async () => ({ success: true, data: config })
132
+ } as Response);
133
+
134
+ const options: RemoteConfigOptions = {
135
+ apiUrl: 'https://kya.vouched.id',
136
+ apiKey: 'test-key',
137
+ agentDid: 'did:key:test',
138
+ fetchProvider: mockFetch
139
+ };
140
+
141
+ const result = await fetchRemoteConfig(options, mockCache);
142
+
143
+ expect(result).toEqual(config);
144
+ expect(mockCache.get).toHaveBeenCalledWith('config:agent:did:key:test');
145
+ });
146
+ });
147
+
148
+ describe('Error handling', () => {
149
+ it('should return null if API request fails', async () => {
150
+ vi.mocked(mockCache.get).mockResolvedValue(null);
151
+
152
+ mockFetch.mockResolvedValue({
153
+ ok: false,
154
+ status: 404,
155
+ statusText: 'Not Found'
156
+ } as Response);
157
+
158
+ const options: RemoteConfigOptions = {
159
+ apiUrl: 'https://kya.vouched.id',
160
+ apiKey: 'test-key',
161
+ projectId: 'test-project',
162
+ fetchProvider: mockFetch
163
+ };
164
+
165
+ const result = await fetchRemoteConfig(options, mockCache);
166
+
167
+ expect(result).toBeNull();
168
+ });
169
+
170
+ it('should return null if API throws error', async () => {
171
+ vi.mocked(mockCache.get).mockResolvedValue(null);
172
+
173
+ mockFetch.mockRejectedValue(new Error('Network error'));
174
+
175
+ const options: RemoteConfigOptions = {
176
+ apiUrl: 'https://kya.vouched.id',
177
+ apiKey: 'test-key',
178
+ projectId: 'test-project',
179
+ fetchProvider: mockFetch
180
+ };
181
+
182
+ const result = await fetchRemoteConfig(options, mockCache);
183
+
184
+ expect(result).toBeNull();
185
+ });
186
+
187
+ it('should return null if neither projectId nor agentDid provided', async () => {
188
+ const options: RemoteConfigOptions = {
189
+ apiUrl: 'https://kya.vouched.id',
190
+ apiKey: 'test-key',
191
+ fetchProvider: mockFetch
192
+ };
193
+
194
+ const result = await fetchRemoteConfig(options, mockCache);
195
+
196
+ expect(result).toBeNull();
197
+ expect(mockFetch).not.toHaveBeenCalled();
198
+ });
199
+
200
+ it('should handle cache read errors gracefully', async () => {
201
+ vi.mocked(mockCache.get).mockRejectedValue(new Error('Cache error'));
202
+
203
+ const config: MCPIConfig = {
204
+ environment: 'production'
205
+ };
206
+
207
+ mockFetch.mockResolvedValue({
208
+ ok: true,
209
+ json: async () => ({ success: true, data: config })
210
+ } as Response);
211
+
212
+ const options: RemoteConfigOptions = {
213
+ apiUrl: 'https://kya.vouched.id',
214
+ apiKey: 'test-key',
215
+ projectId: 'test-project',
216
+ fetchProvider: mockFetch
217
+ };
218
+
219
+ const result = await fetchRemoteConfig(options, mockCache);
220
+
221
+ expect(result).toEqual(config);
222
+ });
223
+ });
224
+
225
+ describe('Response format handling', () => {
226
+ it('should handle different API response formats', async () => {
227
+ vi.mocked(mockCache.get).mockResolvedValue(null);
228
+
229
+ const config: MCPIConfig = {
230
+ environment: 'production'
231
+ };
232
+
233
+ // Format 1: { config: {...} }
234
+ mockFetch.mockResolvedValueOnce({
235
+ ok: true,
236
+ json: async () => ({ config })
237
+ } as Response);
238
+
239
+ const options: RemoteConfigOptions = {
240
+ apiUrl: 'https://kya.vouched.id',
241
+ apiKey: 'test-key',
242
+ projectId: 'test-project',
243
+ fetchProvider: mockFetch
244
+ };
245
+
246
+ const result1 = await fetchRemoteConfig(options, mockCache);
247
+ expect(result1).toEqual(config);
248
+
249
+ // Format 2: { data: { config: {...} } }
250
+ vi.mocked(mockCache.get).mockResolvedValue(null);
251
+ mockFetch.mockResolvedValueOnce({
252
+ ok: true,
253
+ json: async () => ({ data: { config } })
254
+ } as Response);
255
+
256
+ const result2 = await fetchRemoteConfig(options, mockCache);
257
+ expect(result2).toEqual(config);
258
+
259
+ // Format 3: { success: true, data: {...} }
260
+ vi.mocked(mockCache.get).mockResolvedValue(null);
261
+ mockFetch.mockResolvedValueOnce({
262
+ ok: true,
263
+ json: async () => ({ success: true, data: config })
264
+ } as Response);
265
+
266
+ const result3 = await fetchRemoteConfig(options, mockCache);
267
+ expect(result3).toEqual(config);
268
+ });
269
+ });
270
+ });
271
+
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Remote Configuration Fetching
3
+ *
4
+ * Service for fetching configuration from remote APIs (AgentShield dashboard)
5
+ * with caching support for performance optimization.
6
+ *
7
+ * NOTE: This implementation is extracted from @kya-os/mcp-i-core/src/config/remote-config.ts
8
+ * to avoid runtime dependency on the entire mcp-i-core package.
9
+ *
10
+ * Source: packages/mcp-i-core/src/config/remote-config.ts
11
+ *
12
+ * @module @kya-os/create-mcpi-app/utils/fetch-remote-config
13
+ */
14
+
15
+ import type { MCPIConfig } from '@kya-os/contracts/config';
16
+ import { AGENTSHIELD_ENDPOINTS } from '@kya-os/contracts/agentshield-api';
17
+
18
+ /**
19
+ * Options for fetching remote configuration
20
+ */
21
+ export interface RemoteConfigOptions {
22
+ /**
23
+ * API base URL
24
+ * @example 'https://kya.vouched.id'
25
+ */
26
+ apiUrl: string;
27
+
28
+ /**
29
+ * API key for authentication
30
+ */
31
+ apiKey: string;
32
+
33
+ /**
34
+ * Project ID (optional, preferred over agentDid)
35
+ * Used for project-scoped configuration
36
+ */
37
+ projectId?: string;
38
+
39
+ /**
40
+ * Agent DID (optional, used when projectId not available)
41
+ * Used for agent-scoped configuration
42
+ */
43
+ agentDid?: string;
44
+
45
+ /**
46
+ * Cache TTL in milliseconds
47
+ * @default 300000 (5 minutes)
48
+ */
49
+ cacheTtl?: number;
50
+
51
+ /**
52
+ * Fetch provider function
53
+ * Platform-agnostic fetch implementation
54
+ */
55
+ fetchProvider: (url: string, options: RequestInit) => Promise<Response>;
56
+ }
57
+
58
+ /**
59
+ * Cache interface for remote configuration
60
+ * Abstracts platform-specific caching (KV, Redis, Memory, etc.)
61
+ */
62
+ export interface RemoteConfigCache {
63
+ /**
64
+ * Get a cached value
65
+ */
66
+ get(key: string): Promise<string | null>;
67
+
68
+ /**
69
+ * Set a cached value with TTL
70
+ */
71
+ set(key: string, value: string, ttl: number): Promise<void>;
72
+ }
73
+
74
+ /**
75
+ * Fetch configuration from remote API (AgentShield dashboard)
76
+ *
77
+ * Attempts to fetch configuration from the AgentShield API with caching support.
78
+ * Falls back gracefully if remote fetch fails.
79
+ *
80
+ * @param options - Remote config options
81
+ * @param cache - Optional cache implementation
82
+ * @returns Configuration object or null if fetch fails
83
+ */
84
+ export async function fetchRemoteConfig(
85
+ options: RemoteConfigOptions,
86
+ cache?: RemoteConfigCache
87
+ ): Promise<MCPIConfig | null> {
88
+ const { apiUrl, apiKey, projectId, agentDid, cacheTtl = 300000, fetchProvider } = options;
89
+
90
+ // Generate cache key
91
+ const cacheKey = projectId
92
+ ? `config:project:${projectId}`
93
+ : agentDid
94
+ ? `config:agent:${agentDid}`
95
+ : null;
96
+
97
+ // Try cache first
98
+ if (cache && cacheKey) {
99
+ try {
100
+ const cached = await cache.get(cacheKey);
101
+ if (cached) {
102
+ try {
103
+ const parsed = JSON.parse(cached) as { config: MCPIConfig; expiresAt: number };
104
+ if (parsed.expiresAt > Date.now()) {
105
+ return parsed.config;
106
+ }
107
+ } catch {
108
+ // Invalid cache entry, continue to fetch
109
+ }
110
+ }
111
+ } catch (error) {
112
+ // Cache read failed, continue to fetch
113
+ console.warn('[RemoteConfig] Cache read failed:', error);
114
+ }
115
+ }
116
+
117
+ // Fetch from API
118
+ try {
119
+ // Build API URL
120
+ let url: string;
121
+ if (projectId) {
122
+ // Use project-scoped endpoint (preferred)
123
+ url = `${apiUrl}${AGENTSHIELD_ENDPOINTS.CONFIG(projectId)}`;
124
+ } else if (agentDid) {
125
+ // Use agent-scoped endpoint
126
+ url = `${apiUrl}/api/v1/bouncer/config?agent_did=${encodeURIComponent(agentDid)}`;
127
+ } else {
128
+ console.warn('[RemoteConfig] Neither projectId nor agentDid provided');
129
+ return null;
130
+ }
131
+
132
+ const response = await fetchProvider(url, {
133
+ headers: {
134
+ 'Authorization': `Bearer ${apiKey}`,
135
+ 'Content-Type': 'application/json'
136
+ }
137
+ });
138
+
139
+ if (!response.ok) {
140
+ console.warn(`[RemoteConfig] API returned ${response.status}: ${response.statusText}`);
141
+ return null;
142
+ }
143
+
144
+ const data = await response.json();
145
+
146
+ // Extract config from API response
147
+ // API response format: { success: boolean, data: { config: MCPIConfig } }
148
+ const responseData = data as { config?: MCPIConfig; data?: { config?: MCPIConfig }; success?: boolean };
149
+ const config = responseData.config || responseData.data?.config || (responseData.success ? responseData.data as MCPIConfig | null : null) as MCPIConfig | null;
150
+
151
+ if (!config) {
152
+ console.warn('[RemoteConfig] No config found in API response');
153
+ return null;
154
+ }
155
+
156
+ // Cache the result
157
+ if (cache && cacheKey) {
158
+ try {
159
+ await cache.set(
160
+ cacheKey,
161
+ JSON.stringify({
162
+ config,
163
+ expiresAt: Date.now() + cacheTtl
164
+ }),
165
+ cacheTtl
166
+ );
167
+ } catch (error) {
168
+ // Cache write failed, but we got the config so continue
169
+ console.warn('[RemoteConfig] Cache write failed:', error);
170
+ }
171
+ }
172
+
173
+ return config as MCPIConfig;
174
+ } catch (error) {
175
+ console.warn('[RemoteConfig] Failed to fetch config:', error);
176
+ return null;
177
+ }
178
+ }
179
+
@@ -1,4 +0,0 @@
1
-
2
- > @kya-os/create-mcpi-app@1.7.15 build /Users/dylanhobbs/Documents/@kya-os/xmcp-i/packages/create-mcpi-app
3
- > tsc --build && chmod +x dist/index.js
4
-