@agentuity/cli 0.1.33 → 0.1.35

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 (122) hide show
  1. package/bin/cli.ts +107 -6
  2. package/dist/cmd/ai/index.d.ts.map +1 -1
  3. package/dist/cmd/ai/index.js +0 -6
  4. package/dist/cmd/ai/index.js.map +1 -1
  5. package/dist/cmd/ai/opencode/uninstall.js +1 -1
  6. package/dist/cmd/ai/opencode/uninstall.js.map +1 -1
  7. package/dist/cmd/build/vite/bun-dev-server.d.ts +6 -0
  8. package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
  9. package/dist/cmd/build/vite/bun-dev-server.js +100 -33
  10. package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
  11. package/dist/cmd/cloud/apikey/create.d.ts.map +1 -1
  12. package/dist/cmd/cloud/apikey/create.js +1 -1
  13. package/dist/cmd/cloud/apikey/create.js.map +1 -1
  14. package/dist/cmd/cloud/deploy.d.ts.map +1 -1
  15. package/dist/cmd/cloud/deploy.js +4 -1
  16. package/dist/cmd/cloud/deploy.js.map +1 -1
  17. package/dist/cmd/cloud/env/delete.d.ts.map +1 -1
  18. package/dist/cmd/cloud/env/delete.js +100 -45
  19. package/dist/cmd/cloud/env/delete.js.map +1 -1
  20. package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
  21. package/dist/cmd/cloud/env/pull.js +1 -1
  22. package/dist/cmd/cloud/env/pull.js.map +1 -1
  23. package/dist/cmd/cloud/machine/delete.js +1 -1
  24. package/dist/cmd/cloud/machine/delete.js.map +1 -1
  25. package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
  26. package/dist/cmd/cloud/sandbox/exec.js +10 -35
  27. package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
  28. package/dist/cmd/dev/index.d.ts.map +1 -1
  29. package/dist/cmd/dev/index.js +58 -10
  30. package/dist/cmd/dev/index.js.map +1 -1
  31. package/dist/cmd/index.d.ts.map +1 -1
  32. package/dist/cmd/index.js +1 -0
  33. package/dist/cmd/index.js.map +1 -1
  34. package/dist/cmd/support/index.d.ts +2 -0
  35. package/dist/cmd/support/index.d.ts.map +1 -0
  36. package/dist/cmd/support/index.js +11 -0
  37. package/dist/cmd/support/index.js.map +1 -0
  38. package/dist/cmd/support/logs/index.d.ts +3 -0
  39. package/dist/cmd/support/logs/index.d.ts.map +1 -0
  40. package/dist/cmd/support/logs/index.js +9 -0
  41. package/dist/cmd/support/logs/index.js.map +1 -0
  42. package/dist/cmd/support/logs/path.d.ts +3 -0
  43. package/dist/cmd/support/logs/path.d.ts.map +1 -0
  44. package/dist/cmd/support/logs/path.js +52 -0
  45. package/dist/cmd/support/logs/path.js.map +1 -0
  46. package/dist/cmd/support/logs/show.d.ts +3 -0
  47. package/dist/cmd/support/logs/show.d.ts.map +1 -0
  48. package/dist/cmd/support/logs/show.js +121 -0
  49. package/dist/cmd/support/logs/show.js.map +1 -0
  50. package/dist/cmd/support/report.d.ts +3 -0
  51. package/dist/cmd/support/report.d.ts.map +1 -0
  52. package/dist/cmd/support/report.js +299 -0
  53. package/dist/cmd/support/report.js.map +1 -0
  54. package/dist/cmd/support/system.d.ts +3 -0
  55. package/dist/cmd/support/system.d.ts.map +1 -0
  56. package/dist/cmd/support/system.js +120 -0
  57. package/dist/cmd/support/system.js.map +1 -0
  58. package/dist/cmd/version/index.d.ts.map +1 -1
  59. package/dist/cmd/version/index.js +1 -0
  60. package/dist/cmd/version/index.js.map +1 -1
  61. package/dist/composite-logger.d.ts +35 -0
  62. package/dist/composite-logger.d.ts.map +1 -0
  63. package/dist/composite-logger.js +78 -0
  64. package/dist/composite-logger.js.map +1 -0
  65. package/dist/index.d.ts +2 -0
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +2 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/internal-logger.d.ts +77 -0
  70. package/dist/internal-logger.d.ts.map +1 -0
  71. package/dist/internal-logger.js +340 -0
  72. package/dist/internal-logger.js.map +1 -0
  73. package/dist/types.d.ts +6 -0
  74. package/dist/types.d.ts.map +1 -1
  75. package/dist/types.js.map +1 -1
  76. package/dist/utils/installation-type.d.ts.map +1 -1
  77. package/dist/utils/installation-type.js +54 -16
  78. package/dist/utils/installation-type.js.map +1 -1
  79. package/package.json +6 -6
  80. package/src/cmd/ai/index.ts +0 -6
  81. package/src/cmd/ai/opencode/uninstall.ts +1 -1
  82. package/src/cmd/build/vite/bun-dev-server.ts +113 -36
  83. package/src/cmd/cloud/apikey/create.ts +3 -1
  84. package/src/cmd/cloud/deploy.ts +4 -1
  85. package/src/cmd/cloud/env/delete.ts +100 -45
  86. package/src/cmd/cloud/env/pull.ts +1 -6
  87. package/src/cmd/cloud/machine/delete.ts +1 -1
  88. package/src/cmd/cloud/sandbox/exec.ts +10 -41
  89. package/src/cmd/dev/index.ts +59 -11
  90. package/src/cmd/index.ts +1 -0
  91. package/src/cmd/support/index.ts +11 -0
  92. package/src/cmd/support/logs/index.ts +9 -0
  93. package/src/cmd/support/logs/path.ts +56 -0
  94. package/src/cmd/support/logs/show.ts +144 -0
  95. package/src/cmd/support/report.ts +364 -0
  96. package/src/cmd/support/system.ts +130 -0
  97. package/src/cmd/version/index.ts +1 -0
  98. package/src/composite-logger.ts +86 -0
  99. package/src/index.ts +7 -0
  100. package/src/internal-logger.ts +411 -0
  101. package/src/types.ts +6 -0
  102. package/src/utils/installation-type.ts +55 -16
  103. package/dist/cmd/ai/skills/generate.d.ts +0 -3
  104. package/dist/cmd/ai/skills/generate.d.ts.map +0 -1
  105. package/dist/cmd/ai/skills/generate.js +0 -65
  106. package/dist/cmd/ai/skills/generate.js.map +0 -1
  107. package/dist/cmd/ai/skills/generator.d.ts +0 -4
  108. package/dist/cmd/ai/skills/generator.d.ts.map +0 -1
  109. package/dist/cmd/ai/skills/generator.js +0 -410
  110. package/dist/cmd/ai/skills/generator.js.map +0 -1
  111. package/dist/cmd/ai/skills/index.d.ts +0 -4
  112. package/dist/cmd/ai/skills/index.d.ts.map +0 -1
  113. package/dist/cmd/ai/skills/index.js +0 -21
  114. package/dist/cmd/ai/skills/index.js.map +0 -1
  115. package/dist/cmd/dev/skills.d.ts +0 -10
  116. package/dist/cmd/dev/skills.d.ts.map +0 -1
  117. package/dist/cmd/dev/skills.js +0 -57
  118. package/dist/cmd/dev/skills.js.map +0 -1
  119. package/src/cmd/ai/skills/generate.ts +0 -75
  120. package/src/cmd/ai/skills/generator.ts +0 -527
  121. package/src/cmd/ai/skills/index.ts +0 -23
  122. package/src/cmd/dev/skills.ts +0 -82
