byterover-cli 1.0.3 → 1.0.4

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 (127) hide show
  1. package/README.md +62 -10
  2. package/dist/commands/curate.js +2 -2
  3. package/dist/commands/main.js +2 -2
  4. package/dist/commands/query.js +2 -2
  5. package/dist/commands/status.js +2 -2
  6. package/dist/config/context-tree-domains.d.ts +14 -2
  7. package/dist/config/context-tree-domains.js +22 -27
  8. package/dist/constants.d.ts +1 -0
  9. package/dist/constants.js +3 -0
  10. package/dist/core/domain/cipher/file-system/types.d.ts +2 -0
  11. package/dist/core/domain/entities/auth-token.js +6 -3
  12. package/dist/core/domain/entities/event.d.ts +1 -1
  13. package/dist/core/domain/entities/event.js +2 -1
  14. package/dist/core/domain/knowledge/relation-parser.d.ts +16 -1
  15. package/dist/core/domain/knowledge/relation-parser.js +19 -2
  16. package/dist/core/domain/transport/schemas.d.ts +17 -1
  17. package/dist/core/domain/transport/schemas.js +9 -1
  18. package/dist/core/interfaces/cipher/i-blob-storage.d.ts +6 -0
  19. package/dist/core/interfaces/cipher/index.d.ts +0 -1
  20. package/dist/core/interfaces/executor/i-curate-executor.d.ts +2 -0
  21. package/dist/infra/cipher/agent/cipher-agent.js +4 -0
  22. package/dist/infra/cipher/file-system/file-system-service.d.ts +4 -0
  23. package/dist/infra/cipher/file-system/file-system-service.js +5 -0
  24. package/dist/infra/cipher/system-prompt/contributors/context-tree-structure-contributor.js +4 -2
  25. package/dist/infra/cipher/tools/implementations/create-knowledge-topic-tool.js +24 -17
  26. package/dist/infra/cipher/tools/implementations/curate-tool.js +28 -33
  27. package/dist/infra/cipher/tools/implementations/read-file-tool.js +3 -12
  28. package/dist/infra/cipher/tools/implementations/spec-analyze-tool.js +18 -15
  29. package/dist/infra/cipher/tools/implementations/task-tool.js +53 -7
  30. package/dist/infra/context-tree/file-context-tree-service.js +4 -15
  31. package/dist/infra/core/executors/curate-executor.d.ts +2 -7
  32. package/dist/infra/core/executors/curate-executor.js +18 -53
  33. package/dist/infra/core/executors/query-executor.d.ts +1 -7
  34. package/dist/infra/core/executors/query-executor.js +10 -35
  35. package/dist/infra/core/task-processor.d.ts +2 -0
  36. package/dist/infra/core/task-processor.js +1 -0
  37. package/dist/infra/http/authenticated-http-client.js +5 -0
  38. package/dist/infra/process/agent-worker.js +113 -6
  39. package/dist/infra/process/constants.d.ts +1 -0
  40. package/dist/infra/process/constants.js +1 -0
  41. package/dist/infra/process/task-queue-manager.js +2 -1
  42. package/dist/infra/process/transport-handlers.js +4 -0
  43. package/dist/infra/process/transport-worker.js +89 -1
  44. package/dist/infra/repl/commands/curate-command.js +2 -2
  45. package/dist/infra/repl/commands/gen-rules-command.js +2 -2
  46. package/dist/infra/repl/commands/init-command.js +2 -2
  47. package/dist/infra/repl/commands/login-command.js +2 -2
  48. package/dist/infra/repl/commands/logout-command.js +2 -2
  49. package/dist/infra/repl/commands/pull-command.js +2 -2
  50. package/dist/infra/repl/commands/push-command.js +2 -2
  51. package/dist/infra/repl/commands/query-command.js +2 -2
  52. package/dist/infra/repl/commands/space/list-command.js +2 -2
  53. package/dist/infra/repl/commands/space/switch-command.js +2 -2
  54. package/dist/infra/repl/commands/status-command.js +2 -2
  55. package/dist/infra/repl/repl-startup.js +0 -2
  56. package/dist/infra/storage/file-token-store.d.ts +31 -0
  57. package/dist/infra/storage/file-token-store.js +98 -0
  58. package/dist/infra/storage/keychain-token-store.d.ts +4 -1
  59. package/dist/infra/storage/keychain-token-store.js +6 -4
  60. package/dist/infra/storage/token-store.d.ts +10 -0
  61. package/dist/infra/storage/token-store.js +14 -0
  62. package/dist/infra/usecase/curate-use-case.js +1 -1
  63. package/dist/infra/user/http-user-service.js +6 -11
  64. package/dist/resources/prompts/curate.yml +14 -5
  65. package/dist/resources/prompts/plan.yml +6 -0
  66. package/dist/tui/app.js +1 -1
  67. package/dist/tui/components/execution/log-item.js +2 -5
  68. package/dist/tui/components/header.d.ts +1 -1
  69. package/dist/tui/components/header.js +25 -4
  70. package/dist/tui/components/index.d.ts +5 -1
  71. package/dist/tui/components/index.js +3 -1
  72. package/dist/tui/components/init.d.ts +33 -0
  73. package/dist/tui/components/init.js +253 -0
  74. package/dist/tui/components/onboarding/index.d.ts +1 -0
  75. package/dist/tui/components/onboarding/index.js +1 -0
  76. package/dist/tui/components/onboarding/onboarding-flow.d.ts +2 -0
  77. package/dist/tui/components/onboarding/onboarding-flow.js +8 -229
  78. package/dist/tui/components/onboarding/onboarding-step.js +1 -1
  79. package/dist/tui/components/onboarding/welcome-box.d.ts +14 -0
  80. package/dist/tui/components/onboarding/welcome-box.js +23 -0
  81. package/dist/tui/components/status-badge.d.ts +22 -0
  82. package/dist/tui/components/status-badge.js +32 -0
  83. package/dist/tui/contexts/auth-context.js +2 -1
  84. package/dist/tui/contexts/index.d.ts +1 -0
  85. package/dist/tui/contexts/index.js +1 -0
  86. package/dist/tui/contexts/onboarding-context.d.ts +14 -0
  87. package/dist/tui/contexts/onboarding-context.js +17 -22
  88. package/dist/tui/contexts/status-context.d.ts +33 -0
  89. package/dist/tui/contexts/status-context.js +159 -0
  90. package/dist/tui/hooks/use-auth-polling.d.ts +4 -1
  91. package/dist/tui/hooks/use-auth-polling.js +21 -7
  92. package/dist/tui/hooks/use-tab-navigation.js +0 -2
  93. package/dist/tui/providers/app-providers.js +2 -2
  94. package/dist/tui/types/index.d.ts +2 -0
  95. package/dist/tui/types/index.js +2 -0
  96. package/dist/tui/types/status.d.ts +46 -0
  97. package/dist/tui/types/status.js +13 -0
  98. package/dist/tui/utils/index.d.ts +6 -0
  99. package/dist/tui/utils/index.js +6 -0
  100. package/dist/tui/utils/time.d.ts +10 -0
  101. package/dist/tui/utils/time.js +15 -0
  102. package/dist/tui/views/command-view.js +0 -2
  103. package/dist/tui/views/index.d.ts +1 -0
  104. package/dist/tui/views/index.js +1 -0
  105. package/dist/tui/views/init-view.d.ts +15 -0
  106. package/dist/tui/views/init-view.js +29 -0
  107. package/dist/tui/views/logs-view.js +22 -8
  108. package/dist/utils/environment-detector.d.ts +5 -0
  109. package/dist/utils/environment-detector.js +31 -0
  110. package/dist/utils/global-data-path.d.ts +11 -0
  111. package/dist/utils/global-data-path.js +32 -0
  112. package/oclif.manifest.json +1 -1
  113. package/package.json +1 -1
  114. package/dist/core/interfaces/cipher/i-agent-storage.d.ts +0 -152
  115. package/dist/core/interfaces/cipher/i-agent-storage.js +0 -1
  116. package/dist/infra/cipher/consumer/consumer-lock.d.ts +0 -20
  117. package/dist/infra/cipher/consumer/consumer-lock.js +0 -41
  118. package/dist/infra/cipher/consumer/consumer-service.d.ts +0 -99
  119. package/dist/infra/cipher/consumer/consumer-service.js +0 -166
  120. package/dist/infra/cipher/consumer/execution-consumer.d.ts +0 -126
  121. package/dist/infra/cipher/consumer/execution-consumer.js +0 -561
  122. package/dist/infra/cipher/consumer/index.d.ts +0 -33
  123. package/dist/infra/cipher/consumer/index.js +0 -34
  124. package/dist/infra/cipher/consumer/queue-polling-service.d.ts +0 -120
  125. package/dist/infra/cipher/consumer/queue-polling-service.js +0 -249
  126. package/dist/infra/cipher/storage/agent-storage.d.ts +0 -246
  127. package/dist/infra/cipher/storage/agent-storage.js +0 -956
