@agentuity/cli 0.0.43 → 0.0.45

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 (209) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/api.d.ts +3 -3
  4. package/dist/api.d.ts.map +1 -1
  5. package/dist/auth.d.ts +10 -2
  6. package/dist/auth.d.ts.map +1 -1
  7. package/dist/banner.d.ts.map +1 -1
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cmd/auth/api.d.ts +4 -4
  10. package/dist/cmd/auth/api.d.ts.map +1 -1
  11. package/dist/cmd/auth/index.d.ts.map +1 -1
  12. package/dist/cmd/auth/login.d.ts.map +1 -1
  13. package/dist/cmd/auth/signup.d.ts.map +1 -1
  14. package/dist/cmd/auth/ssh/add.d.ts +2 -0
  15. package/dist/cmd/auth/ssh/add.d.ts.map +1 -0
  16. package/dist/cmd/auth/ssh/api.d.ts +16 -0
  17. package/dist/cmd/auth/ssh/api.d.ts.map +1 -0
  18. package/dist/cmd/auth/ssh/delete.d.ts +2 -0
  19. package/dist/cmd/auth/ssh/delete.d.ts.map +1 -0
  20. package/dist/cmd/auth/ssh/index.d.ts +3 -0
  21. package/dist/cmd/auth/ssh/index.d.ts.map +1 -0
  22. package/dist/cmd/auth/ssh/list.d.ts +2 -0
  23. package/dist/cmd/auth/ssh/list.d.ts.map +1 -0
  24. package/dist/cmd/auth/whoami.d.ts.map +1 -1
  25. package/dist/cmd/bundle/ast.d.ts +14 -3
  26. package/dist/cmd/bundle/ast.d.ts.map +1 -1
  27. package/dist/cmd/bundle/ast.test.d.ts +2 -0
  28. package/dist/cmd/bundle/ast.test.d.ts.map +1 -0
  29. package/dist/cmd/bundle/bundler.d.ts +6 -1
  30. package/dist/cmd/bundle/bundler.d.ts.map +1 -1
  31. package/dist/cmd/bundle/file.d.ts.map +1 -1
  32. package/dist/cmd/bundle/fix-duplicate-exports.d.ts +2 -0
  33. package/dist/cmd/bundle/fix-duplicate-exports.d.ts.map +1 -0
  34. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts +2 -0
  35. package/dist/cmd/bundle/fix-duplicate-exports.test.d.ts.map +1 -0
  36. package/dist/cmd/bundle/plugin.d.ts +2 -0
  37. package/dist/cmd/bundle/plugin.d.ts.map +1 -1
  38. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  39. package/dist/cmd/cloud/domain.d.ts +17 -0
  40. package/dist/cmd/cloud/domain.d.ts.map +1 -0
  41. package/dist/cmd/cloud/index.d.ts.map +1 -1
  42. package/dist/cmd/cloud/resource/add.d.ts +2 -0
  43. package/dist/cmd/cloud/resource/add.d.ts.map +1 -0
  44. package/dist/cmd/cloud/resource/delete.d.ts +2 -0
  45. package/dist/cmd/cloud/resource/delete.d.ts.map +1 -0
  46. package/dist/cmd/cloud/resource/index.d.ts +3 -0
  47. package/dist/cmd/cloud/resource/index.d.ts.map +1 -0
  48. package/dist/cmd/cloud/resource/list.d.ts +2 -0
  49. package/dist/cmd/cloud/resource/list.d.ts.map +1 -0
  50. package/dist/cmd/cloud/scp/download.d.ts +2 -0
  51. package/dist/cmd/cloud/scp/download.d.ts.map +1 -0
  52. package/dist/cmd/cloud/scp/index.d.ts +3 -0
  53. package/dist/cmd/cloud/scp/index.d.ts.map +1 -0
  54. package/dist/cmd/cloud/scp/upload.d.ts +2 -0
  55. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -0
  56. package/dist/cmd/cloud/ssh.d.ts +2 -0
  57. package/dist/cmd/cloud/ssh.d.ts.map +1 -0
  58. package/dist/cmd/dev/api.d.ts +18 -0
  59. package/dist/cmd/dev/api.d.ts.map +1 -0
  60. package/dist/cmd/dev/download.d.ts +11 -0
  61. package/dist/cmd/dev/download.d.ts.map +1 -0
  62. package/dist/cmd/dev/index.d.ts.map +1 -1
  63. package/dist/cmd/dev/templates.d.ts +3 -0
  64. package/dist/cmd/dev/templates.d.ts.map +1 -0
  65. package/dist/cmd/env/delete.d.ts.map +1 -1
  66. package/dist/cmd/env/get.d.ts.map +1 -1
  67. package/dist/cmd/env/import.d.ts.map +1 -1
  68. package/dist/cmd/env/list.d.ts.map +1 -1
  69. package/dist/cmd/env/pull.d.ts.map +1 -1
  70. package/dist/cmd/env/push.d.ts.map +1 -1
  71. package/dist/cmd/env/set.d.ts.map +1 -1
  72. package/dist/cmd/profile/show.d.ts.map +1 -1
  73. package/dist/cmd/project/create.d.ts.map +1 -1
  74. package/dist/cmd/project/delete.d.ts.map +1 -1
  75. package/dist/cmd/project/list.d.ts.map +1 -1
  76. package/dist/cmd/project/show.d.ts.map +1 -1
  77. package/dist/cmd/project/template-flow.d.ts +4 -0
  78. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  79. package/dist/cmd/secret/delete.d.ts.map +1 -1
  80. package/dist/cmd/secret/get.d.ts.map +1 -1
  81. package/dist/cmd/secret/import.d.ts.map +1 -1
  82. package/dist/cmd/secret/list.d.ts.map +1 -1
  83. package/dist/cmd/secret/pull.d.ts.map +1 -1
  84. package/dist/cmd/secret/push.d.ts.map +1 -1
  85. package/dist/cmd/secret/set.d.ts.map +1 -1
  86. package/dist/config.d.ts +9 -3
  87. package/dist/config.d.ts.map +1 -1
  88. package/dist/crypto/box.d.ts +65 -0
  89. package/dist/crypto/box.d.ts.map +1 -0
  90. package/dist/crypto/box.test.d.ts +2 -0
  91. package/dist/crypto/box.test.d.ts.map +1 -0
  92. package/dist/download.d.ts.map +1 -1
  93. package/dist/steps.d.ts +4 -1
  94. package/dist/steps.d.ts.map +1 -1
  95. package/dist/terminal.d.ts.map +1 -1
  96. package/dist/tui.d.ts +31 -1
  97. package/dist/tui.d.ts.map +1 -1
  98. package/dist/types.d.ts +249 -126
  99. package/dist/types.d.ts.map +1 -1
  100. package/dist/utils/detectSubagent.d.ts +15 -0
  101. package/dist/utils/detectSubagent.d.ts.map +1 -0
  102. package/dist/utils/zip.d.ts +7 -0
  103. package/dist/utils/zip.d.ts.map +1 -0
  104. package/package.json +11 -3
  105. package/src/api-errors.md +2 -2
  106. package/src/api.ts +12 -7
  107. package/src/auth.ts +116 -7
  108. package/src/banner.ts +13 -6
  109. package/src/cli.ts +695 -63
  110. package/src/cmd/auth/api.ts +10 -16
  111. package/src/cmd/auth/index.ts +2 -1
  112. package/src/cmd/auth/login.ts +24 -8
  113. package/src/cmd/auth/signup.ts +15 -11
  114. package/src/cmd/auth/ssh/add.ts +263 -0
  115. package/src/cmd/auth/ssh/api.ts +94 -0
  116. package/src/cmd/auth/ssh/delete.ts +102 -0
  117. package/src/cmd/auth/ssh/index.ts +10 -0
  118. package/src/cmd/auth/ssh/list.ts +74 -0
  119. package/src/cmd/auth/whoami.ts +13 -13
  120. package/src/cmd/bundle/ast.test.ts +565 -0
  121. package/src/cmd/bundle/ast.ts +457 -44
  122. package/src/cmd/bundle/bundler.ts +255 -57
  123. package/src/cmd/bundle/file.ts +6 -12
  124. package/src/cmd/bundle/fix-duplicate-exports.test.ts +387 -0
  125. package/src/cmd/bundle/fix-duplicate-exports.ts +204 -0
  126. package/src/cmd/bundle/index.ts +9 -9
  127. package/src/cmd/bundle/patch/aisdk.ts +1 -1
  128. package/src/cmd/bundle/plugin.ts +373 -53
  129. package/src/cmd/cloud/deploy.ts +300 -93
  130. package/src/cmd/cloud/domain.ts +92 -0
  131. package/src/cmd/cloud/index.ts +4 -1
  132. package/src/cmd/cloud/resource/add.ts +56 -0
  133. package/src/cmd/cloud/resource/delete.ts +120 -0
  134. package/src/cmd/cloud/resource/index.ts +11 -0
  135. package/src/cmd/cloud/resource/list.ts +69 -0
  136. package/src/cmd/cloud/scp/download.ts +59 -0
  137. package/src/cmd/cloud/scp/index.ts +9 -0
  138. package/src/cmd/cloud/scp/upload.ts +62 -0
  139. package/src/cmd/cloud/ssh.ts +68 -0
  140. package/src/cmd/dev/api.ts +46 -0
  141. package/src/cmd/dev/download.ts +111 -0
  142. package/src/cmd/dev/index.ts +360 -34
  143. package/src/cmd/dev/templates.ts +84 -0
  144. package/src/cmd/env/delete.ts +5 -20
  145. package/src/cmd/env/get.ts +5 -18
  146. package/src/cmd/env/import.ts +5 -20
  147. package/src/cmd/env/list.ts +5 -18
  148. package/src/cmd/env/pull.ts +10 -23
  149. package/src/cmd/env/push.ts +5 -23
  150. package/src/cmd/env/set.ts +5 -20
  151. package/src/cmd/index.ts +2 -2
  152. package/src/cmd/profile/show.ts +15 -6
  153. package/src/cmd/project/create.ts +7 -2
  154. package/src/cmd/project/delete.ts +75 -18
  155. package/src/cmd/project/download.ts +2 -2
  156. package/src/cmd/project/list.ts +8 -8
  157. package/src/cmd/project/show.ts +3 -7
  158. package/src/cmd/project/template-flow.ts +170 -72
  159. package/src/cmd/secret/delete.ts +5 -20
  160. package/src/cmd/secret/get.ts +5 -18
  161. package/src/cmd/secret/import.ts +5 -20
  162. package/src/cmd/secret/list.ts +5 -18
  163. package/src/cmd/secret/pull.ts +10 -23
  164. package/src/cmd/secret/push.ts +5 -23
  165. package/src/cmd/secret/set.ts +5 -20
  166. package/src/config.ts +224 -24
  167. package/src/crypto/box.test.ts +431 -0
  168. package/src/crypto/box.ts +477 -0
  169. package/src/download.ts +1 -0
  170. package/src/env-util.test.ts +1 -1
  171. package/src/steps.ts +65 -6
  172. package/src/terminal.ts +24 -23
  173. package/src/tui.ts +192 -61
  174. package/src/types.ts +291 -201
  175. package/src/utils/detectSubagent.ts +31 -0
  176. package/src/utils/zip.ts +38 -0
  177. package/dist/cmd/example/create-user.d.ts +0 -2
  178. package/dist/cmd/example/create-user.d.ts.map +0 -1
  179. package/dist/cmd/example/create.d.ts +0 -2
  180. package/dist/cmd/example/create.d.ts.map +0 -1
  181. package/dist/cmd/example/deploy.d.ts +0 -2
  182. package/dist/cmd/example/deploy.d.ts.map +0 -1
  183. package/dist/cmd/example/index.d.ts +0 -2
  184. package/dist/cmd/example/index.d.ts.map +0 -1
  185. package/dist/cmd/example/list.d.ts +0 -2
  186. package/dist/cmd/example/list.d.ts.map +0 -1
  187. package/dist/cmd/example/optional-auth.d.ts +0 -3
  188. package/dist/cmd/example/optional-auth.d.ts.map +0 -1
  189. package/dist/cmd/example/run-command.d.ts +0 -2
  190. package/dist/cmd/example/run-command.d.ts.map +0 -1
  191. package/dist/cmd/example/sound.d.ts +0 -3
  192. package/dist/cmd/example/sound.d.ts.map +0 -1
  193. package/dist/cmd/example/spinner.d.ts +0 -2
  194. package/dist/cmd/example/spinner.d.ts.map +0 -1
  195. package/dist/cmd/example/steps.d.ts +0 -2
  196. package/dist/cmd/example/steps.d.ts.map +0 -1
  197. package/dist/cmd/example/version.d.ts +0 -2
  198. package/dist/cmd/example/version.d.ts.map +0 -1
  199. package/src/cmd/example/create-user.ts +0 -38
  200. package/src/cmd/example/create.ts +0 -31
  201. package/src/cmd/example/deploy.ts +0 -36
  202. package/src/cmd/example/index.ts +0 -29
  203. package/src/cmd/example/list.ts +0 -32
  204. package/src/cmd/example/optional-auth.ts +0 -38
  205. package/src/cmd/example/run-command.ts +0 -45
  206. package/src/cmd/example/sound.ts +0 -14
  207. package/src/cmd/example/spinner.ts +0 -44
  208. package/src/cmd/example/steps.ts +0 -66
  209. package/src/cmd/example/version.ts +0 -13
