@agentuity/cli 0.1.13 → 0.1.15

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 (173) hide show
  1. package/dist/auth.d.ts +1 -1
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +12 -7
  4. package/dist/auth.js.map +1 -1
  5. package/dist/cli.d.ts.map +1 -1
  6. package/dist/cli.js +94 -97
  7. package/dist/cli.js.map +1 -1
  8. package/dist/cmd/ai/index.d.ts.map +1 -1
  9. package/dist/cmd/ai/index.js +6 -1
  10. package/dist/cmd/ai/index.js.map +1 -1
  11. package/dist/cmd/ai/opencode/index.d.ts +3 -0
  12. package/dist/cmd/ai/opencode/index.d.ts.map +1 -0
  13. package/dist/cmd/ai/opencode/index.js +27 -0
  14. package/dist/cmd/ai/opencode/index.js.map +1 -0
  15. package/dist/cmd/ai/opencode/install.d.ts +3 -0
  16. package/dist/cmd/ai/opencode/install.d.ts.map +1 -0
  17. package/dist/cmd/ai/opencode/install.js +102 -0
  18. package/dist/cmd/ai/opencode/install.js.map +1 -0
  19. package/dist/cmd/ai/opencode/run.d.ts +3 -0
  20. package/dist/cmd/ai/opencode/run.d.ts.map +1 -0
  21. package/dist/cmd/ai/opencode/run.js +88 -0
  22. package/dist/cmd/ai/opencode/run.js.map +1 -0
  23. package/dist/cmd/ai/opencode/uninstall.d.ts +3 -0
  24. package/dist/cmd/ai/opencode/uninstall.d.ts.map +1 -0
  25. package/dist/cmd/ai/opencode/uninstall.js +82 -0
  26. package/dist/cmd/ai/opencode/uninstall.js.map +1 -0
  27. package/dist/cmd/auth/index.d.ts.map +1 -1
  28. package/dist/cmd/auth/index.js +3 -0
  29. package/dist/cmd/auth/index.js.map +1 -1
  30. package/dist/cmd/auth/org/index.d.ts +2 -0
  31. package/dist/cmd/auth/org/index.d.ts.map +1 -0
  32. package/dist/cmd/auth/org/index.js +121 -0
  33. package/dist/cmd/auth/org/index.js.map +1 -0
  34. package/dist/cmd/build/vite/beacon-plugin.d.ts +19 -0
  35. package/dist/cmd/build/vite/beacon-plugin.d.ts.map +1 -0
  36. package/dist/cmd/build/vite/beacon-plugin.js +137 -0
  37. package/dist/cmd/build/vite/beacon-plugin.js.map +1 -0
  38. package/dist/cmd/build/vite/vite-builder.d.ts +2 -0
  39. package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
  40. package/dist/cmd/build/vite/vite-builder.js +12 -2
  41. package/dist/cmd/build/vite/vite-builder.js.map +1 -1
  42. package/dist/cmd/build/webanalytics-generator.js +25 -9
  43. package/dist/cmd/build/webanalytics-generator.js.map +1 -1
  44. package/dist/cmd/cloud/db/get.d.ts.map +1 -1
  45. package/dist/cmd/cloud/db/get.js +7 -0
  46. package/dist/cmd/cloud/db/get.js.map +1 -1
  47. package/dist/cmd/cloud/db/list.d.ts.map +1 -1
  48. package/dist/cmd/cloud/db/list.js +19 -6
  49. package/dist/cmd/cloud/db/list.js.map +1 -1
  50. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  51. package/dist/cmd/cloud/deploy.js +24 -1
  52. package/dist/cmd/cloud/deploy.js.map +1 -1
  53. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  54. package/dist/cmd/cloud/deployment/show.js +5 -0
  55. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  56. package/dist/cmd/cloud/index.d.ts.map +1 -1
  57. package/dist/cmd/cloud/index.js +3 -0
  58. package/dist/cmd/cloud/index.js.map +1 -1
  59. package/dist/cmd/cloud/region/index.d.ts +2 -0
  60. package/dist/cmd/cloud/region/index.d.ts.map +1 -0
  61. package/dist/cmd/cloud/region/index.js +136 -0
  62. package/dist/cmd/cloud/region/index.js.map +1 -0
  63. package/dist/cmd/cloud/sandbox/snapshot/build.d.ts.map +1 -1
  64. package/dist/cmd/cloud/sandbox/snapshot/build.js +35 -5
  65. package/dist/cmd/cloud/sandbox/snapshot/build.js.map +1 -1
  66. package/dist/cmd/cloud/scp/download.d.ts.map +1 -1
  67. package/dist/cmd/cloud/scp/download.js +4 -2
  68. package/dist/cmd/cloud/scp/download.js.map +1 -1
  69. package/dist/cmd/cloud/scp/upload.d.ts.map +1 -1
  70. package/dist/cmd/cloud/scp/upload.js +4 -2
  71. package/dist/cmd/cloud/scp/upload.js.map +1 -1
  72. package/dist/cmd/cloud/ssh.d.ts.map +1 -1
  73. package/dist/cmd/cloud/ssh.js +3 -1
  74. package/dist/cmd/cloud/ssh.js.map +1 -1
  75. package/dist/cmd/cloud/storage/get.d.ts.map +1 -1
  76. package/dist/cmd/cloud/storage/get.js +12 -5
  77. package/dist/cmd/cloud/storage/get.js.map +1 -1
  78. package/dist/cmd/cloud/storage/list.d.ts.map +1 -1
  79. package/dist/cmd/cloud/storage/list.js +10 -0
  80. package/dist/cmd/cloud/storage/list.js.map +1 -1
  81. package/dist/cmd/dev/index.d.ts.map +1 -1
  82. package/dist/cmd/dev/index.js +62 -5
  83. package/dist/cmd/dev/index.js.map +1 -1
  84. package/dist/cmd/help/index.d.ts.map +1 -1
  85. package/dist/cmd/help/index.js +8 -18
  86. package/dist/cmd/help/index.js.map +1 -1
  87. package/dist/cmd/project/create.d.ts.map +1 -1
  88. package/dist/cmd/project/create.js +18 -9
  89. package/dist/cmd/project/create.js.map +1 -1
  90. package/dist/cmd/project/download.d.ts +3 -1
  91. package/dist/cmd/project/download.d.ts.map +1 -1
  92. package/dist/cmd/project/download.js +5 -0
  93. package/dist/cmd/project/download.js.map +1 -1
  94. package/dist/cmd/project/import.d.ts +2 -0
  95. package/dist/cmd/project/import.d.ts.map +1 -0
  96. package/dist/cmd/project/import.js +88 -0
  97. package/dist/cmd/project/import.js.map +1 -0
  98. package/dist/cmd/project/index.d.ts.map +1 -1
  99. package/dist/cmd/project/index.js +3 -0
  100. package/dist/cmd/project/index.js.map +1 -1
  101. package/dist/cmd/project/reconcile.d.ts +67 -0
  102. package/dist/cmd/project/reconcile.d.ts.map +1 -0
  103. package/dist/cmd/project/reconcile.js +458 -0
  104. package/dist/cmd/project/reconcile.js.map +1 -0
  105. package/dist/cmd/project/template-flow.d.ts +13 -1
  106. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  107. package/dist/cmd/project/template-flow.js +56 -18
  108. package/dist/cmd/project/template-flow.js.map +1 -1
  109. package/dist/config.d.ts +8 -3
  110. package/dist/config.d.ts.map +1 -1
  111. package/dist/config.js +50 -21
  112. package/dist/config.js.map +1 -1
  113. package/dist/legacy-check.d.ts.map +1 -1
  114. package/dist/legacy-check.js +8 -0
  115. package/dist/legacy-check.js.map +1 -1
  116. package/dist/program-ref.d.ts +4 -0
  117. package/dist/program-ref.d.ts.map +1 -0
  118. package/dist/program-ref.js +8 -0
  119. package/dist/program-ref.js.map +1 -0
  120. package/dist/regions.d.ts +8 -0
  121. package/dist/regions.d.ts.map +1 -0
  122. package/dist/regions.js +77 -0
  123. package/dist/regions.js.map +1 -0
  124. package/dist/tui/prompt.d.ts.map +1 -1
  125. package/dist/tui/prompt.js +7 -1
  126. package/dist/tui/prompt.js.map +1 -1
  127. package/dist/tui.d.ts.map +1 -1
  128. package/dist/tui.js +79 -25
  129. package/dist/tui.js.map +1 -1
  130. package/dist/types.d.ts +1 -0
  131. package/dist/types.d.ts.map +1 -1
  132. package/dist/types.js +1 -0
  133. package/dist/types.js.map +1 -1
  134. package/package.json +7 -7
  135. package/src/auth.ts +14 -13
  136. package/src/cli.ts +118 -114
  137. package/src/cmd/ai/index.ts +6 -1
  138. package/src/cmd/ai/opencode/index.ts +28 -0
  139. package/src/cmd/ai/opencode/install.ts +120 -0
  140. package/src/cmd/ai/opencode/run.ts +103 -0
  141. package/src/cmd/ai/opencode/uninstall.ts +90 -0
  142. package/src/cmd/auth/index.ts +3 -0
  143. package/src/cmd/auth/org/index.ts +142 -0
  144. package/src/cmd/build/vite/beacon-plugin.ts +164 -0
  145. package/src/cmd/build/vite/vite-builder.ts +19 -2
  146. package/src/cmd/build/webanalytics-generator.ts +25 -9
  147. package/src/cmd/cloud/db/get.ts +7 -0
  148. package/src/cmd/cloud/db/list.ts +20 -6
  149. package/src/cmd/cloud/deploy.ts +32 -1
  150. package/src/cmd/cloud/deployment/show.ts +5 -0
  151. package/src/cmd/cloud/index.ts +3 -0
  152. package/src/cmd/cloud/region/index.ts +157 -0
  153. package/src/cmd/cloud/sandbox/snapshot/build.ts +42 -5
  154. package/src/cmd/cloud/scp/download.ts +6 -2
  155. package/src/cmd/cloud/scp/upload.ts +6 -2
  156. package/src/cmd/cloud/ssh.ts +5 -1
  157. package/src/cmd/cloud/storage/get.ts +12 -5
  158. package/src/cmd/cloud/storage/list.ts +11 -0
  159. package/src/cmd/dev/index.ts +62 -5
  160. package/src/cmd/help/index.ts +8 -22
  161. package/src/cmd/project/create.ts +19 -9
  162. package/src/cmd/project/download.ts +7 -1
  163. package/src/cmd/project/import.ts +98 -0
  164. package/src/cmd/project/index.ts +3 -0
  165. package/src/cmd/project/reconcile.ts +606 -0
  166. package/src/cmd/project/template-flow.ts +69 -18
  167. package/src/config.ts +58 -22
  168. package/src/legacy-check.ts +10 -0
  169. package/src/program-ref.ts +11 -0
  170. package/src/regions.ts +95 -0
  171. package/src/tui/prompt.ts +10 -3
  172. package/src/tui.ts +82 -26
  173. package/src/types.ts +2 -0
