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.
- package/README.md +62 -10
- package/dist/commands/curate.js +2 -2
- package/dist/commands/main.js +2 -2
- package/dist/commands/query.js +2 -2
- package/dist/commands/status.js +2 -2
- package/dist/config/context-tree-domains.d.ts +14 -2
- package/dist/config/context-tree-domains.js +22 -27
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +3 -0
- package/dist/core/domain/cipher/file-system/types.d.ts +2 -0
- package/dist/core/domain/entities/auth-token.js +6 -3
- package/dist/core/domain/entities/event.d.ts +1 -1
- package/dist/core/domain/entities/event.js +2 -1
- package/dist/core/domain/knowledge/relation-parser.d.ts +16 -1
- package/dist/core/domain/knowledge/relation-parser.js +19 -2
- package/dist/core/domain/transport/schemas.d.ts +17 -1
- package/dist/core/domain/transport/schemas.js +9 -1
- package/dist/core/interfaces/cipher/i-blob-storage.d.ts +6 -0
- package/dist/core/interfaces/cipher/index.d.ts +0 -1
- package/dist/core/interfaces/executor/i-curate-executor.d.ts +2 -0
- package/dist/infra/cipher/agent/cipher-agent.js +4 -0
- package/dist/infra/cipher/file-system/file-system-service.d.ts +4 -0
- package/dist/infra/cipher/file-system/file-system-service.js +5 -0
- package/dist/infra/cipher/system-prompt/contributors/context-tree-structure-contributor.js +4 -2
- package/dist/infra/cipher/tools/implementations/create-knowledge-topic-tool.js +24 -17
- package/dist/infra/cipher/tools/implementations/curate-tool.js +28 -33
- package/dist/infra/cipher/tools/implementations/read-file-tool.js +3 -12
- package/dist/infra/cipher/tools/implementations/spec-analyze-tool.js +18 -15
- package/dist/infra/cipher/tools/implementations/task-tool.js +53 -7
- package/dist/infra/context-tree/file-context-tree-service.js +4 -15
- package/dist/infra/core/executors/curate-executor.d.ts +2 -7
- package/dist/infra/core/executors/curate-executor.js +18 -53
- package/dist/infra/core/executors/query-executor.d.ts +1 -7
- package/dist/infra/core/executors/query-executor.js +10 -35
- package/dist/infra/core/task-processor.d.ts +2 -0
- package/dist/infra/core/task-processor.js +1 -0
- package/dist/infra/http/authenticated-http-client.js +5 -0
- package/dist/infra/process/agent-worker.js +113 -6
- package/dist/infra/process/constants.d.ts +1 -0
- package/dist/infra/process/constants.js +1 -0
- package/dist/infra/process/task-queue-manager.js +2 -1
- package/dist/infra/process/transport-handlers.js +4 -0
- package/dist/infra/process/transport-worker.js +89 -1
- package/dist/infra/repl/commands/curate-command.js +2 -2
- package/dist/infra/repl/commands/gen-rules-command.js +2 -2
- package/dist/infra/repl/commands/init-command.js +2 -2
- package/dist/infra/repl/commands/login-command.js +2 -2
- package/dist/infra/repl/commands/logout-command.js +2 -2
- package/dist/infra/repl/commands/pull-command.js +2 -2
- package/dist/infra/repl/commands/push-command.js +2 -2
- package/dist/infra/repl/commands/query-command.js +2 -2
- package/dist/infra/repl/commands/space/list-command.js +2 -2
- package/dist/infra/repl/commands/space/switch-command.js +2 -2
- package/dist/infra/repl/commands/status-command.js +2 -2
- package/dist/infra/repl/repl-startup.js +0 -2
- package/dist/infra/storage/file-token-store.d.ts +31 -0
- package/dist/infra/storage/file-token-store.js +98 -0
- package/dist/infra/storage/keychain-token-store.d.ts +4 -1
- package/dist/infra/storage/keychain-token-store.js +6 -4
- package/dist/infra/storage/token-store.d.ts +10 -0
- package/dist/infra/storage/token-store.js +14 -0
- package/dist/infra/usecase/curate-use-case.js +1 -1
- package/dist/infra/user/http-user-service.js +6 -11
- package/dist/resources/prompts/curate.yml +14 -5
- package/dist/resources/prompts/plan.yml +6 -0
- package/dist/tui/app.js +1 -1
- package/dist/tui/components/execution/log-item.js +2 -5
- package/dist/tui/components/header.d.ts +1 -1
- package/dist/tui/components/header.js +25 -4
- package/dist/tui/components/index.d.ts +5 -1
- package/dist/tui/components/index.js +3 -1
- package/dist/tui/components/init.d.ts +33 -0
- package/dist/tui/components/init.js +253 -0
- package/dist/tui/components/onboarding/index.d.ts +1 -0
- package/dist/tui/components/onboarding/index.js +1 -0
- package/dist/tui/components/onboarding/onboarding-flow.d.ts +2 -0
- package/dist/tui/components/onboarding/onboarding-flow.js +8 -229
- package/dist/tui/components/onboarding/onboarding-step.js +1 -1
- package/dist/tui/components/onboarding/welcome-box.d.ts +14 -0
- package/dist/tui/components/onboarding/welcome-box.js +23 -0
- package/dist/tui/components/status-badge.d.ts +22 -0
- package/dist/tui/components/status-badge.js +32 -0
- package/dist/tui/contexts/auth-context.js +2 -1
- package/dist/tui/contexts/index.d.ts +1 -0
- package/dist/tui/contexts/index.js +1 -0
- package/dist/tui/contexts/onboarding-context.d.ts +14 -0
- package/dist/tui/contexts/onboarding-context.js +17 -22
- package/dist/tui/contexts/status-context.d.ts +33 -0
- package/dist/tui/contexts/status-context.js +159 -0
- package/dist/tui/hooks/use-auth-polling.d.ts +4 -1
- package/dist/tui/hooks/use-auth-polling.js +21 -7
- package/dist/tui/hooks/use-tab-navigation.js +0 -2
- package/dist/tui/providers/app-providers.js +2 -2
- package/dist/tui/types/index.d.ts +2 -0
- package/dist/tui/types/index.js +2 -0
- package/dist/tui/types/status.d.ts +46 -0
- package/dist/tui/types/status.js +13 -0
- package/dist/tui/utils/index.d.ts +6 -0
- package/dist/tui/utils/index.js +6 -0
- package/dist/tui/utils/time.d.ts +10 -0
- package/dist/tui/utils/time.js +15 -0
- package/dist/tui/views/command-view.js +0 -2
- package/dist/tui/views/index.d.ts +1 -0
- package/dist/tui/views/index.js +1 -0
- package/dist/tui/views/init-view.d.ts +15 -0
- package/dist/tui/views/init-view.js +29 -0
- package/dist/tui/views/logs-view.js +22 -8
- package/dist/utils/environment-detector.d.ts +5 -0
- package/dist/utils/environment-detector.js +31 -0
- package/dist/utils/global-data-path.d.ts +11 -0
- package/dist/utils/global-data-path.js +32 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
- package/dist/core/interfaces/cipher/i-agent-storage.d.ts +0 -152
- package/dist/core/interfaces/cipher/i-agent-storage.js +0 -1
- package/dist/infra/cipher/consumer/consumer-lock.d.ts +0 -20
- package/dist/infra/cipher/consumer/consumer-lock.js +0 -41
- package/dist/infra/cipher/consumer/consumer-service.d.ts +0 -99
- package/dist/infra/cipher/consumer/consumer-service.js +0 -166
- package/dist/infra/cipher/consumer/execution-consumer.d.ts +0 -126
- package/dist/infra/cipher/consumer/execution-consumer.js +0 -561
- package/dist/infra/cipher/consumer/index.d.ts +0 -33
- package/dist/infra/cipher/consumer/index.js +0 -34
- package/dist/infra/cipher/consumer/queue-polling-service.d.ts +0 -120
- package/dist/infra/cipher/consumer/queue-polling-service.js +0 -249
- package/dist/infra/cipher/storage/agent-storage.d.ts +0 -246
- 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
|
|
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
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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. **
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
11
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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>;
|