@@ -3,8 +3,6 @@ import { join } from 'node:path';
3
3
  import { createSubcommand } from '../../types';
4
4
  import * as tui from '../../tui';
5
5
  import { projectGet } from '@agentuity/server';
6
- import { getAPIBaseURL, APIClient } from '../../api';
7
- import { loadProjectConfig } from '../../config';
8
6
  import {
9
7
  findEnvFile,
10
8
  findExistingEnvFile,
@@ -16,40 +14,29 @@ import {
16
14
  export const pullSubcommand = createSubcommand({
17
15
  name: 'pull',
18
16
  description: 'Pull secrets from cloud to local .env.production file',
19
- requiresAuth: true,
17
+ requires: { auth: true, project: true, apiClient: true },
20
18
  schema: {
21
19
  options: z.object({
22
- dir: z.string().optional().describe('project directory (default: current directory)'),
23
20
  force: z.boolean().default(false).describe('overwrite local values with cloud values'),
24
21
  }),
25
22
  },
26
23
 
27
24
  async handler(ctx) {
28
- const { opts, config } = ctx;
29
- const dir = opts?.dir ?? process.cwd();
30
-
31
- // Load project config to get project ID
32
- const projectConfig = await loadProjectConfig(dir);
33
- if (!projectConfig) {
34
- tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
35
- }
36
-
37
- const apiUrl = getAPIBaseURL(config);
38
- const client = new APIClient(apiUrl, config);
25
+ const { opts, apiClient, project, projectDir } = ctx;
39
26
 
40
27
  // Fetch project with unmasked secrets
41
- const project = await tui.spinner('Pulling secrets from cloud', () => {
42
- return projectGet(client, { id: projectConfig.projectId, mask: false });
28
+ const projectData = await tui.spinner('Pulling secrets from cloud', () => {
29
+ return projectGet(apiClient, { id: project.projectId, mask: false });
43
30
  });
44
31
 
45
- const cloudSecrets = project.secrets || {};
32
+ const cloudSecrets = projectData.secrets || {};
46
33
 
47
34
  // Read current local env from existing file (.env.production or .env)
48
- const existingEnvPath = await findExistingEnvFile(dir);
35
+ const existingEnvPath = await findExistingEnvFile(projectDir);
49
36
  const localEnv = await readEnvFile(existingEnvPath);
50
37
 
51
38
  // Target file is always .env.production
52
- const targetEnvPath = await findEnvFile(dir);
39
+ const targetEnvPath = await findEnvFile(projectDir);
53
40
 
54
41
  // Merge: cloud values override local if force=true, otherwise keep local
55
42
  let mergedEnv: Record<string, string>;
@@ -67,12 +54,12 @@ export const pullSubcommand = createSubcommand({
67
54
  });
68
55
 
69
56
  // Write AGENTUITY_SDK_KEY to .env if present and missing locally
70
- if (project.api_key) {
71
- const dotEnvPath = join(dir, '.env');
57
+ if (projectData.api_key) {
58
+ const dotEnvPath = join(projectDir, '.env');
72
59
  const dotEnv = await readEnvFile(dotEnvPath);
73
60
 
74
61
  if (!dotEnv.AGENTUITY_SDK_KEY) {
75
- dotEnv.AGENTUITY_SDK_KEY = project.api_key;
62
+ dotEnv.AGENTUITY_SDK_KEY = projectData.api_key;
76
63
  await writeEnvFile(dotEnvPath, dotEnv, {
77
64
  addComment: (key) => {
78
65
  if (key === 'AGENTUITY_SDK_KEY') {
@@ -1,33 +1,18 @@
1
- import { z } from 'zod';
2
1
  import { createSubcommand } from '../../types';
3
2
  import * as tui from '../../tui';
4
3
  import { projectEnvUpdate } from '@agentuity/server';
5
- import { getAPIBaseURL, APIClient } from '../../api';
6
- import { loadProjectConfig } from '../../config';
7
4
  import { findEnvFile, readEnvFile, filterAgentuitySdkKeys } from '../../env-util';
8
5
 
9
6
  export const pushSubcommand = createSubcommand({
10
7
  name: 'push',
11
8
  description: 'Push secrets from local .env.production file to cloud',
12
- requiresAuth: true,
13
- schema: {
14
- options: z.object({
15
- dir: z.string().optional().describe('project directory (default: current directory)'),
16
- }),
17
- },
9
+ requires: { auth: true, project: true, apiClient: true },
18
10
 
19
11
  async handler(ctx) {
20
- const { opts, config } = ctx;
21
- const dir = opts?.dir ?? process.cwd();
22
-
23
- // Load project config to get project ID
24
- const projectConfig = await loadProjectConfig(dir);
25
- if (!projectConfig) {
26
- tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
27
- }
12
+ const { apiClient, project, projectDir } = ctx;
28
13
 
29
14
  // Read local env file
30
- const envFilePath = await findEnvFile(dir);
15
+ const envFilePath = await findEnvFile(projectDir);
31
16
  const localEnv = await readEnvFile(envFilePath);
32
17
 
33
18
  // Filter out AGENTUITY_ prefixed keys (don't push SDK keys)
@@ -38,13 +23,10 @@ export const pushSubcommand = createSubcommand({
38
23
  return;
39
24
  }
40
25
 
41
- const apiUrl = getAPIBaseURL(config);
42
- const client = new APIClient(apiUrl, config);
43
-
44
26
  // Push to cloud (using secrets field)
45
27
  await tui.spinner('Pushing secrets to cloud', () => {
46
- return projectEnvUpdate(client, {
47
- id: projectConfig.projectId,
28
+ return projectEnvUpdate(apiClient, {
29
+ id: project.projectId,
48
30
  secrets: filteredSecrets,
49
31
  });
50
32
  });
@@ -2,52 +2,37 @@ import { z } from 'zod';
2
2
  import { createSubcommand } from '../../types';
3
3
  import * as tui from '../../tui';
4
4
  import { projectEnvUpdate } from '@agentuity/server';
5
- import { getAPIBaseURL, APIClient } from '../../api';
6
- import { loadProjectConfig } from '../../config';
7
5
  import { findEnvFile, readEnvFile, writeEnvFile, filterAgentuitySdkKeys } from '../../env-util';
8
6
 
9
7
  export const setSubcommand = createSubcommand({
10
8
  name: 'set',
11
9
  description: 'Set a secret',
12
- requiresAuth: true,
10
+ requires: { auth: true, project: true, apiClient: true },
13
11
  schema: {
14
12
  args: z.object({
15
13
  key: z.string().min(1, 'key must not be empty').describe('the secret key'),
16
14
  value: z.string().min(1, 'value must not be empty').describe('the secret value'),
17
15
  }),
18
- options: z.object({
19
- dir: z.string().optional().describe('project directory (default: current directory)'),
20
- }),
21
16
  },
22
17
 
23
18
  async handler(ctx) {
24
- const { args, opts, config } = ctx;
25
- const dir = opts?.dir ?? process.cwd();
19
+ const { args, apiClient, project, projectDir } = ctx;
26
20
 
27
21
  // Validate key doesn't start with AGENTUITY_
28
22
  if (args.key.startsWith('AGENTUITY_')) {
29
23
  tui.fatal('Cannot set AGENTUITY_ prefixed variables. These are reserved for system use.');
30
24
  }
31
25
 
32
- // Load project config to get project ID
33
- const projectConfig = await loadProjectConfig(dir);
34
- if (!projectConfig) {
35
- tui.fatal(`No Agentuity project found in ${dir}. Missing agentuity.json`);
36
- }
37
-
38
- const apiUrl = getAPIBaseURL(config);
39
- const client = new APIClient(apiUrl, config);
40
-
41
26
  // Set in cloud (using secrets field)
42
27
  await tui.spinner('Setting secret in cloud', () => {
43
- return projectEnvUpdate(client, {
44
- id: projectConfig.projectId,
28
+ return projectEnvUpdate(apiClient, {
29
+ id: project.projectId,
45
30
  secrets: { [args.key]: args.value },
46
31
  });
47
32
  });
48
33
 
49
34
  // Update local .env.production file
50
- const envFilePath = await findEnvFile(dir);
35
+ const envFilePath = await findEnvFile(projectDir);
51
36
  const currentEnv = await readEnvFile(envFilePath);
52
37
  currentEnv[args.key] = args.value;
53
38
 
package/src/config.ts CHANGED
@@ -1,18 +1,29 @@
1
+ import { z } from 'zod';
2
+ import { existsSync } from 'node:fs';
3
+ import type { Logger } from '@agentuity/core';
4
+ import {
5
+ BuildMetadataSchema,
6
+ type BuildMetadata,
7
+ getServiceUrls,
8
+ APIClient as ServerAPIClient,
9
+ } from '@agentuity/server';
1
10
  import { YAML } from 'bun';
2
11
  import { join, extname, basename, resolve, normalize } from 'node:path';
3
12
  import { homedir } from 'node:os';
4
13
  import { mkdir, readdir, readFile, writeFile, chmod } from 'node:fs/promises';
14
+ import JSON5 from 'json5';
5
15
  import type { Config, Profile, AuthData } from './types';
6
- import { ConfigSchema, ProjectSchema, BuildMetadataSchema, type BuildMetadata } from './types';
16
+ import { ConfigSchema, ProjectSchema } from './types';
7
17
  import * as tui from './tui';
8
- import { z } from 'zod';
18
+
19
+ export const defaultProfileName = 'production';
9
20
 
10
21
  export function getDefaultConfigDir(): string {
11
22
  return join(homedir(), '.config', 'agentuity');
12
23
  }
13
24
 
14
25
  export function getDefaultConfigPath(): string {
15
- return join(getDefaultConfigDir(), 'production.yaml');
26
+ return join(getDefaultConfigDir(), defaultProfileName + '.yaml');
16
27
  }
17
28
 
18
29
  export function getProfilePath(): string {
@@ -71,7 +82,7 @@ export async function fetchProfiles(): Promise<Profile[]> {
71
82
  const content = await readFile(filePath, 'utf-8');
72
83
  const match = nameRegex.exec(content);
73
84
 
74
- if (match && match[1]) {
85
+ if (match?.[1]) {
75
86
  profiles.push({
76
87
  name: match[1],
77
88
  filename: filePath,
@@ -103,22 +114,24 @@ export async function loadConfig(customPath?: string): Promise<Config | null> {
103
114
  try {
104
115
  const file = Bun.file(configPath);
105
116
  const exists = await file.exists();
117
+ let result: ReturnType<typeof ConfigSchema.safeParse>;
106
118
 
107
- if (!exists) {
108
- return null;
109
- }
119
+ if (exists) {
120
+ const content = await file.text();
121
+ const config = YAML.parse(content);
110
122
 
111
- const content = await file.text();
112
- const config = YAML.parse(content);
123
+ // check to see if this is a legacy config file that might not have the required name
124
+ // and in this case we can just use the filename
125
+ const _config = config as { name?: string };
126
+ if (!_config.name) {
127
+ _config.name = basename(configPath).replace(extname(configPath), '');
128
+ }
113
129
 
114
- // check to see if this is a legacy config file that might not have the required name
115
- // and in this case we can just use the filename
116
- const _config = config as { name?: string };
117
- if (!_config.name) {
118
- _config.name = basename(configPath).replace(extname(configPath), '');
130
+ result = ConfigSchema.safeParse(config);
131
+ } else {
132
+ result = ConfigSchema.safeParse({ name: defaultProfileName });
119
133
  }
120
134
 
121
- const result = ConfigSchema.safeParse(config);
122
135
  if (!result.success) {
123
136
  tui.error(`Invalid config in ${configPath}:`);
124
137
  for (const issue of result.error.issues) {
@@ -128,6 +141,36 @@ export async function loadConfig(customPath?: string): Promise<Config | null> {
128
141
  process.exit(1);
129
142
  }
130
143
 
144
+ // allow environment variables to override
145
+ const overrides = result.data.overrides ?? ConfigSchema.shape.overrides.parse({});
146
+ if (overrides) {
147
+ if (process.env.AGENTUITY_API_URL) {
148
+ overrides.api_url = process.env.AGENTUITY_API_URL;
149
+ }
150
+ if (process.env.AGENTUITY_APP_URL) {
151
+ overrides.app_url = process.env.AGENTUITY_APP_URL;
152
+ }
153
+ if (process.env.AGENTUITY_CATALYST_URL) {
154
+ overrides.catalyst_url = process.env.AGENTUITY_CATALYST_URL;
155
+ }
156
+ if (process.env.AGENTUITY_TRANSPORT_URL) {
157
+ overrides.transport_url = process.env.AGENTUITY_TRANSPORT_URL;
158
+ }
159
+ if (process.env.AGENTUITY_KEYVALUE_URL) {
160
+ overrides.kv_url = process.env.AGENTUITY_KEYVALUE_URL;
161
+ }
162
+ if (process.env.AGENTUITY_OBJECTSTORE_URL) {
163
+ overrides.object_url = process.env.AGENTUITY_OBJECTSTORE_URL;
164
+ }
165
+ if (process.env.AGENTUITY_VECTOR_URL) {
166
+ overrides.vector_url = process.env.AGENTUITY_VECTOR_URL;
167
+ }
168
+ if (process.env.AGENTUITY_STREAM_URL) {
169
+ overrides.stream_url = process.env.AGENTUITY_STREAM_URL;
170
+ }
171
+ result.data.overrides = overrides;
172
+ }
173
+
131
174
  return result.data;
132
175
  } catch (error) {
133
176
  if (error instanceof Error) {
@@ -239,6 +282,15 @@ export async function saveOrgId(orgId: string): Promise<void> {
239
282
  }
240
283
 
241
284
  export async function getAuth(): Promise<AuthData | null> {
285
+ // allow automated login from environment variables if present
286
+ if (process.env.AGENTUITY_CLI_API_KEY && process.env.AGENTUITY_USER_ID) {
287
+ return {
288
+ apiKey: process.env.AGENTUITY_CLI_API_KEY,
289
+ userId: process.env.AGENTUITY_USER_ID,
290
+ expires: new Date(Date.now() + 30 * 60_000),
291
+ };
292
+ }
293
+
242
294
  const config = await loadConfig();
243
295
  if (!config) return null;
244
296
  const auth = config.auth as { api_key?: string; user_id?: string; expires?: number } | undefined;
@@ -280,6 +332,81 @@ function getPlaceholderValue(schema: z.ZodTypeAny): string {
280
332
  }
281
333
  }
282
334
 
335
+ function extractDefaultValue(schema: z.ZodTypeAny): unknown {
336
+ let unwrapped = schema;
337
+
338
+ // Unwrap optional layers
339
+ while (unwrapped instanceof z.ZodOptional) {
340
+ unwrapped = (unwrapped._def as unknown as { innerType: z.ZodTypeAny }).innerType;
341
+ }
342
+
343
+ // Check if it's a ZodDefault (has defaultValue in def or _def)
344
+ const checkDef = (obj: unknown): unknown => {
345
+ if (typeof obj !== 'object' || obj === null) return undefined;
346
+ const anyObj = obj as Record<string, unknown>;
347
+
348
+ // Check `def` property first (used in some Zod versions)
349
+ if ('def' in anyObj && typeof anyObj.def === 'object' && anyObj.def !== null) {
350
+ const def = anyObj.def as Record<string, unknown>;
351
+ if (def.type === 'default' && 'defaultValue' in def) {
352
+ const val = def.defaultValue;
353
+ return typeof val === 'function' ? (val as () => unknown)() : val;
354
+ }
355
+ }
356
+
357
+ // Check `_def` property (standard Zod property)
358
+ if ('_def' in anyObj && typeof anyObj._def === 'object' && anyObj._def !== null) {
359
+ const def = anyObj._def as Record<string, unknown>;
360
+ if (def.type === 'default' && 'defaultValue' in def) {
361
+ const val = def.defaultValue;
362
+ return typeof val === 'function' ? (val as () => unknown)() : val;
363
+ }
364
+ }
365
+
366
+ return undefined;
367
+ };
368
+
369
+ return checkDef(unwrapped);
370
+ }
371
+
372
+ function getValueWithDefaults(schema: z.ZodTypeAny, providedValue: unknown): unknown {
373
+ // If value is explicitly provided, use it
374
+ if (providedValue !== undefined) {
375
+ return providedValue;
376
+ }
377
+
378
+ // Try to extract default value
379
+ const defaultValue = extractDefaultValue(schema);
380
+ if (defaultValue !== undefined) {
381
+ return defaultValue;
382
+ }
383
+
384
+ // For optional fields without defaults, check if it's an object
385
+ let unwrapped = schema;
386
+ if (schema instanceof z.ZodOptional) {
387
+ unwrapped = (schema._def as unknown as { innerType: z.ZodTypeAny }).innerType;
388
+ }
389
+
390
+ // If it's an object schema, recursively populate defaults
391
+ if (unwrapped instanceof z.ZodObject) {
392
+ const shape = unwrapped.shape;
393
+ const result: Record<string, unknown> = {};
394
+ let hasAnyDefaults = false;
395
+
396
+ for (const [key, fieldSchema] of Object.entries(shape)) {
397
+ const fieldValue = getValueWithDefaults(fieldSchema as z.ZodTypeAny, undefined);
398
+ if (fieldValue !== undefined) {
399
+ result[key] = fieldValue;
400
+ hasAnyDefaults = true;
401
+ }
402
+ }
403
+
404
+ return hasAnyDefaults ? result : undefined;
405
+ }
406
+
407
+ return undefined;
408
+ }
409
+
283
410
  export function generateYAMLTemplate(name: string): string {
284
411
  const lines: string[] = [];
285
412
 
@@ -350,15 +477,61 @@ class ProjectConfigNotFoundExpection extends Error {
350
477
 
351
478
  type ProjectConfig = z.infer<typeof ProjectSchema>;
352
479
 
353
- export async function loadProjectConfig(dir: string): Promise<ProjectConfig> {
354
- const configPath = join(dir, 'agentuity.json');
480
+ function generateJSON5WithComments(
481
+ schema: z.ZodObject<z.ZodRawShape>,
482
+ data: Record<string, unknown>
483
+ ): string {
484
+ const lines: string[] = ['{'];
485
+ const shape = schema.shape;
486
+ const keys = Object.keys(shape);
487
+
488
+ for (let i = 0; i < keys.length; i++) {
489
+ const key = keys[i];
490
+ const fieldSchema = shape[key] as z.ZodTypeAny;
491
+ const description = getSchemaDescription(fieldSchema);
492
+ const providedValue = data[key];
493
+
494
+ if (description) {
495
+ lines.push(` // ${description}`);
496
+ }
497
+
498
+ // Get value with defaults applied
499
+ const valueWithDefaults = getValueWithDefaults(fieldSchema, providedValue);
500
+ const safeValue = valueWithDefaults === undefined ? null : valueWithDefaults;
501
+ const jsonValue = JSON.stringify(safeValue, null, 2).replace(/\n/g, '\n ');
502
+ const comma = i < keys.length - 1 ? ',' : '';
503
+ lines.push(` ${JSON.stringify(key)}: ${jsonValue}${comma}`);
504
+ }
505
+
506
+ lines.push('}');
507
+ return lines.join('\n');
508
+ }
509
+
510
+ export async function loadProjectConfig(
511
+ dir: string,
512
+ config?: Config | null
513
+ ): Promise<ProjectConfig> {
514
+ let configPath = join(dir, 'agentuity.json');
515
+
516
+ // Check for profile-specific override if config is provided
517
+ if (config?.name) {
518
+ const profileConfigPath = join(dir, `agentuity.${config.name}.json`);
519
+ if (await Bun.file(profileConfigPath).exists()) {
520
+ configPath = profileConfigPath;
521
+ }
522
+ }
523
+
355
524
  const file = Bun.file(configPath);
356
525
  if (!(await file.exists())) {
526
+ // TODO: check to see if a valid project that was created unauthenticated
527
+ // and then if so:
528
+ // 1. if authentication, offer to import the project
529
+ // 2. tell them that they need to login to use the command and import the project
357
530
  throw new ProjectConfigNotFoundExpection();
358
531
  }
359
532
  const text = await file.text();
360
- const config = JSON.parse(text);
361
- const result = ProjectSchema.safeParse(config);
533
+ const parsedConfig = JSON5.parse(text);
534
+ const result = ProjectSchema.safeParse(parsedConfig);
362
535
  if (!result.success) {
363
536
  tui.error(`Invalid project config at ${configPath}:`);
364
537
  for (const issue of result.error.issues) {
@@ -375,20 +548,17 @@ type InitialProjectConfig = ProjectConfig & {
375
548
  };
376
549
 
377
550
  export async function createProjectConfig(dir: string, config: InitialProjectConfig) {
378
- // Create a sanitized config without the apiKey for agentuity.json
379
551
  const { apiKey, ...sanitizedConfig } = config;
380
552
 
381
553
  const configPath = join(dir, 'agentuity.json');
382
- const configFile = Bun.file(configPath);
383
- await configFile.write(JSON.stringify(sanitizedConfig, null, 2));
554
+ const json5Content = generateJSON5WithComments(ProjectSchema, sanitizedConfig);
555
+ await Bun.write(configPath, json5Content + '\n');
384
556
 
385
- // Write SDK key to .env with comment
386
557
  const envPath = join(dir, '.env');
387
558
  const comment =
388
559
  '# AGENTUITY_SDK_KEY is a sensitive value and should not be committed to version control.';
389
560
  const content = `${comment}\nAGENTUITY_SDK_KEY=${apiKey}\n`;
390
561
  await Bun.write(envPath, content);
391
- // Set restrictive permissions (owner read/write only) to protect sensitive key
392
562
  await chmod(envPath, 0o600);
393
563
  }
394
564
 
@@ -411,3 +581,33 @@ export async function loadBuildMetadata(dir: string): Promise<BuildMetadata> {
411
581
  }
412
582
  return result.data;
413
583
  }
584
+
585
+ export async function loadDevelopmentProjectSDKKey(
586
+ projectDir: string
587
+ ): Promise<string | undefined> {
588
+ const files: string[] = ['.env.development', '.env.local', '.env'];
589
+ for (const filename of files) {
590
+ const fn = join(projectDir, filename);
591
+ if (existsSync(fn)) {
592
+ const buf = await Bun.file(fn).text();
593
+ const tok = buf.split(/\n/);
594
+ for (const t of tok) {
595
+ if (t.charAt(0) !== '#' && t.startsWith('AGENTUITY_SDK_KEY=')) {
596
+ const i = t.indexOf('=');
597
+ return t.substring(i + 1).trim();
598
+ }
599
+ }
600
+ }
601
+ }
602
+ }
603
+
604
+ export function getCatalystAPIClient(config: Config | null, logger: Logger, auth: AuthData) {
605
+ const serviceUrls = getServiceUrls();
606
+ const catalystUrl = config?.overrides?.catalyst_url ?? serviceUrls.catalyst;
607
+ return new ServerAPIClient(catalystUrl, logger, auth.apiKey);
608
+ }
609
+
610
+ export function getIONHost(config: Config | null) {
611
+ const url = new URL(config?.overrides?.ion_url ?? 'https://ion.agentuity.cloud');
612
+ return url.hostname;
613
+ }