@@ -0,0 +1,120 @@
1
+ import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { createSubcommand, type CommandContext } from '../../../types';
5
+ import * as tui from '../../../tui';
6
+ import { getCommand } from '../../../command-prefix';
7
+
8
+ const OPENCODE_CONFIG_DIR = join(homedir(), '.config', 'opencode');
9
+ const OPENCODE_CONFIG_FILE = join(OPENCODE_CONFIG_DIR, 'opencode.json');
10
+
11
+ export const installSubcommand = createSubcommand({
12
+ name: 'install',
13
+ description: 'Install Agentuity Open Code plugin',
14
+ tags: ['fast'],
15
+ requires: { auth: true, apiClient: true, org: true },
16
+ examples: [
17
+ {
18
+ command: getCommand('ai opencode install'),
19
+ description: 'Install Agentuity Open Code plugin',
20
+ },
21
+ ],
22
+ async handler(ctx: CommandContext<{ auth: true; apiClient: true; org: true }>) {
23
+ const { options, orgId } = ctx;
24
+ const jsonMode = !!options?.json;
25
+
26
+ if (!jsonMode) {
27
+ tui.newline();
28
+ tui.output(tui.bold('Installing Agentuity Open Code plugin'));
29
+ tui.newline();
30
+ tui.success(`Using organization: ${orgId}`);
31
+ }
32
+
33
+ const pluginEntry = '@agentuity/opencode';
34
+
35
+ // Update Open Code config if needed
36
+ let openCodeUpdated = false;
37
+ if (!existsSync(OPENCODE_CONFIG_DIR)) {
38
+ mkdirSync(OPENCODE_CONFIG_DIR, { recursive: true });
39
+ }
40
+
41
+ let openCodeConfig: { plugin?: string[]; $schema?: string } = {};
42
+ if (existsSync(OPENCODE_CONFIG_FILE)) {
43
+ try {
44
+ const content = readFileSync(OPENCODE_CONFIG_FILE, 'utf-8');
45
+ openCodeConfig = JSON.parse(content);
46
+ } catch {
47
+ openCodeConfig = {};
48
+ }
49
+ }
50
+
51
+ if (!openCodeConfig.plugin) {
52
+ openCodeConfig.plugin = [];
53
+ }
54
+
55
+ // Check if the exact plugin entry already exists
56
+ const hasExactEntry = openCodeConfig.plugin.includes(pluginEntry);
57
+
58
+ // Check if there's an existing entry that needs updating
59
+ const existingIndex = openCodeConfig.plugin.findIndex((p) => p === '@agentuity/opencode');
60
+
61
+ if (hasExactEntry) {
62
+ if (!jsonMode) {
63
+ tui.info('Open Code plugin already configured');
64
+ }
65
+ } else if (existingIndex >= 0) {
66
+ // Update existing entry to new value
67
+ openCodeConfig.plugin[existingIndex] = pluginEntry;
68
+ writeFileSync(OPENCODE_CONFIG_FILE, JSON.stringify(openCodeConfig, null, 2));
69
+ if (!jsonMode) {
70
+ tui.success(`Updated Open Code plugin to: ${pluginEntry}`);
71
+ }
72
+ openCodeUpdated = true;
73
+ } else {
74
+ // Add new entry
75
+ openCodeConfig.plugin.push(pluginEntry);
76
+ writeFileSync(OPENCODE_CONFIG_FILE, JSON.stringify(openCodeConfig, null, 2));
77
+ if (!jsonMode) {
78
+ tui.success(`Added ${pluginEntry} to Open Code config`);
79
+ }
80
+ openCodeUpdated = true;
81
+ }
82
+
83
+ // Summary (TUI only)
84
+ if (!jsonMode) {
85
+ tui.newline();
86
+ if (openCodeUpdated) {
87
+ tui.output(tui.bold('Agentuity Open Code plugin installed successfully!'));
88
+ } else {
89
+ tui.output(tui.bold('Agentuity Open Code plugin already installed'));
90
+ }
91
+
92
+ tui.newline();
93
+ tui.info('Next steps:');
94
+ tui.output(` ${tui.ICONS.bullet} Start Open Code to use Agentuity Coder agents`);
95
+ tui.output(
96
+ ` ${tui.ICONS.bullet} Run ${tui.bold(getCommand('ai opencode run "<task>"'))} to execute tasks`
97
+ );
98
+ tui.newline();
99
+
100
+ tui.output(tui.muted('Recommended MCP servers for Scout/Expert agents:'));
101
+ tui.output(tui.muted('Add to your opencode.json:'));
102
+ tui.newline();
103
+ tui.output(tui.muted(' "mcp": {'));
104
+ tui.output(
105
+ tui.muted(
106
+ ' "context7": { "type": "remote", "url": "https://mcp.context7.com/mcp" },'
107
+ )
108
+ );
109
+ tui.output(
110
+ tui.muted(' "grep_app": { "type": "remote", "url": "https://mcp.grep.app" }')
111
+ );
112
+ tui.output(tui.muted(' }'));
113
+ tui.newline();
114
+ }
115
+
116
+ return { success: true, orgId };
117
+ },
118
+ });
119
+
120
+ export default installSubcommand;
@@ -0,0 +1,103 @@
1
+ import { createSubcommand, type CommandContext } from '../../../types';
2
+ import * as tui from '../../../tui';
3
+ import { getCommand } from '../../../command-prefix';
4
+ import { z } from 'zod';
5
+
6
+ const RunArgsSchema = z.object({
7
+ task: z.string().describe('The task description to execute'),
8
+ });
9
+
10
+ const RunOptionsSchema = z.object({
11
+ sandbox: z.boolean().optional().describe('Run in a cloud sandbox environment'),
12
+ json: z.boolean().optional().describe('Output raw JSON events'),
13
+ });
14
+
15
+ export const runSubcommand = createSubcommand({
16
+ name: 'run',
17
+ description: 'Run a task with the Agentuity Coder agent team (non-interactive)',
18
+ tags: ['slow'],
19
+ requires: { auth: true },
20
+ schema: {
21
+ args: RunArgsSchema,
22
+ options: RunOptionsSchema,
23
+ },
24
+ examples: [
25
+ {
26
+ command: getCommand('ai opencode run "implement dark mode toggle"'),
27
+ description: 'Run a coding task',
28
+ },
29
+ {
30
+ command: getCommand('ai opencode run --sandbox "run the test suite"'),
31
+ description: 'Run in a cloud sandbox',
32
+ },
33
+ {
34
+ command: getCommand('ai opencode run --json "fix the bug"'),
35
+ description: 'Output raw JSON events',
36
+ },
37
+ ],
38
+ async handler(
39
+ ctx: CommandContext<{ auth: true }, undefined, typeof RunArgsSchema, typeof RunOptionsSchema>
40
+ ) {
41
+ const { logger, args, opts } = ctx;
42
+ const { task } = args;
43
+ const sandbox = opts?.sandbox;
44
+ const json = opts?.json;
45
+
46
+ if (!json) {
47
+ tui.newline();
48
+ tui.output(tui.bold('Agentuity Coder'));
49
+ tui.newline();
50
+ tui.output(`${tui.ICONS.arrow} Running task: ${task}`);
51
+ if (sandbox) {
52
+ tui.info('Sandbox mode enabled');
53
+ }
54
+ tui.newline();
55
+ }
56
+
57
+ const openCodeArgs = ['run', '--agent', 'Agentuity Coder Lead'];
58
+
59
+ if (json) {
60
+ openCodeArgs.push('--format', 'json');
61
+ }
62
+
63
+ // Build the task prompt with any mode prefixes
64
+ let taskPrompt = task;
65
+ if (sandbox) {
66
+ taskPrompt = `[SANDBOX MODE: Execute all code in an Agentuity cloud sandbox, not locally] ${taskPrompt}`;
67
+ }
68
+ if (json) {
69
+ taskPrompt = `[JSON OUTPUT: Your final response MUST be a valid JSON object with this structure: {"status": "success" | "failed" | "partial", "summary": "Brief description of what was done", "filesChanged": ["path/to/file.ts"], "errors": ["error message if any"], "payload": <any task-specific return data or null>}. Output ONLY the JSON, no other text.] ${taskPrompt}`;
70
+ }
71
+
72
+ openCodeArgs.push(taskPrompt);
73
+
74
+ logger.debug(`Spawning: opencode ${openCodeArgs.join(' ')}`);
75
+
76
+ const proc = Bun.spawn(['opencode', ...openCodeArgs], {
77
+ stdio: ['inherit', 'inherit', 'inherit'],
78
+ env: {
79
+ ...process.env,
80
+ AGENTUITY_CODER_MODE: 'non-interactive',
81
+ },
82
+ });
83
+
84
+ const exitCode = await proc.exited;
85
+
86
+ // In JSON mode, let opencode's output speak for itself - exit code indicates success
87
+ if (json) {
88
+ process.exit(exitCode);
89
+ }
90
+
91
+ if (exitCode === 0) {
92
+ tui.newline();
93
+ tui.success('Task completed');
94
+ } else {
95
+ tui.newline();
96
+ tui.error(`Task failed with exit code ${exitCode}`);
97
+ }
98
+
99
+ return { success: exitCode === 0, task, exitCode, sandbox: !!sandbox };
100
+ },
101
+ });
102
+
103
+ export default runSubcommand;
@@ -0,0 +1,90 @@
1
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { createSubcommand } from '../../../types';
5
+ import * as tui from '../../../tui';
6
+ import { getCommand } from '../../../command-prefix';
7
+
8
+ const OPENCODE_CONFIG_DIR = join(homedir(), '.config', 'opencode');
9
+ const OPENCODE_CONFIG_FILE = join(OPENCODE_CONFIG_DIR, 'opencode.json');
10
+
11
+ export const uninstallSubcommand = createSubcommand({
12
+ name: 'uninstall',
13
+ description: 'Uninstall Agentuity Open Code plugin',
14
+ tags: ['fast'],
15
+ examples: [
16
+ {
17
+ command: getCommand('ai opencode uninstall'),
18
+ description: 'Uninstall Agentuity Open Code plugin',
19
+ },
20
+ ],
21
+ async handler(ctx) {
22
+ const { options } = ctx;
23
+ const jsonMode = !!options?.json;
24
+
25
+ if (!jsonMode) {
26
+ tui.newline();
27
+ tui.output(tui.bold('Uninstalling Agentuity Open Code plugin'));
28
+ tui.newline();
29
+ }
30
+
31
+ let removedFromOpenCode = false;
32
+
33
+ // Remove from OpenCode config
34
+ if (!existsSync(OPENCODE_CONFIG_FILE)) {
35
+ if (!jsonMode) {
36
+ tui.info('Open Code config not found - nothing to uninstall');
37
+ }
38
+ } else {
39
+ try {
40
+ const content = readFileSync(OPENCODE_CONFIG_FILE, 'utf-8');
41
+ const openCodeConfig: { plugin?: string[]; $schema?: string } = JSON.parse(content);
42
+
43
+ if (!openCodeConfig.plugin || openCodeConfig.plugin.length === 0) {
44
+ if (!jsonMode) {
45
+ tui.info('No plugins configured in Open Code');
46
+ }
47
+ } else {
48
+ const originalLength = openCodeConfig.plugin.length;
49
+ openCodeConfig.plugin = openCodeConfig.plugin.filter(
50
+ (p) => p !== '@agentuity/opencode'
51
+ );
52
+
53
+ if (openCodeConfig.plugin.length < originalLength) {
54
+ writeFileSync(OPENCODE_CONFIG_FILE, JSON.stringify(openCodeConfig, null, 2));
55
+ if (!jsonMode) {
56
+ tui.success('Removed Agentuity Open Code plugin');
57
+ }
58
+ removedFromOpenCode = true;
59
+ } else {
60
+ if (!jsonMode) {
61
+ tui.info('Agentuity Open Code plugin not found');
62
+ }
63
+ }
64
+ }
65
+ } catch (error) {
66
+ if (!jsonMode) {
67
+ tui.warn(`Failed to parse Open Code config: ${error}`);
68
+ }
69
+ }
70
+ }
71
+
72
+ if (!jsonMode) {
73
+ tui.newline();
74
+
75
+ if (removedFromOpenCode) {
76
+ tui.output(tui.bold('Agentuity Open Code plugin uninstalled successfully'));
77
+ } else {
78
+ tui.output(tui.bold('Agentuity Open Code plugin was not installed'));
79
+ }
80
+
81
+ tui.newline();
82
+ tui.info(`To reinstall, run: ${tui.bold(getCommand('ai opencode install'))}`);
83
+ tui.newline();
84
+ }
85
+
86
+ return { success: true, removedFromOpenCode };
87
+ },
88
+ });
89
+
90
+ export default uninstallSubcommand;
@@ -5,6 +5,7 @@ import { logoutCommand } from './logout';
5
5
  import { signupCommand } from './signup';