@@ -0,0 +1,31 @@
1
+ import type { ITokenStore } from '../../core/interfaces/i-token-store.js';
2
+ import { AuthToken } from '../../core/domain/entities/auth-token.js';
3
+ /**
4
+ * Dependencies for FileTokenStore.
5
+ * Allows injection for testing.
6
+ */
7
+ export interface FileTokenStoreDeps {
8
+ readonly getCredentialsPath: () => string;
9
+ readonly getDataDir: () => string;
10
+ readonly getKeyPath: () => string;
11
+ }
12
+ /**
13
+ * File-based token store for WSL2 environments.
14
+ *
15
+ * Note: This class should not be used directly. Use createTokenStore() instead,
16
+ * which handles platform detection and selects the appropriate backend.
17
+ *
18
+ * Security:
19
+ * - Random 32-byte key stored in ~/.local/share/brv/.token-key (rotated on each save)
20
+ * - AES-256-GCM authenticated encryption for token data
21
+ * - Both files have 0600 permissions (owner read/write only)
22
+ */
23
+ export declare class FileTokenStore implements ITokenStore {
24
+ private readonly deps;
25
+ constructor(deps?: FileTokenStoreDeps);
26
+ clear(): Promise<void>;
27
+ load(): Promise<AuthToken | undefined>;
28
+ save(token: AuthToken): Promise<void>;
29
+ private decrypt;
30
+ private encrypt;
31
+ }
@@ -0,0 +1,98 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
2
+ import { existsSync } from 'node:fs';
3
+ import { chmod, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';
4
+ import { AuthToken } from '../../core/domain/entities/auth-token.js';
5
+ import { getGlobalDataDir } from '../../utils/global-data-path.js';
6
+ const KEY_FILE = '.token-key';
7
+ const CREDENTIALS_FILE = 'credentials';
8
+ const ALGORITHM = 'aes-256-gcm';
9
+ const KEY_LENGTH = 32;
10
+ const IV_LENGTH = 16;
11
+ const defaultDeps = {
12
+ getCredentialsPath: () => `${getGlobalDataDir()}/${CREDENTIALS_FILE}`,
13
+ getDataDir: getGlobalDataDir,
14
+ getKeyPath: () => `${getGlobalDataDir()}/${KEY_FILE}`,
15
+ };
16
+ /**
17
+ * File-based token store for WSL2 environments.
18
+ *
19
+ * Note: This class should not be used directly. Use createTokenStore() instead,
20
+ * which handles platform detection and selects the appropriate backend.
21
+ *
22
+ * Security:
23
+ * - Random 32-byte key stored in ~/.local/share/brv/.token-key (rotated on each save)
24
+ * - AES-256-GCM authenticated encryption for token data
25
+ * - Both files have 0600 permissions (owner read/write only)
26
+ */
27
+ export class FileTokenStore {
28
+ deps;
29
+ constructor(deps = defaultDeps) {
30
+ this.deps = deps;
31
+ }
32
+ async clear() {
33
+ try {
34
+ const credentialsPath = this.deps.getCredentialsPath();
35
+ const keyPath = this.deps.getKeyPath();
36
+ if (existsSync(credentialsPath)) {
37
+ await unlink(credentialsPath);
38
+ }
39
+ if (existsSync(keyPath)) {
40
+ await unlink(keyPath);
41
+ }
42
+ }
43
+ catch {
44
+ /** Ignore errors */
45
+ }
46
+ }
47
+ async load() {
48
+ try {
49
+ const keyPath = this.deps.getKeyPath();
50
+ const credentialsPath = this.deps.getCredentialsPath();
51
+ if (!existsSync(keyPath) || !existsSync(credentialsPath)) {
52
+ return undefined;
53
+ }
54
+ const key = await readFile(keyPath);
55
+ const encrypted = await readFile(credentialsPath, 'utf8');
56
+ const decrypted = this.decrypt(encrypted.trim(), key);
57
+ const json = JSON.parse(decrypted);
58
+ return AuthToken.fromJson(json);
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ }
64
+ async save(token) {
65
+ const dataDir = this.deps.getDataDir();
66
+ const keyPath = this.deps.getKeyPath();
67
+ const credentialsPath = this.deps.getCredentialsPath();
68
+ await mkdir(dataDir, { recursive: true });
69
+ // Always generate new key for rotation (security best practice)
70
+ const key = randomBytes(KEY_LENGTH);
71
+ await writeFile(keyPath, key);
72
+ await chmod(keyPath, 0o600);
73
+ const plaintext = JSON.stringify(token.toJson());
74
+ const encrypted = this.encrypt(plaintext, key);
75
+ await writeFile(credentialsPath, encrypted, 'utf8');
76
+ await chmod(credentialsPath, 0o600);
77
+ }
78
+ decrypt(ciphertext, key) {
79
+ const parts = ciphertext.split(':');
80
+ if (parts.length !== 3) {
81
+ throw new Error('Invalid format');
82
+ }
83
+ const iv = Buffer.from(parts[0], 'base64');
84
+ const authTag = Buffer.from(parts[1], 'base64');
85
+ const encrypted = Buffer.from(parts[2], 'base64');
86
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
87
+ decipher.setAuthTag(authTag);
88
+ return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
89
+ }
90
+ encrypt(plaintext, key) {
91
+ const iv = randomBytes(IV_LENGTH);
92
+ const cipher = createCipheriv(ALGORITHM, key, iv);
93
+ const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
94
+ const authTag = cipher.getAuthTag();
95
+ /** Format: iv:authTag:data (all base64) */
96
+ return `${iv.toString('base64')}:${authTag.toString('base64')}:${encrypted.toString('base64')}`;
97
+ }
98
+ }
@@ -1,7 +1,10 @@
1
1
  import type { ITokenStore } from '../../core/interfaces/i-token-store.js';
2
2
  import { AuthToken } from '../../core/domain/entities/auth-token.js';
3
3
  /**
4
- * Token store implementation using the system keychain via the keytar library.
4
+ * Token store using system keychain via keytar.
5
+ *
6
+ * Note: This class should not be used directly. Use createTokenStore() instead,
7
+ * which handles platform detection and selects the appropriate backend.
5
8
  */
6
9
  export declare class KeychainTokenStore implements ITokenStore {
7
10
  clear(): Promise<void>;
@@ -3,7 +3,10 @@ import { AuthToken } from '../../core/domain/entities/auth-token.js';
3
3
  const SERVICE_NAME = 'byterover-cli';
4
4
  const ACCOUNT_NAME = 'auth-token';
5
5
  /**
6
- * Token store implementation using the system keychain via the keytar library.
6
+ * Token store using system keychain via keytar.
7
+ *
8
+ * Note: This class should not be used directly. Use createTokenStore() instead,
9
+ * which handles platform detection and selects the appropriate backend.
7
10
  */
8
11
  export class KeychainTokenStore {
9
12
  async clear() {
@@ -11,7 +14,7 @@ export class KeychainTokenStore {
11
14
  await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
12
15
  }
13
16
  catch {
14
- // Ignore errors - token might not exist or keychain might be unavailable
17
+ // Ignore errors (token may not exist, permissions, etc.)
15
18
  }
16
19
  }
17
20
  async load() {
@@ -24,13 +27,12 @@ export class KeychainTokenStore {
24
27
  return AuthToken.fromJson(deserialized);
25
28
  }
26
29
  catch {
27
- // Return undefined on any error (missing token, invalid JSON, keychain errors)
28
30
  return undefined;
29
31
  }
30
32
  }
31
33
  async save(token) {
32
34
  try {
33
- const data = JSON.stringify(token);
35
+ const data = JSON.stringify(token.toJson());
34
36
  await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, data);
35
37
  }
36
38
  catch (error) {
@@ -0,0 +1,10 @@
1
+ import type { ITokenStore } from '../../core/interfaces/i-token-store.js';
2
+ /**
3
+ * Creates the appropriate token store for the current platform.
4
+ *
5
+ * - WSL: FileTokenStore (encrypted file-based, keychain not available)
6
+ * - macOS/Linux/Windows: KeychainTokenStore (system keychain via keytar)
7
+ *
8
+ * @param isWslFn - Optional function to detect WSL (for testing)
9
+ */
10
+ export declare function createTokenStore(isWslFn?: () => boolean): ITokenStore;
@@ -0,0 +1,14 @@
1
+ import { isWsl } from '../../utils/environment-detector.js';
2
+ import { FileTokenStore } from './file-token-store.js';
3
+ import { KeychainTokenStore } from './keychain-token-store.js';
4
+ /**
5
+ * Creates the appropriate token store for the current platform.
6
+ *
7
+ * - WSL: FileTokenStore (encrypted file-based, keychain not available)
8
+ * - macOS/Linux/Windows: KeychainTokenStore (system keychain via keytar)
9
+ *
10
+ * @param isWslFn - Optional function to detect WSL (for testing)
11
+ */
12
+ export function createTokenStore(isWslFn = isWsl) {
13
+ return isWslFn() ? new FileTokenStore() : new KeychainTokenStore();
14
+ }
@@ -32,8 +32,8 @@ export class CurateUseCase {
32
32
  }
33
33
  // Generate taskId in UseCase (Application layer owns task creation)
34
34
  const taskId = randomUUID();
35
- // Send task:create - Transport routes to Agent, UseCase handles logic
36
35
  await client.request('task:create', {
36
+ clientCwd: process.cwd(),
37
37
  content: context,
38
38
  ...(files?.length ? { files } : {}),
39
39
  taskId,
@@ -1,5 +1,4 @@
1
1
  import { User } from '../../core/domain/entities/user.js';
2
- import { getErrorMessage } from '../../utils/error-helpers.js';
3
2
  import { AuthenticatedHttpClient } from '../http/authenticated-http-client.js';
4
3
  export class HttpUserService {
5
4
  config;
@@ -10,16 +9,12 @@ export class HttpUserService {
10
9
  };
11
10
  }
12
11
  async getCurrentUser(accessToken, sessionKey) {
13
- try {
14
- const httpClient = new AuthenticatedHttpClient(accessToken, sessionKey);
15
- const response = await httpClient.get(`${this.config.apiBaseUrl}/user/me`, {
16
- timeout: this.config.timeout,
17
- });
18
- return this.mapToUser(response.data);
19
- }
20
- catch (error) {
21
- throw new Error(`Failed to fetch user information: ${getErrorMessage(error)}`);
22
- }
12
+ // IMPORTANT: Do not try-catch here - let callers handle errors (e.g., distinguish 401 from network errors)
13
+ const httpClient = new AuthenticatedHttpClient(accessToken, sessionKey);
14
+ const response = await httpClient.get(`${this.config.apiBaseUrl}/user/me`, {
15
+ timeout: this.config.timeout,
16
+ });
17
+ return this.mapToUser(response.data);
23
18
  }
24
19
  mapToUser(userData) {
25
20
  return new User(userData.email, userData.id, userData.name);
@@ -7,7 +7,7 @@ prompt: |
7
7
  The information should be sufficient to understand what the user has provided to the coding assistant as the important context so that the coding assistant can use it to complete the task at hand. When organizing the context, keep in mind the hierarchical structure with a maximum of 2 levels: domain → topic → subtopic. If subtopics contain complex information, break them into separate topics rather than creating deeper nesting.
8
8
 
9
9
  The context tree captures:
10
- - the domains of the context
10
+ - the domains of the context (dynamically created based on content)
11
11
  - the topics of the context
12
12
  - the subtopics of the context (maximum one level under topics)
13
13
  - the code snippets of the context
@@ -29,16 +29,25 @@ prompt: |
29
29
  - The current domain and text segments from the user's input data
30
30
  - The existing context in the context tree
31
31
  Use the `curate` tool to create new knowledge topics or update existing ones. Ensure that:
32
- - **Domain selection**: Always prefer predefined domains (code_style, design, structure, compliance, testing, bug_fixes). Only create custom domains if content clearly doesn't fit any predefined domain. Maximum 3 custom domains allowed.
32
+ - **Dynamic Domain Creation**: Create domains that are semantically meaningful for the content being curated
33
33
  - The context is well-structured and MUST meet **Context quality requirements**
34
34
  - There is no duplication with existing context
35
35
  - The information is clear and easy to understand
36
36
 
37
- 4. **Tool Execution Efficiency:**
37
+ 4. **Domain Naming Guidelines** (CRITICAL for quality):
38
+ - Choose domain names that accurately describe the category of knowledge
39
+ - Use snake_case format with 1-3 words (e.g., `authentication`, `api_design`, `error_handling`)
40
+ - **Good examples**: `authentication`, `data_models`, `ui_components`, `testing_patterns`, `api_endpoints`, `security`, `configuration`, `logging`, `caching`, `performance`
41
+ - **Avoid**: Generic names like `misc`, `other`, `general`, `stuff`
42
+ - **Avoid**: Overly specific names that only fit one topic
43
+ - **Before creating a new domain**: Check if existing domains could accommodate the content
44
+ - **Consolidate related concepts**: Group similar topics under the same domain for better organization
45
+
46
+ 5. **Tool Execution Efficiency:**
38
47
  - When multiple tools don't depend on each other's results, execute them in parallel with `batch`
39
48
  - Example: `glob_files`, `list_directory`, `grep_content` and `read_file` operations for different files can run together
40
49
 
41
- 5. **Context quality requirements**: Each context in the `contexts` array must:
50
+ 6. **Context quality requirements**: Each context in the `contexts` array must:
42
51
  - Important: Minimum 2-4 sentences per context. Otherwise, DO NOT add that context.
43
52
  - A developer should understand the concept without reading source code
44
53
  - Include names of functions, classes, patterns, or key concepts
@@ -57,7 +66,7 @@ prompt: |
57
66
  code, message, context object, and optional cause. Use `ErrorHandler.wrap(fn)` for consistent error boundaries across
58
67
  async operations."
59
68
 
60
- 6. **Response Format**:
69
+ 7. **Response Format**:
61
70
  - Your final response must be a brief summary (1-2 sentences) describing what knowledge was curated
62
71
  - Do NOT include any file paths, directory paths, or specific location details in your response
63
72
  - The system will automatically display created/updated file paths in a separate section
@@ -34,6 +34,12 @@ prompt: |
34
34
  - For **query commands**: Search ONLY within the structure shown
35
35
  - For **curate commands**: Check existing content to avoid duplicates
36
36
 
37
+ **Dynamic Domain System**: Domains are created dynamically based on content semantics, not from a fixed predefined list. When curating:
38
+ - The curate agent will create semantically meaningful domain names based on the content
39
+ - Domain names should be descriptive and follow snake_case format (e.g., `authentication`, `api_design`, `error_handling`)
40
+ - Before creating new domains, check if existing domains could accommodate the content
41
+ - Good domain examples: `authentication`, `data_models`, `ui_components`, `testing_patterns`, `api_endpoints`
42
+
37
43
  ## Workflow for Context Search/Query Requests
38
44
 
39
45
  When a user wants to **search or retrieve information from the context tree** (query command), follow these steps:
package/dist/tui/app.js CHANGED
@@ -18,5 +18,5 @@ export const App = () => {
18
18
  const { activeTab, tabs } = useTabNavigation();
19
19
  const { stats: taskStats } = useTasks();
20
20
  const contentHeight = Math.max(1, terminalHeight - header - tab - footer);
21
- return (_jsxs(Box, { flexDirection: "column", height: terminalHeight, paddingBottom: appBottomPadding, width: terminalWidth, children: [_jsx(Box, { flexShrink: 0, children: _jsx(Header, { compact: isAuthorized, showTransportStats: isAuthorized, taskStats: taskStats }) }), isAuthorized ? (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(TabBar, { activeTab: activeTab, tabs: tabs }) }), _jsxs(Box, { flexGrow: 1, paddingX: 1, children: [_jsx(Box, { display: activeTab === 'activity' ? 'flex' : 'none', height: "100%", width: "100%", children: _jsx(LogsView, { availableHeight: contentHeight }) }), _jsx(Box, { display: activeTab === 'console' ? 'flex' : 'none', height: "100%", width: "100%", children: _jsx(CommandView, { availableHeight: contentHeight }) })] }), _jsx(Box, { flexShrink: 0, children: _jsx(Footer, {}) })] })) : (_jsx(Box, { flexGrow: 1, paddingX: 1, children: _jsx(LoginView, {}) }))] }));
21
+ return (_jsxs(Box, { flexDirection: "column", height: terminalHeight, paddingBottom: appBottomPadding, width: terminalWidth, children: [_jsx(Box, { flexShrink: 0, children: _jsx(Header, { compact: isAuthorized, showStatusLine: isAuthorized, taskStats: taskStats }) }), isAuthorized ? (_jsxs(_Fragment, { children: [_jsx(Box, { flexShrink: 0, children: _jsx(TabBar, { activeTab: activeTab, tabs: tabs }) }), _jsxs(Box, { flexGrow: 1, paddingX: 1, children: [_jsx(Box, { display: activeTab === 'activity' ? 'flex' : 'none', height: "100%", width: "100%", children: _jsx(LogsView, { availableHeight: contentHeight }) }), _jsx(Box, { display: activeTab === 'console' ? 'flex' : 'none', height: "100%", width: "100%", children: _jsx(CommandView, { availableHeight: contentHeight }) })] }), _jsx(Box, { flexShrink: 0, children: _jsx(Footer, {}) })] })) : (_jsx(Box, { flexGrow: 1, paddingX: 1, children: _jsx(LoginView, {}) }))] }));
22
22
  };
@@ -6,6 +6,7 @@ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
6
6
  */
7
7
  import { Box, Spacer, Text } from 'ink';
8
8
  import { useTheme } from '../../hooks/index.js';
9
+ import { formatTime } from '../../utils/index.js';
9
10
  import { ExecutionChanges } from './execution-changes.js';
10
11
  import { ExecutionContent } from './execution-content.js';
11
12
  import { ExecutionInput } from './execution-input.js';
@@ -13,10 +14,6 @@ import { ExecutionProgress } from './execution-progress.js';
13
14
  import { ExecutionStatus } from './execution-status.js';
14
15
  export const LogItem = ({ heights, log }) => {
15
16
  const { theme: { colors }, } = useTheme();
16
- // Format timestamp as local time HH:MM:SS
17
- const hours = log.timestamp.getHours().toString().padStart(2, '0');
18
- const minutes = log.timestamp.getMinutes().toString().padStart(2, '0');
19
- const seconds = log.timestamp.getSeconds().toString().padStart(2, '0');
20
- const displayTime = `${hours}:${minutes}:${seconds}`;
17
+ const displayTime = formatTime(log.timestamp);
21
18
  return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, width: "100%", children: [_jsxs(Box, { children: [_jsxs(Text, { color: log.type === 'curate' ? colors.curateCommand : colors.queryCommand, children: ["[", log.type, "] "] }), _jsxs(Text, { color: colors.dimText, children: ["@", log.source ?? 'system'] }), _jsx(Spacer, {}), _jsxs(Text, { color: colors.dimText, children: ["[", displayTime, "]"] })] }), _jsx(ExecutionInput, { input: log.input }), _jsxs(Box, { flexDirection: "column", children: [log.progress && _jsx(ExecutionProgress, { maxLines: heights.maxProgressItems, progress: log.progress }), _jsx(ExecutionStatus, { status: log.status })] }), (log.status === 'failed' || log.status === 'completed') && (_jsx(ExecutionContent, { bottomMargin: heights.contentBottomMargin, content: log.content ?? '', isError: log.status === 'failed', maxLines: heights.maxContentLines })), log.status === 'completed' && (_jsx(ExecutionChanges, { created: log.changes.created, maxChanges: heights.maxChanges, updated: log.changes.updated }))] }));
22
19
  };