@@ -0,0 +1,411 @@
1
+ /**
2
+ * Internal Logger for CLI command tracing
3
+ *
4
+ * This logger captures all CLI execution details for debugging purposes.
5
+ * It maintains two files per command execution:
6
+ * 1. session.json - Command metadata, environment, and system info
7
+ * 2. logs.jsonl - JSON Lines format log entries
8
+ *
9
+ * The logger automatically cleans up old logs, keeping only the most recent execution.
10
+ */
11
+
12
+ import {
13
+ appendFileSync,
14
+ existsSync,
15
+ mkdirSync,
16
+ readdirSync,
17
+ rmSync,
18
+ writeFileSync,
19
+ readFileSync,
20
+ } from 'node:fs';
21
+ import { join, resolve } from 'node:path';
22
+ import { homedir, platform, arch, cpus, totalmem } from 'node:os';
23
+ import type { Logger, LogLevel } from '@agentuity/core';
24
+ import { randomUUID } from 'node:crypto';
25
+
26
+ // Sensitive environment variable patterns to mask
27
+ const SENSITIVE_ENV_PATTERNS = [
28
+ /KEY/i, // Any env var with KEY in the name
29
+ /SECRET/i,
30
+ /TOKEN/i,
31
+ /PASSWORD/i,
32
+ /^AWS_/i,
33
+ /^GCP_/i,
34
+ /^AZURE_/i,
35
+ /^CLOUDFLARE_/i,
36
+ /^DATABASE_URL$/i,
37
+ /^DB_/i,
38
+ /^QUILL_/i, // Code signing keys
39
+ /^MACOS_/i, // macOS signing keys
40
+ /_P12$/i, // Certificate files
41
+ /BEARER/i,
42
+ /CREDENTIALS?/i,
43
+ /AUTH/i,
44
+ ];
45
+
46
+ interface SessionMetadata {
47
+ sessionId: string;
48
+ command: string;
49
+ args: string[];
50
+ timestamp: string;
51
+ cli: {
52
+ version: string;
53
+ name: string;
54
+ };
55
+ system: {
56
+ platform: string;
57
+ arch: string;
58
+ cpus: number;
59
+ memory: number;
60
+ bunPath: string;
61
+ bunVersion: string;
62
+ };
63
+ environment: Record<string, string>;
64
+ cwd: string;
65
+ userId?: string;
66
+ projectId?: string;
67
+ orgId?: string;
68
+ }
69
+
70
+ interface LogEntry {
71
+ timestamp: string;
72
+ level: LogLevel;
73
+ message: string;
74
+ context?: Record<string, unknown>;
75
+ }
76
+
77
+ /**
78
+ * Mask sensitive values in environment variables
79
+ */
80
+ function maskEnvironment(): Record<string, string> {
81
+ const masked: Record<string, string> = {};
82
+ for (const [key, value] of Object.entries(process.env)) {
83
+ if (!value) continue;
84
+
85
+ // Check if this env var matches sensitive patterns
86
+ const isSensitive = SENSITIVE_ENV_PATTERNS.some((pattern) => pattern.test(key));
87
+
88
+ if (isSensitive) {
89
+ // Show only first and last 4 chars for keys/tokens, or just mask completely
90
+ if (value.length > 12) {
91
+ masked[key] = `${value.slice(0, 4)}...${value.slice(-4)}`;
92
+ } else {
93
+ masked[key] = '***MASKED***';
94
+ }
95
+ } else {
96
+ masked[key] = value;
97
+ }
98
+ }
99
+ return masked;
100
+ }
101
+
102
+ /**
103
+ * Get the logs directory path
104
+ */
105
+ function getLogsDir(): string {
106
+ return join(homedir(), '.config', 'agentuity', 'logs');
107
+ }
108
+
109
+ /**
110
+ * Clean up old log directories, keeping only the most recent one
111
+ */
112
+ function cleanupOldLogs(currentSessionId: string): void {
113
+ const logsDir = getLogsDir();
114
+ if (!existsSync(logsDir)) {
115
+ return;
116
+ }
117
+
118
+ try {
119
+ const entries = readdirSync(logsDir, { withFileTypes: true });
120
+ const dirs = entries
121
+ .filter((e) => e.isDirectory())
122
+ .map((e) => e.name)
123
+ .filter((name) => name !== currentSessionId);
124
+
125
+ // Remove all directories except the current one
126
+ for (const dir of dirs) {
127
+ const dirPath = join(logsDir, dir);
128
+ try {
129
+ rmSync(dirPath, { recursive: true, force: true });
130
+ } catch (err) {
131
+ // Ignore errors during cleanup
132
+ console.debug(`Failed to remove old log directory ${dir}: ${err}`);
133
+ }
134
+ }
135
+ } catch (err) {
136
+ // Ignore errors during cleanup
137
+ console.debug(`Failed to cleanup old logs: ${err}`);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Internal logger for capturing all CLI command execution details
143
+ */
144
+ export class InternalLogger implements Logger {
145
+ private sessionId: string;
146
+ private sessionDir: string;
147
+ private sessionFile: string;
148
+ private logsFile: string;
149
+ private initialized = false;
150
+ private disabled = false;
151
+
152
+ constructor(
153
+ private cliVersion: string,
154
+ private cliName: string
155
+ ) {
156
+ this.sessionId = randomUUID();
157
+ this.sessionDir = join(getLogsDir(), this.sessionId);
158
+ this.sessionFile = join(this.sessionDir, 'session.json');
159
+ this.logsFile = join(this.sessionDir, 'logs.jsonl');
160
+ }
161
+
162
+ /**
163
+ * Initialize the internal logger with command metadata
164
+ * @param command - The command being executed
165
+ * @param args - Command line arguments
166
+ * @param userId - Optional user ID (set later via setUserId if not provided)
167
+ * @param projectDir - Optional project directory from --dir flag (defaults to process.cwd())
168
+ */
169
+ init(command: string, args: string[], userId?: string, projectDir?: string): void {
170
+ if (this.disabled) return;
171
+
172
+ try {
173
+ // Create logs directory
174
+ mkdirSync(this.sessionDir, { recursive: true, mode: 0o700 });
175
+
176
+ // Clean up old logs (keep only this session)
177
+ cleanupOldLogs(this.sessionId);
178
+
179
+ // Determine project directory: use provided projectDir, or fall back to cwd
180
+ let workingDir = projectDir || process.cwd();
181
+
182
+ // Handle home directory expansion (~/path -> /home/user/path)
183
+ if (workingDir.startsWith('~/')) {
184
+ workingDir = join(homedir(), workingDir.slice(2));
185
+ }
186
+
187
+ // Resolve to absolute path
188
+ workingDir = resolve(workingDir);
189
+
190
+ // Check for agentuity.json in the determined directory
191
+ let projectId: string | undefined;
192
+ let orgId: string | undefined;
193
+
194
+ try {
195
+ const agentuityJsonPath = join(workingDir, 'agentuity.json');
196
+ if (existsSync(agentuityJsonPath)) {
197
+ const agentuityJson = JSON.parse(readFileSync(agentuityJsonPath, 'utf-8'));
198
+ projectId = agentuityJson.projectId;
199
+ orgId = agentuityJson.orgId;
200
+ }
201
+ } catch {
202
+ // Ignore errors reading agentuity.json
203
+ }
204
+
205
+ // Use workingDir as cwd in session metadata
206
+ const cwd = workingDir;
207
+
208
+ // Gather session metadata
209
+ const sessionMetadata: SessionMetadata = {
210
+ sessionId: this.sessionId,
211
+ command,
212
+ args,
213
+ timestamp: new Date().toISOString(),
214
+ cli: {
215
+ version: this.cliVersion,
216
+ name: this.cliName,
217
+ },
218
+ system: {
219
+ platform: platform(),
220
+ arch: arch(),
221
+ cpus: cpus().length,
222
+ memory: totalmem(),
223
+ bunPath: process.execPath || '',
224
+ bunVersion: Bun.version || process.version,
225
+ },
226
+ environment: maskEnvironment(),
227
+ cwd,
228
+ ...(userId && { userId }),
229
+ ...(projectId && { projectId }),
230
+ ...(orgId && { orgId }),
231
+ };
232
+
233
+ // Write session metadata
234
+ writeFileSync(this.sessionFile, JSON.stringify(sessionMetadata, null, 2));
235
+ this.initialized = true;
236
+ } catch (err) {
237
+ // If we fail to initialize, disable the logger
238
+ console.debug(`Failed to initialize internal logger: ${err}`);
239
+ this.disabled = true;
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Write a log entry to the logs file
245
+ */
246
+ private writeLog(level: LogLevel, message: unknown, args: unknown[]): void {
247
+ if (!this.initialized || this.disabled) return;
248
+
249
+ try {
250
+ // Format the message
251
+ let formattedMessage: string;
252
+ if (typeof message === 'string') {
253
+ // Simple sprintf-style formatting for %s and %d
254
+ formattedMessage = message;
255
+ let argIndex = 0;
256
+ formattedMessage = formattedMessage.replace(/%[sd]/g, () => {
257
+ if (argIndex < args.length) {
258
+ return String(args[argIndex++]);
259
+ }
260
+ return '';
261
+ });
262
+ // Append any remaining args
263
+ if (argIndex < args.length) {
264
+ formattedMessage += ' ' + args.slice(argIndex).map(String).join(' ');
265
+ }
266
+ } else {
267
+ formattedMessage = [message, ...args].map(String).join(' ');
268
+ }
269
+
270
+ // Strip ANSI color codes since this is going to JSON
271
+ if (typeof Bun !== 'undefined' && typeof Bun.stripANSI === 'function') {
272
+ formattedMessage = Bun.stripANSI(formattedMessage);
273
+ }
274
+
275
+ // Extract context from args (look for objects)
276
+ const context: Record<string, unknown> = {};
277
+ for (const arg of args) {
278
+ if (arg && typeof arg === 'object' && !Array.isArray(arg)) {
279
+ Object.assign(context, arg);
280
+ }
281
+ }
282
+
283
+ const entry: LogEntry = {
284
+ timestamp: new Date().toISOString(),
285
+ level,
286
+ message: formattedMessage,
287
+ ...(Object.keys(context).length > 0 && { context }),
288
+ };
289
+
290
+ appendFileSync(this.logsFile, JSON.stringify(entry) + '\n');
291
+ } catch (err) {
292
+ // If write fails, disable the logger to prevent repeated errors
293
+ console.debug(`Failed to write log entry: ${err}`);
294
+ this.disabled = true;
295
+ }
296
+ }
297
+
298
+ trace(message: unknown, ...args: unknown[]): void {
299
+ this.writeLog('trace', message, args);
300
+ }
301
+
302
+ debug(message: unknown, ...args: unknown[]): void {
303
+ this.writeLog('debug', message, args);
304
+ }
305
+
306
+ info(message: unknown, ...args: unknown[]): void {
307
+ this.writeLog('info', message, args);
308
+ }
309
+
310
+ warn(message: unknown, ...args: unknown[]): void {
311
+ this.writeLog('warn', message, args);
312
+ }
313
+
314
+ error(message: unknown, ...args: unknown[]): void {
315
+ this.writeLog('error', message, args);
316
+ }
317
+
318
+ fatal(message: unknown, ...args: unknown[]): never {
319
+ this.writeLog('error', message, args);
320
+ process.exit(1);
321
+ }
322
+
323
+ child(_opts: Record<string, unknown>): Logger {
324
+ // Return the same logger - we don't need separate child loggers for internal logging
325
+ return this;
326
+ }
327
+
328
+ /**
329
+ * Get the session ID for this logger
330
+ */
331
+ getSessionId(): string {
332
+ return this.sessionId;
333
+ }
334
+
335
+ /**
336
+ * Get the session directory path
337
+ */
338
+ getSessionDir(): string {
339
+ return this.sessionDir;
340
+ }
341
+
342
+ /**
343
+ * Check if the logger is disabled
344
+ */
345
+ isDisabled(): boolean {
346
+ return this.disabled;
347
+ }
348
+
349
+ /**
350
+ * Update the session with user ID after authentication
351
+ */
352
+ setUserId(userId: string): void {
353
+ if (!this.initialized || this.disabled) return;
354
+
355
+ try {
356
+ // Read existing session data
357
+ const existingData = JSON.parse(readFileSync(this.sessionFile, 'utf-8'));
358
+ existingData.userId = userId;
359
+ // Write updated session data
360
+ writeFileSync(this.sessionFile, JSON.stringify(existingData, null, 2));
361
+ } catch (err) {
362
+ // Ignore errors - this is a best-effort update
363
+ console.debug(`Failed to update userId in session: ${err}`);
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Disable the internal logger (prevents init and logging)
369
+ */
370
+ disable(): void {
371
+ this.disabled = true;
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Create a new internal logger instance
377
+ */
378
+ export function createInternalLogger(cliVersion: string, cliName: string): InternalLogger {
379
+ return new InternalLogger(cliVersion, cliName);
380
+ }
381
+
382
+ /**
383
+ * Get the latest log session directory (if any)
384
+ */
385
+ export function getLatestLogSession(): string | null {
386
+ const logsDir = getLogsDir();
387
+ if (!existsSync(logsDir)) {
388
+ return null;
389
+ }
390
+
391
+ try {
392
+ const entries = readdirSync(logsDir, { withFileTypes: true });
393
+ const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
394
+
395
+ if (dirs.length === 0) {
396
+ return null;
397
+ }
398
+
399
+ // Return the first directory (should be the only one due to cleanup)
400
+ return join(logsDir, dirs[0]);
401
+ } catch {
402
+ return null;
403
+ }
404
+ }
405
+
406
+ /**
407
+ * Get the logs directory path (exported for external use)
408
+ */
409
+ export function getLogsDirPath(): string {
410
+ return getLogsDir();
411
+ }
package/src/types.ts CHANGED
@@ -457,6 +457,7 @@ export function createSubcommand<
457
457
  banner?: true;
458
458
  aliases?: string[];
459
459
  toplevel?: boolean;
460
+ skipInternalLogging?: boolean;
460
461
  requires?: R;
461
462
  optional?: O;
462
463
  examples?: CommandExample[];
@@ -503,6 +504,7 @@ export function createCommand<
503
504
  executable?: boolean;
504
505
  skipUpgradeCheck?: boolean;
505
506
  passThroughArgs?: boolean;
507
+ skipInternalLogging?: boolean;
506
508
  requires?: R;
507
509
  optional?: O;
508
510
  examples?: CommandExample[];
@@ -545,6 +547,7 @@ type CommandDefBase =
545
547
  skipUpgradeCheck?: boolean;
546
548
  passThroughArgs?: boolean;
547
549
  skipSkill?: boolean;
550
+ skipInternalLogging?: boolean;
548
551
  examples?: CommandExample[];
549
552
  idempotent?: boolean;
550
553
  prerequisites?: string[];
@@ -564,6 +567,7 @@ type CommandDefBase =
564
567
  skipUpgradeCheck?: boolean;
565
568
  passThroughArgs?: boolean;
566
569
  skipSkill?: boolean;
570
+ skipInternalLogging?: boolean;
567
571
  examples?: CommandExample[];
568
572
  idempotent?: boolean;
569
573
  prerequisites?: string[];
@@ -583,6 +587,7 @@ type SubcommandDefBase =
583
587
  toplevel?: boolean;
584
588
  banner?: boolean;
585
589
  skipSkill?: boolean;
590
+ skipInternalLogging?: boolean;
586
591
  examples?: CommandExample[];
587
592
  idempotent?: boolean;
588
593
  prerequisites?: string[];
@@ -600,6 +605,7 @@ type SubcommandDefBase =
600
605
  toplevel?: boolean;
601
606
  banner?: boolean;
602
607
  skipSkill?: boolean;
608
+ skipInternalLogging?: boolean;
603
609
  examples?: CommandExample[];
604
610
  idempotent?: boolean;
605
611
  prerequisites?: string[];
@@ -2,10 +2,26 @@
2
2
  * Detects how the CLI was installed and is being run
3
3
  */
4
4
 
5
+ import fs from 'node:fs';
5
6
  import os from 'node:os';
6
7
 
7
8
  export type InstallationType = 'global' | 'local' | 'source';
8
9
 
10
+ /**
11
+ * Resolve a path to its real path (following symlinks) and normalize to POSIX separators.
12
+ * Returns the original path if resolution fails.
13
+ */
14
+ function resolveRealPath(path: string): string {
15
+ if (!path) return '';
16
+ try {
17
+ // fs.realpathSync resolves symlinks (e.g., /tmp -> /private/tmp on macOS)
18
+ return fs.realpathSync(path).replace(/\\/g, '/');
19
+ } catch {
20
+ // If the path doesn't exist or can't be resolved, return normalized original
21
+ return path.replace(/\\/g, '/');
22
+ }
23
+ }
24
+
9
25
  /**
10
26
  * Determines the installation type based on how the CLI is being executed
11
27
  *
@@ -14,34 +30,57 @@ export type InstallationType = 'global' | 'local' | 'source';
14
30
  * @returns 'source' - Running from source code (development)
15
31
  */
16
32
  export function getInstallationType(): InstallationType {
17
- // Normalize paths to POSIX separators for cross-platform compatibility
33
+ // Bun.main already returns the resolved real path, just normalize separators
18
34
  const mainPath = Bun.main.replace(/\\/g, '/');
19
- // Bun.argv[1] contains the original invocation path (before symlink resolution)
20
- const invokedPath = (Bun.argv[1] ?? '').replace(/\\/g, '/');
21
35
 
22
- // Get bun's global bin directory from BUN_INSTALL or default to ~/.bun/bin
23
- // Use os.homedir() as primary fallback to avoid "undefined/.bun" paths
24
- const home = os.homedir() ?? process.env.HOME ?? process.env.USERPROFILE ?? '';
25
- const bunInstall = (process.env.BUN_INSTALL ?? (home ? `${home}/.bun` : '')).replace(/\\/g, '/');
26
- const globalBinDir = bunInstall ? `${bunInstall}/bin/` : '';
36
+ // Get home directory reliably and resolve symlinks
37
+ // On macOS, os.homedir() returns /Users/xxx which is already real
38
+ const home = resolveRealPath(os.homedir() ?? process.env.HOME ?? process.env.USERPROFILE ?? '');
27
39
 
28
- // Global install: invoked from bun's global bin directory (e.g., ~/.bun/bin/agentuity)
29
- // This handles symlinks created by `bun add -g @agentuity/cli`
30
- if (globalBinDir && invokedPath.startsWith(globalBinDir)) {
31
- return 'global';
40
+ // Get bun install directory from BUN_INSTALL or default to ~/.bun
41
+ // Resolve symlinks to handle cases like BUN_INSTALL=/tmp/... on macOS where /tmp -> /private/tmp
42
+ const bunInstallRaw = process.env.BUN_INSTALL ?? (home ? `${home}/.bun` : '');
43
+ const bunInstall = resolveRealPath(bunInstallRaw);
44
+
45
+ // GLOBAL DETECTION: Check if running from bun's global install location
46
+ // When installed via `bun add -g`, the CLI lives at ~/.bun/node_modules/@agentuity/cli/
47
+ // or ~/.bun/install/global/node_modules/@agentuity/cli/
48
+ if (bunInstall) {
49
+ // Check for ~/.bun/node_modules/@agentuity/cli/ (common bun global layout)
50
+ if (mainPath.startsWith(`${bunInstall}/node_modules/@agentuity/cli/`)) {
51
+ return 'global';
52
+ }
53
+ // Check for ~/.bun/install/global/node_modules/@agentuity/cli/ (alternative layout)
54
+ if (mainPath.startsWith(`${bunInstall}/install/global/`)) {
55
+ return 'global';
56
+ }
57
+ }
58
+
59
+ // GLOBAL DETECTION: Check for legacy ~/.agentuity/ installation
60
+ // The install.sh script may install to ~/.agentuity/node_modules/@agentuity/cli/
61
+ // or create a shim at ~/.agentuity/bin/agentuity
62
+ if (home) {
63
+ const agentuityDir = resolveRealPath(`${home}/.agentuity`);
64
+ if (mainPath.startsWith(`${agentuityDir}/`)) {
65
+ return 'global';
66
+ }
32
67
  }
33
68
 
34
- // Also check the resolved path for explicit global install locations
35
- if (mainPath.includes('/.bun/install/global/')) {
69
+ // GLOBAL DETECTION: Fallback check for any path containing /.bun/ before node_modules
70
+ // This catches edge cases where BUN_INSTALL might not match the actual path
71
+ if (mainPath.includes('/.bun/') && mainPath.includes('/node_modules/@agentuity/cli/')) {
36
72
  return 'global';
37
73
  }
38
74
 
39
- // Local project install: ./node_modules/@agentuity/cli/...
75
+ // LOCAL DETECTION: Running from a project's node_modules
76
+ // This is when someone runs `bunx agentuity` or has it as a project dependency
77
+ // At this point, we've ruled out global installs, so any node_modules path is local
40
78
  if (mainPath.includes('/node_modules/@agentuity/cli/')) {
41
79
  return 'local';
42
80
  }
43
81
 
44
- // Source/development: packages/cli/bin/cli.ts or similar
82
+ // SOURCE DETECTION: Running from source code (development)
83
+ // This is when running directly from the monorepo: packages/cli/bin/cli.ts
45
84
  return 'source';
46
85
  }
47
86
 
@@ -1,3 +0,0 @@
1
- export declare const generateSubcommand: import("../../..").SubcommandDefinition;
2
- export default generateSubcommand;
3
- //# sourceMappingURL=generate.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../../../src/cmd/ai/skills/generate.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,kBAAkB,yCA0D7B,CAAC;AAEH,eAAe,kBAAkB,CAAC"}
@@ -1,65 +0,0 @@
1
- import { z } from 'zod';
2
- import { createSubcommand } from '../../../types';
3
- import { getCommand } from '../../../command-prefix';
4
- import { ErrorCode } from '../../../errors';
5
- import * as tui from '../../../tui';
6
- import * as path from 'node:path';
7
- import { generateSkills, collectSkillsForPreview } from './generator';
8
- const OptionsSchema = z.object({
9
- output: z.string().describe('Output directory for generated skills'),
10
- includeHidden: z.boolean().default(false).describe('Include hidden commands'),
11
- });
12
- export const generateSubcommand = createSubcommand({
13
- name: 'generate',
14
- description: 'Generate Agent Skills from CLI schema',
15
- tags: ['fast'],
16
- idempotent: true,
17
- examples: [
18
- {
19
- command: getCommand('ai skills generate --output ./skills'),
20
- description: 'Generate skills to a directory',
21
- },
22
- {
23
- command: getCommand('--dry-run ai skills generate --output ./skills'),
24
- description: 'Preview without writing files',
25
- },
26
- {
27
- command: getCommand('ai skills generate --output ./skills --include-hidden'),
28
- description: 'Include hidden commands',
29
- },
30
- ],
31
- schema: {
32
- options: OptionsSchema,
33
- },
34
- async handler(ctx) {
35
- const { logger, opts, options } = ctx;
36
- const { output, includeHidden } = opts;
37
- const dryRun = options.dryRun === true;
38
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
- const schema = global.__CLI_SCHEMA__;
40
- if (!schema) {
41
- return logger.fatal('Schema not available. This is a CLI bug.', ErrorCode.INTERNAL_ERROR);
42
- }
43
- const baseDir = path.join(output, 'skills', 'agentuity', 'cli');
44
- if (dryRun) {
45
- const skills = collectSkillsForPreview(schema, output, includeHidden);
46
- if (skills.length === 0) {
47
- logger.warn('No skills to generate');
48
- return;
49
- }
50
- tui.info(`Would generate ${skills.length} skills:`);
51
- for (const skillPath of skills) {
52
- console.log(tui.muted(` ${skillPath}`));
53
- }
54
- return;
55
- }
56
- const created = await generateSkills(schema, output, includeHidden);
57
- if (created === 0) {
58
- logger.warn('No skills to generate');
59
- return;
60
- }
61
- tui.success(`Generated ${created} skills to ${baseDir}`);
62
- },
63
- });
64
- export default generateSubcommand;
65
- //# sourceMappingURL=generate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../../../src/cmd/ai/skills/generate.ts"],"names":[],"mappings":"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"}
@@ -1,4 +0,0 @@
1
- import type { CLISchema } from '../../../schema-generator';
2
- export declare function collectSkillsForPreview(schema: CLISchema, outputDir: string, includeHidden: boolean): string[];
3
- export declare function generateSkills(schema: CLISchema, outputDir: string, includeHidden: boolean): Promise<number>;
4
- //# sourceMappingURL=generator.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../../src/cmd/ai/skills/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,SAAS,EAIT,MAAM,2BAA2B,CAAC;AAienC,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,OAAO,GACpB,MAAM,EAAE,CAGV;AAED,wBAAsB,cAAc,CACnC,MAAM,EAAE,SAAS,EACjB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,OAAO,GACpB,OAAO,CAAC,MAAM,CAAC,CA2BjB"}