6
6
  import { whoamiCommand } from './whoami';
7
7
  import { sshSubcommand } from './ssh';
8
+ import { orgSubcommand } from './org';
8
9
  import { getCommand } from '../../command-prefix';
9
10
 
10
11
  export const command = createCommand({
@@ -14,6 +15,7 @@ export const command = createCommand({
14
15
  examples: [
15
16
  { command: getCommand('auth login'), description: 'Login to your account' },
16
17
  { command: getCommand('auth whoami'), description: 'Show current user info' },
18
+ { command: getCommand('auth org select'), description: 'Set default organization' },
17
19
  ],
18
20
  subcommands: [
19
21
  apikeyCommand,
@@ -22,5 +24,6 @@ export const command = createCommand({
22
24
  signupCommand,
23
25
  whoamiCommand,
24
26
  sshSubcommand,
27
+ orgSubcommand,
25
28
  ],
26
29
  });
@@ -0,0 +1,142 @@
1
+ import { z } from 'zod';
2
+ import { createSubcommand, createCommand } from '../../../types';
3
+ import { getCommand } from '../../../command-prefix';
4
+ import { saveOrgId, clearOrgId } from '../../../config';
5
+ import * as tui from '../../../tui';
6
+ import { listOrganizations } from '@agentuity/server';
7
+
8
+ const selectCommand = createSubcommand({
9
+ name: 'select',
10
+ description: 'Set the default organization for all commands',
11
+ tags: ['fast', 'requires-auth'],
12
+ requires: { auth: true, apiClient: true },
13
+ examples: [
14
+ { command: getCommand('auth org select'), description: 'Select default organization' },
15
+ {
16
+ command: getCommand('auth org select org_abc123'),
17
+ description: 'Set specific organization as default',
18
+ },
19
+ ],
20
+ schema: {
21
+ args: z.object({
22
+ org_id: z.string().optional().describe('Organization ID to set as default'),
23
+ }),
24
+ response: z.object({
25
+ orgId: z.string().describe('The selected organization ID'),
26
+ name: z.string().describe('The organization name'),
27
+ }),
28
+ },
29
+
30
+ async handler(ctx) {
31
+ const { apiClient, args, options } = ctx;
32
+ const argOrgId = args.org_id;
33
+
34
+ const orgs = await tui.spinner({
35
+ message: 'Fetching organizations',
36
+ clearOnSuccess: true,
37
+ callback: () => listOrganizations(apiClient),
38
+ });
39
+
40
+ if (orgs.length === 0) {
41
+ tui.fatal('No organizations found for your account');
42
+ }
43
+
44
+ let selectedOrgId = argOrgId;
45
+
46
+ if (selectedOrgId) {
47
+ const org = orgs.find((o) => o.id === selectedOrgId);
48
+ if (!org) {
49
+ tui.fatal(
50
+ `Organization '${selectedOrgId}' not found. Use '${getCommand('auth org select')}' to see available organizations.`
51
+ );
52
+ }
53
+ } else {
54
+ if (!process.stdin.isTTY) {
55
+ tui.fatal(
56
+ 'Organization ID required in non-interactive mode. Usage: ' +
57
+ getCommand('auth org select <org_id>')
58
+ );
59
+ }
60
+ selectedOrgId = await tui.selectOrganization(orgs);
61
+ }
62
+
63
+ await saveOrgId(selectedOrgId);
64
+
65
+ const selectedOrg = orgs.find((o) => o.id === selectedOrgId)!;
66
+
67
+ if (!options.json) {
68
+ tui.success(`Default organization set to ${tui.bold(selectedOrg.name)}`);
69
+ }
70
+
71
+ return { orgId: selectedOrgId, name: selectedOrg.name };
72
+ },
73
+ });
74
+
75
+ const unselectCommand = createSubcommand({
76
+ name: 'unselect',
77
+ description: 'Clear the default organization preference',
78
+ tags: ['fast'],
79
+ examples: [
80
+ { command: getCommand('auth org unselect'), description: 'Clear default organization' },
81
+ ],
82
+ schema: {
83
+ response: z.object({
84
+ cleared: z.boolean().describe('Whether the preference was cleared'),
85
+ }),
86
+ },
87
+
88
+ async handler(ctx) {
89
+ const { options, config } = ctx;
90
+
91
+ const hadOrg = !!config?.preferences?.orgId;
92
+ await clearOrgId();
93
+
94
+ if (!options.json) {
95
+ if (hadOrg) {
96
+ tui.success('Default organization cleared');
97
+ } else {
98
+ tui.info('No default organization was set');
99
+ }
100
+ }
101
+
102
+ return { cleared: hadOrg };
103
+ },
104
+ });
105
+
106
+ const currentCommand = createSubcommand({
107
+ name: 'current',
108
+ description: 'Show the current default organization',
109
+ tags: ['read-only', 'fast'],
110
+ idempotent: true,
111
+ examples: [
112
+ { command: getCommand('auth org current'), description: 'Show default organization' },
113
+ { command: getCommand('auth org current --json'), description: 'Show output in JSON format' },
114
+ ],
115
+ schema: {
116
+ response: z.string().nullable().describe('The current organization ID or null if not set'),
117
+ },
118
+
119
+ async handler(ctx) {
120
+ const { options, config } = ctx;
121
+ const orgId = config?.preferences?.orgId || null;
122
+
123
+ if (!options.json) {
124
+ if (orgId) {
125
+ console.log(orgId);
126
+ }
127
+ }
128
+
129
+ return orgId;
130
+ },
131
+ });
132
+
133
+ export const orgSubcommand = createCommand({
134
+ name: 'org',
135
+ description: 'Manage default organization preference',
136
+ tags: ['fast'],
137
+ examples: [
138
+ { command: getCommand('auth org select'), description: 'Set default organization' },
139
+ { command: getCommand('auth org current'), description: 'Show current default' },
140
+ ],
141
+ subcommands: [selectCommand, unselectCommand, currentCommand],
142
+ });
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Vite plugin to emit the analytics beacon as a hashed CDN asset
3
+ *
4
+ * This plugin:
5
+ * 1. Reads the pre-built beacon from @agentuity/frontend
6
+ * 2. Emits it as a Rollup asset with content-based hashing
7
+ * 3. Injects a <script data-agentuity-beacon> tag into the HTML
8
+ */
9
+
10
+ import type { Plugin, ResolvedConfig } from 'vite';
11
+ import { join } from 'node:path';
12
+ import { createRequire } from 'node:module';
13
+
14
+ export interface BeaconPluginOptions {
15
+ /** Whether analytics is enabled */
16
+ enabled: boolean;
17
+ }
18
+
19
+ /**
20
+ * Read the pre-built beacon script from @agentuity/frontend package
21
+ * @param projectRoot - The root directory of the project (for resolving node_modules)
22
+ */
23
+ async function readBeaconScript(projectRoot: string): Promise<string> {
24
+ let frontendPath: string | null = null;
25
+
26
+ // Strategy 1: Use Bun.resolve (works with workspaces and symlinks)
27
+ try {
28
+ frontendPath = await Bun.resolve('@agentuity/frontend', projectRoot);
29
+ } catch {
30
+ // Not found from project root
31
+ }
32
+
33
+ // Strategy 2: Try from this file's directory (for installed CLI case)
34
+ if (!frontendPath) {
35
+ try {
36
+ const thisDir = new URL('.', import.meta.url).pathname;
37
+ frontendPath = await Bun.resolve('@agentuity/frontend', thisDir);
38
+ } catch {
39
+ // Not found from CLI directory
40
+ }
41
+ }
42
+
43
+ // Strategy 3: Fallback to createRequire for Node.js compatibility
44
+ if (!frontendPath) {
45
+ try {
46
+ const projectRequire = createRequire(join(projectRoot, 'package.json'));
47
+ frontendPath = projectRequire.resolve('@agentuity/frontend');
48
+ } catch {
49
+ // Not found via createRequire
50
+ }
51
+ }
52
+
53
+ if (!frontendPath) {
54
+ throw new Error(
55
+ 'Could not resolve @agentuity/frontend. Ensure the package is installed and built.'
56
+ );
57
+ }
58
+
59
+ // The beacon.js file is in the dist folder of @agentuity/frontend
60
+ // frontendPath points to dist/index.js, so we go up one level
61
+ const packageDir = join(frontendPath, '..');
62
+ const beaconPath = join(packageDir, 'beacon.js');
63
+
64
+ const beaconFile = Bun.file(beaconPath);
65
+ if (!(await beaconFile.exists())) {
66
+ throw new Error(
67
+ `Beacon script not found at ${beaconPath}. Run "bun run build" in @agentuity/frontend first.`
68
+ );
69
+ }
70
+
71
+ return beaconFile.text();
72
+ }
73
+
74
+ /**
75
+ * Vite plugin that emits the analytics beacon as a hashed asset
76
+ * and injects a script tag into the HTML
77
+ */
78
+ export function beaconPlugin(options: BeaconPluginOptions): Plugin {
79
+ const { enabled } = options;
80
+
81
+ let resolvedConfig: ResolvedConfig | null = null;
82
+ let beaconReferenceId: string | null = null;
83
+
84
+ return {
85
+ name: 'agentuity:beacon',
86
+ apply: 'build',
87
+
88
+ configResolved(config) {
89
+ resolvedConfig = config;
90
+ },
91
+
92
+ async buildStart() {
93
+ if (!enabled) return;
94
+
95
+ try {
96
+ // Get project root from Vite config
97
+ const projectRoot = resolvedConfig?.root || process.cwd();
98
+ const beaconCode = await readBeaconScript(projectRoot);
99
+
100
+ // Emit the beacon as an asset - Rollup will hash it
101
+ beaconReferenceId = this.emitFile({
102
+ type: 'asset',
103
+ name: 'agentuity-beacon.js',
104
+ source: beaconCode,
105
+ });
106
+ } catch (error) {
107
+ this.error(
108
+ `Failed to read beacon script: ${error instanceof Error ? error.message : String(error)}`
109
+ );
110
+ }
111
+ },
112
+
113
+ // Use transformIndexHtml hook to modify the HTML
114
+ // This is called during the HTML transformation phase and works with Vite's HTML pipeline
115
+ transformIndexHtml: {
116
+ order: 'post',
117
+ handler(html, ctx) {
118
+ if (!enabled || !beaconReferenceId) return html;
119
+
120
+ // Get the final hashed filename using the bundle from the context
121
+ // We need to use ctx.bundle to access the emitted file
122
+ const bundle = ctx.bundle;
123
+ if (!bundle) return html;
124
+
125
+ // Find our beacon asset in the bundle
126
+ let beaconFileName: string | null = null;
127
+ for (const fileName of Object.keys(bundle)) {
128
+ if (fileName.includes('agentuity-beacon') && fileName.endsWith('.js')) {
129
+ beaconFileName = fileName;
130
+ break;
131
+ }
132
+ }
133
+
134
+ if (!beaconFileName) return html;
135
+
136
+ // Build the beacon URL using Vite's configured base (respects CDN URL)
137
+ // resolvedConfig.base is e.g., "https://cdn.agentuity.com/{deploymentId}/client/" or "/"
138
+ const base = resolvedConfig?.base || '/';
139
+ // Normalize: ensure base ends with / and beaconFileName doesn't start with /
140
+ const normalizedBase = base.endsWith('/') ? base : `${base}/`;
141
+ const normalizedFileName = beaconFileName.startsWith('/')
142
+ ? beaconFileName.slice(1)
143
+ : beaconFileName;
144
+ const beaconUrl = `${normalizedBase}${normalizedFileName}`;
145
+
146
+ // Inject a marker script tag
147
+ // The script must be sync (no async/defer) to patch history API before router loads
148
+ // Use data-agentuity-beacon attribute as a marker for config/session injection
149
+ const beaconScript = `<script data-agentuity-beacon src="${beaconUrl}"></script>`;
150
+
151
+ // Inject before </head>
152
+ if (html.includes('</head>')) {
153
+ return html.replace('</head>', `${beaconScript}</head>`);
154
+ }
155
+ // Fallback: inject at start of body
156
+ if (html.includes('<body')) {
157
+ return html.replace(/<body([^>]*)>/, `<body$1>${beaconScript}`);
158
+ }
159
+
160
+ return beaconScript + html;
161
+ },
162
+ },
163
+ };
164
+ }
@@ -10,6 +10,7 @@ import { createRequire } from 'node:module';
10
10
  import type { InlineConfig, Plugin } from 'vite';
11
11
  import type { Logger, DeployOptions } from '../../../types';
12
12
  import { browserEnvPlugin } from './browser-env-plugin';
13
+ import { beaconPlugin } from './beacon-plugin';
13
14
  import type { BuildReportCollector } from '../../../build-report';
14
15
 
15
16
  /**
@@ -55,6 +56,8 @@ export interface ViteBuildOptions {
55
56
  deploymentId?: string;
56
57
  workbenchRoute?: string;
57
58
  workbenchEnabled?: boolean;
59
+ /** Whether analytics is enabled (for beacon injection in client build) */
60
+ analyticsEnabled?: boolean;
58
61
  logger: Logger;
59
62
  deploymentOptions?: DeployOptions;
60
63
  /** Optional collector for structured error reporting */
@@ -139,11 +142,21 @@ export async function runViteBuild(options: ViteBuildOptions): Promise<void> {
139
142
  const htmlPath = join(rootDir, 'src', 'web', 'index.html');
140
143
 
141
144
  // Use workbench config passed from runAllBuilds
142
- const { workbenchEnabled = false, workbenchRoute = '/workbench' } = options;
145
+ const {
146
+ workbenchEnabled = false,
147
+ workbenchRoute = '/workbench',
148
+ analyticsEnabled = false,
149
+ } = options;
143
150
 
144
151
  // Load custom user plugins from agentuity.config.ts if it exists
145
152
  const clientOutDir = join(rootDir, '.agentuity/client');
146
- const plugins = [react(), browserEnvPlugin(), flattenHtmlOutputPlugin(clientOutDir)];
153
+ const plugins = [
154
+ react(),
155
+ browserEnvPlugin(),
156
+ flattenHtmlOutputPlugin(clientOutDir),
157
+ // Emit analytics beacon as hashed CDN asset (prod builds only)
158
+ beaconPlugin({ enabled: analyticsEnabled && !dev }),
159
+ ];
147
160
  const { loadAgentuityConfig } = await import('./config-loader');
148
161
  const userConfig = await loadAgentuityConfig(rootDir, logger);
149
162
  const userPlugins = userConfig?.plugins || [];
@@ -312,6 +325,9 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
312
325
  // Check if web frontend exists
313
326
  const hasWebFrontend = await Bun.file(join(rootDir, 'src', 'web', 'index.html')).exists();
314
327
 
328
+ // Check if analytics is enabled
329
+ const analyticsEnabled = config?.analytics !== false;
330
+
315
331
  // 2. Build client (only if web frontend exists)
316
332
  if (hasWebFrontend) {
317
333
  logger.debug('Building client assets...');
@@ -322,6 +338,7 @@ export async function runAllBuilds(options: Omit<ViteBuildOptions, 'mode'>): Pro
322
338
  mode: 'client',
323
339
  workbenchEnabled: workbenchConfig.enabled,
324
340
  workbenchRoute: workbenchConfig.route,
341
+ analyticsEnabled,
325
342
  });
326
343
  result.client.included = true;
327
344
  result.client.duration = Date.now() - started;