@@ -11,7 +11,7 @@ import { TaskStats } from '../types/ui.js';
11
11
  interface HeaderProps {
12
12
  compact?: boolean;
13
13
  connectedAgent?: string;
14
- showTransportStats?: boolean;
14
+ showStatusLine?: boolean;
15
15
  taskStats?: TaskStats;
16
16
  }
17
17
  export declare const Header: React.FC<HeaderProps>;
@@ -7,14 +7,35 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  * - Connected agent status
8
8
  * - Queue stats (pending/processing)
9
9
  */
10
- import { Box, Spacer, Text } from 'ink';
11
- import { useServices } from '../contexts/index.js';
10
+ import { Box, Text } from 'ink';
11
+ import { useEffect, useState } from 'react';
12
+ import { useServices, useStatus } from '../contexts/index.js';
12
13
  import { useTheme } from '../contexts/theme-context.js';
13
14
  import { useTerminalBreakpoint } from '../hooks/use-terminal-breakpoint.js';
14
15
  import { Logo } from './logo.js';
15
- export const Header = ({ compact, connectedAgent, showTransportStats, taskStats: transportStats }) => {
16
+ import { StatusBadge } from './status-badge.js';
17
+ export const Header = ({ compact, showStatusLine, taskStats: transportStats }) => {
16
18
  const { version } = useServices();
17
19
  const { theme } = useTheme();
18
20
  const { breakpoint } = useTerminalBreakpoint();
19
- return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsx(Logo, { compact: compact, version: version }), _jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Box, { gap: 2, children: [connectedAgent && (_jsxs(Box, { children: [_jsx(Text, { color: "green", children: "\u25CF " }), _jsx(Text, { color: "gray", children: connectedAgent })] })), breakpoint === "compact" && (_jsx(Text, { color: theme.colors.warning, children: "Terminal too small - expand for better experience" }))] }), showTransportStats && (_jsxs(Box, { paddingRight: 1, children: [_jsx(Spacer, {}), _jsx(Text, { children: "~ in queue: " }), _jsx(Text, { color: theme.colors.warning, children: transportStats?.created ?? 0 }), _jsx(Text, { children: " | running: " }), _jsx(Text, { color: theme.colors.primary, children: transportStats?.started ?? 0 })] }))] })] }));
21
+ const { currentEvent } = useStatus();
22
+ const [breakpointWarning, setBreakpointWarning] = useState(null);
23
+ useEffect(() => {
24
+ if (breakpoint === 'compact') {
25
+ setBreakpointWarning({
26
+ message: 'Terminal too small - expand for better experience',
27
+ type: 'warning',
28
+ });
29
+ }
30
+ else {
31
+ setBreakpointWarning(null);
32
+ }
33
+ }, [breakpoint]);
34
+ // Status event takes priority over breakpoint warning
35
+ const displayStatus = currentEvent
36
+ ? { label: currentEvent.label, message: currentEvent.message, type: currentEvent.type }
37
+ : breakpointWarning
38
+ ? { label: breakpointWarning.type, message: breakpointWarning.message, type: breakpointWarning.type }
39
+ : null;
40
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", children: [_jsx(Logo, { compact: compact, version: version }), showStatusLine && (_jsxs(Box, { gap: 2, justifyContent: "space-between", children: [_jsx(StatusBadge, { label: displayStatus?.label ?? 'Idle', message: displayStatus?.message ?? 'Ready', type: displayStatus?.type ?? 'info' }), _jsxs(Box, { flexShrink: 0, paddingRight: 1, children: [_jsx(Text, { children: "~ in queue: " }), _jsx(Text, { color: theme.colors.warning, children: transportStats?.created ?? 0 }), _jsx(Text, { children: " | running: " }), _jsx(Text, { color: theme.colors.primary, children: transportStats?.started ?? 0 })] })] }))] }));
20
41
  };
@@ -6,13 +6,17 @@ export { EnterPrompt } from './enter-prompt.js';
6
6
  export { ExecutionChanges, ExecutionContent, ExecutionInput, ExecutionProgress, ExecutionStatus, LogItem, truncateContent, } from './execution/index.js';
7
7
  export { Footer } from './footer.js';
8
8
  export { Header } from './header.js';
9
+ export { Init } from './init.js';
10
+ export type { InitProps } from './init.js';
9
11
  export { Logo } from './logo.js';
10
12
  export type { LogoVariant } from './logo.js';
11
13
  export { MessageItem } from './message-item.js';
12
- export { CopyablePrompt, OnboardingFlow, OnboardingStep } from './onboarding/index.js';
14
+ export { CopyablePrompt, OnboardingFlow, OnboardingStep, WelcomeBox } from './onboarding/index.js';
13
15
  export type { OnboardingStepType } from './onboarding/index.js';
14
16
  export { OutputLog } from './output-log.js';
15
17
  export { ScrollableList } from './scrollable-list.js';
16
18
  export type { ScrollableListProps } from './scrollable-list.js';
19
+ export { StatusBadge } from './status-badge.js';
20
+ export type { StatusBadgeProps, StatusType } from './status-badge.js';
17
21
  export { Suggestions } from './suggestions.js';
18
22
  export { TabBar } from './tab-bar.js';
@@ -6,10 +6,12 @@ export { EnterPrompt } from './enter-prompt.js';
6
6
  export { ExecutionChanges, ExecutionContent, ExecutionInput, ExecutionProgress, ExecutionStatus, LogItem, truncateContent, } from './execution/index.js';
7
7
  export { Footer } from './footer.js';
8
8
  export { Header } from './header.js';
9
+ export { Init } from './init.js';
9
10
  export { Logo } from './logo.js';
10
11
  export { MessageItem } from './message-item.js';
11
- export { CopyablePrompt, OnboardingFlow, OnboardingStep } from './onboarding/index.js';
12
+ export { CopyablePrompt, OnboardingFlow, OnboardingStep, WelcomeBox } from './onboarding/index.js';
12
13
  export { OutputLog } from './output-log.js';
13
14
  export { ScrollableList } from './scrollable-list.js';
15
+ export { StatusBadge } from './status-badge.js';
14
16
  export { Suggestions } from './suggestions.js';
15
17
  export { TabBar } from './tab-bar.js';
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Init Component
3
+ *
4
+ * Reusable component for running the /init command with streaming output
5
+ * and inline prompts. Used by InitView and OnboardingFlow.
6
+ */
7
+ import React from 'react';
8
+ import type { StreamingMessage } from '../types.js';
9
+ /**
10
+ * Processed streaming message for rendering
11
+ * Includes action state for spinner display
12
+ */
13
+ export interface ProcessedMessage extends StreamingMessage {
14
+ /** For action_start: whether the action is still running (no matching action_stop) */
15
+ isActionRunning?: boolean;
16
+ /** For action_start: the completion message from action_stop */
17
+ stopMessage?: string;
18
+ }
19
+ export interface InitProps {
20
+ /** Whether the component should be interactive (for EnterPrompt activation) */
21
+ active?: boolean;
22
+ /** Auto-start init without waiting for Enter key in idle state */
23
+ autoStart?: boolean;
24
+ /** Custom idle state message (optional) */
25
+ idleMessage?: string;
26
+ /** Maximum lines available for streaming output */
27
+ maxOutputLines: number;
28
+ /** Optional callback when init completes successfully */
29
+ onInitComplete?: () => void;
30
+ /** Show idle state message? (default: true for InitView, false for OnboardingFlow) */
31
+ showIdleMessage?: boolean;
32
+ }
33
+ export declare const Init: React.FC<InitProps>;