byterover-cli 1.4.0 → 1.6.0
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 +193 -12
- package/dist/core/domain/cipher/process/types.d.ts +1 -1
- package/dist/core/domain/entities/provider-config.d.ts +92 -0
- package/dist/core/domain/entities/provider-config.js +181 -0
- package/dist/core/domain/entities/provider-registry.d.ts +55 -0
- package/dist/core/domain/entities/provider-registry.js +74 -0
- package/dist/core/domain/errors/headless-prompt-error.d.ts +11 -0
- package/dist/core/domain/errors/headless-prompt-error.js +18 -0
- package/dist/core/interfaces/cipher/i-content-generator.d.ts +30 -0
- package/dist/core/interfaces/cipher/i-content-generator.js +12 -1
- package/dist/core/interfaces/cipher/message-factory.d.ts +4 -1
- package/dist/core/interfaces/cipher/message-factory.js +5 -0
- package/dist/core/interfaces/cipher/message-types.d.ts +19 -1
- package/dist/core/interfaces/i-cogit-pull-service.d.ts +0 -1
- package/dist/core/interfaces/i-memory-retrieval-service.d.ts +0 -1
- package/dist/core/interfaces/i-memory-storage-service.d.ts +0 -2
- package/dist/core/interfaces/i-provider-config-store.d.ts +88 -0
- package/dist/core/interfaces/i-provider-config-store.js +1 -0
- package/dist/core/interfaces/i-provider-keychain-store.d.ts +33 -0
- package/dist/core/interfaces/i-provider-keychain-store.js +1 -0
- package/dist/core/interfaces/i-space-service.d.ts +1 -2
- package/dist/core/interfaces/i-team-service.d.ts +1 -2
- package/dist/core/interfaces/i-user-service.d.ts +1 -2
- package/dist/core/interfaces/usecase/i-curate-use-case.d.ts +2 -0
- package/dist/core/interfaces/usecase/i-init-use-case.d.ts +9 -3
- package/dist/core/interfaces/usecase/i-login-use-case.d.ts +4 -1
- package/dist/core/interfaces/usecase/i-pull-use-case.d.ts +5 -3
- package/dist/core/interfaces/usecase/i-push-use-case.d.ts +6 -4
- package/dist/core/interfaces/usecase/i-query-use-case.d.ts +2 -0
- package/dist/core/interfaces/usecase/i-status-use-case.d.ts +1 -0
- package/dist/infra/cipher/agent/service-initializer.d.ts +1 -1
- package/dist/infra/cipher/agent/service-initializer.js +0 -1
- package/dist/infra/cipher/file-system/file-system-service.js +5 -5
- package/dist/infra/cipher/http/internal-llm-http-service.d.ts +40 -1
- package/dist/infra/cipher/http/internal-llm-http-service.js +153 -4
- package/dist/infra/cipher/llm/formatters/gemini-formatter.js +8 -1
- package/dist/infra/cipher/llm/generators/byterover-content-generator.d.ts +2 -3
- package/dist/infra/cipher/llm/generators/byterover-content-generator.js +20 -11
- package/dist/infra/cipher/llm/generators/openrouter-content-generator.d.ts +1 -0
- package/dist/infra/cipher/llm/generators/openrouter-content-generator.js +26 -0
- package/dist/infra/cipher/llm/internal-llm-service.d.ts +13 -0
- package/dist/infra/cipher/llm/internal-llm-service.js +75 -4
- package/dist/infra/cipher/llm/model-capabilities.d.ts +74 -0
- package/dist/infra/cipher/llm/model-capabilities.js +157 -0
- package/dist/infra/cipher/llm/openrouter-llm-service.d.ts +35 -1
- package/dist/infra/cipher/llm/openrouter-llm-service.js +216 -28
- package/dist/infra/cipher/llm/stream-processor.d.ts +22 -2
- package/dist/infra/cipher/llm/stream-processor.js +78 -4
- package/dist/infra/cipher/llm/thought-parser.d.ts +1 -1
- package/dist/infra/cipher/llm/thought-parser.js +5 -5
- package/dist/infra/cipher/llm/transformers/openrouter-stream-transformer.d.ts +49 -0
- package/dist/infra/cipher/llm/transformers/openrouter-stream-transformer.js +272 -0
- package/dist/infra/cipher/llm/transformers/reasoning-extractor.d.ts +71 -0
- package/dist/infra/cipher/llm/transformers/reasoning-extractor.js +253 -0
- package/dist/infra/cipher/process/process-service.js +1 -1
- package/dist/infra/cipher/session/chat-session.d.ts +2 -0
- package/dist/infra/cipher/session/chat-session.js +13 -2
- package/dist/infra/cipher/storage/message-storage-service.js +4 -0
- package/dist/infra/cipher/tools/implementations/bash-exec-tool.js +3 -3
- package/dist/infra/cipher/tools/implementations/task-tool.js +1 -1
- package/dist/infra/cogit/http-cogit-pull-service.js +1 -1
- package/dist/infra/cogit/http-cogit-push-service.js +0 -1
- package/dist/infra/http/authenticated-http-client.d.ts +1 -3
- package/dist/infra/http/authenticated-http-client.js +1 -5
- package/dist/infra/http/openrouter-api-client.d.ts +148 -0
- package/dist/infra/http/openrouter-api-client.js +161 -0
- package/dist/infra/mcp/tools/task-result-waiter.js +9 -1
- package/dist/infra/memory/http-memory-retrieval-service.js +1 -1
- package/dist/infra/memory/http-memory-storage-service.js +2 -2
- package/dist/infra/process/agent-worker.js +178 -70
- package/dist/infra/process/inline-agent-executor.d.ts +32 -0
- package/dist/infra/process/inline-agent-executor.js +259 -0
- package/dist/infra/process/transport-handlers.d.ts +25 -4
- package/dist/infra/process/transport-handlers.js +57 -10
- package/dist/infra/repl/commands/connectors-command.js +2 -2
- package/dist/infra/repl/commands/index.js +5 -0
- package/dist/infra/repl/commands/model-command.d.ts +13 -0
- package/dist/infra/repl/commands/model-command.js +212 -0
- package/dist/infra/repl/commands/provider-command.d.ts +13 -0
- package/dist/infra/repl/commands/provider-command.js +181 -0
- package/dist/infra/repl/transport-client-helper.js +6 -2
- package/dist/infra/space/http-space-service.d.ts +1 -1
- package/dist/infra/space/http-space-service.js +2 -2
- package/dist/infra/storage/file-provider-config-store.d.ts +83 -0
- package/dist/infra/storage/file-provider-config-store.js +157 -0
- package/dist/infra/storage/provider-keychain-store.d.ts +37 -0
- package/dist/infra/storage/provider-keychain-store.js +75 -0
- package/dist/infra/storage/token-store.d.ts +4 -3
- package/dist/infra/storage/token-store.js +6 -5
- package/dist/infra/team/http-team-service.d.ts +1 -1
- package/dist/infra/team/http-team-service.js +2 -2
- package/dist/infra/terminal/headless-terminal.d.ts +91 -0
- package/dist/infra/terminal/headless-terminal.js +211 -0
- package/dist/infra/transport/socket-io-transport-client.d.ts +20 -0
- package/dist/infra/transport/socket-io-transport-client.js +88 -1
- package/dist/infra/usecase/curate-use-case.d.ts +40 -1
- package/dist/infra/usecase/curate-use-case.js +176 -15
- package/dist/infra/usecase/init-use-case.d.ts +27 -5
- package/dist/infra/usecase/init-use-case.js +200 -34
- package/dist/infra/usecase/login-use-case.d.ts +10 -8
- package/dist/infra/usecase/login-use-case.js +35 -2
- package/dist/infra/usecase/pull-use-case.d.ts +19 -5
- package/dist/infra/usecase/pull-use-case.js +71 -13
- package/dist/infra/usecase/push-use-case.d.ts +18 -5
- package/dist/infra/usecase/push-use-case.js +81 -14
- package/dist/infra/usecase/query-use-case.d.ts +21 -0
- package/dist/infra/usecase/query-use-case.js +114 -29
- package/dist/infra/usecase/space-list-use-case.js +1 -1
- package/dist/infra/usecase/space-switch-use-case.js +2 -2
- package/dist/infra/usecase/status-use-case.d.ts +36 -0
- package/dist/infra/usecase/status-use-case.js +185 -48
- package/dist/infra/user/http-user-service.d.ts +1 -1
- package/dist/infra/user/http-user-service.js +2 -2
- package/dist/oclif/commands/curate.d.ts +6 -1
- package/dist/oclif/commands/curate.js +24 -3
- package/dist/oclif/commands/init.d.ts +18 -0
- package/dist/oclif/commands/init.js +129 -0
- package/dist/oclif/commands/login.d.ts +9 -0
- package/dist/oclif/commands/login.js +45 -0
- package/dist/oclif/commands/pull.d.ts +16 -0
- package/dist/oclif/commands/pull.js +78 -0
- package/dist/oclif/commands/push.d.ts +17 -0
- package/dist/oclif/commands/push.js +87 -0
- package/dist/oclif/commands/query.d.ts +6 -1
- package/dist/oclif/commands/query.js +29 -4
- package/dist/oclif/commands/status.d.ts +5 -1
- package/dist/oclif/commands/status.js +17 -5
- package/dist/resources/tools/bash_exec.txt +1 -1
- package/dist/tui/components/api-key-dialog.d.ts +39 -0
- package/dist/tui/components/api-key-dialog.js +94 -0
- package/dist/tui/components/execution/execution-changes.d.ts +3 -1
- package/dist/tui/components/execution/execution-changes.js +4 -4
- package/dist/tui/components/execution/execution-content.d.ts +1 -1
- package/dist/tui/components/execution/execution-content.js +4 -12
- package/dist/tui/components/execution/execution-input.js +1 -1
- package/dist/tui/components/execution/execution-progress.d.ts +10 -13
- package/dist/tui/components/execution/execution-progress.js +70 -17
- package/dist/tui/components/execution/execution-reasoning.d.ts +16 -0
- package/dist/tui/components/execution/execution-reasoning.js +34 -0
- package/dist/tui/components/execution/execution-tool.d.ts +23 -0
- package/dist/tui/components/execution/execution-tool.js +125 -0
- package/dist/tui/components/execution/expanded-log-view.js +3 -3
- package/dist/tui/components/execution/log-item.d.ts +2 -0
- package/dist/tui/components/execution/log-item.js +6 -4
- package/dist/tui/components/index.d.ts +2 -0
- package/dist/tui/components/index.js +2 -0
- package/dist/tui/components/inline-prompts/inline-select.js +3 -2
- package/dist/tui/components/model-dialog.d.ts +63 -0
- package/dist/tui/components/model-dialog.js +89 -0
- package/dist/tui/components/onboarding/onboarding-flow.js +8 -2
- package/dist/tui/components/provider-dialog.d.ts +27 -0
- package/dist/tui/components/provider-dialog.js +31 -0
- package/dist/tui/components/reasoning-text.d.ts +26 -0
- package/dist/tui/components/reasoning-text.js +49 -0
- package/dist/tui/components/selectable-list.d.ts +54 -0
- package/dist/tui/components/selectable-list.js +180 -0
- package/dist/tui/components/streaming-text.d.ts +30 -0
- package/dist/tui/components/streaming-text.js +52 -0
- package/dist/tui/contexts/tasks-context.d.ts +15 -0
- package/dist/tui/contexts/tasks-context.js +224 -40
- package/dist/tui/contexts/theme-context.d.ts +1 -0
- package/dist/tui/contexts/theme-context.js +3 -2
- package/dist/tui/hooks/use-activity-logs.js +7 -1
- package/dist/tui/hooks/use-auth-polling.js +1 -1
- package/dist/tui/types/messages.d.ts +32 -5
- package/dist/tui/utils/index.d.ts +1 -1
- package/dist/tui/utils/index.js +1 -1
- package/dist/tui/utils/log.d.ts +0 -9
- package/dist/tui/utils/log.js +2 -53
- package/dist/tui/views/command-view.js +4 -1
- package/dist/utils/environment-detector.d.ts +15 -0
- package/dist/utils/environment-detector.js +62 -1
- package/oclif.manifest.json +287 -5
- package/package.json +1 -1
|
@@ -1,7 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import type { ITerminal } from '../../core/interfaces/i-terminal.js';
|
|
2
3
|
import type { CurateUseCaseRunOptions, ICurateUseCase } from '../../core/interfaces/usecase/i-curate-use-case.js';
|
|
3
4
|
import { ITrackingService } from '../../core/interfaces/i-tracking-service.js';
|
|
4
5
|
import { type TransportClientFactory } from '../transport/transport-client-factory.js';
|
|
6
|
+
declare const CurateOperationSchema: z.ZodObject<{
|
|
7
|
+
filePath: z.ZodString;
|
|
8
|
+
path: z.ZodString;
|
|
9
|
+
status: z.ZodEnum<["success", "failed"]>;
|
|
10
|
+
type: z.ZodEnum<["ADD", "UPDATE", "MERGE", "DELETE"]>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
path: string;
|
|
13
|
+
type: "DELETE" | "ADD" | "UPDATE" | "MERGE";
|
|
14
|
+
status: "failed" | "success";
|
|
15
|
+
filePath: string;
|
|
16
|
+
}, {
|
|
17
|
+
path: string;
|
|
18
|
+
type: "DELETE" | "ADD" | "UPDATE" | "MERGE";
|
|
19
|
+
status: "failed" | "success";
|
|
20
|
+
filePath: string;
|
|
21
|
+
}>;
|
|
22
|
+
type CurateOperation = z.infer<typeof CurateOperationSchema>;
|
|
23
|
+
export interface CurateResult {
|
|
24
|
+
message: string;
|
|
25
|
+
operations?: CurateOperation[];
|
|
26
|
+
status: 'completed' | 'error' | 'queued';
|
|
27
|
+
taskId?: string;
|
|
28
|
+
}
|
|
5
29
|
export type TransportClientFactoryCreator = () => TransportClientFactory;
|
|
6
30
|
export interface CurateUseCaseOptions {
|
|
7
31
|
terminal: ITerminal;
|
|
@@ -14,6 +38,21 @@ export declare class CurateUseCase implements ICurateUseCase {
|
|
|
14
38
|
private readonly trackingService;
|
|
15
39
|
private readonly transportClientFactoryCreator;
|
|
16
40
|
constructor(options: CurateUseCaseOptions);
|
|
17
|
-
run({ context, files, verbose }: CurateUseCaseRunOptions): Promise<void>;
|
|
41
|
+
run({ context, files, format, headless, verbose, }: CurateUseCaseRunOptions): Promise<void>;
|
|
42
|
+
private createClient;
|
|
18
43
|
private handleConnectionError;
|
|
44
|
+
/**
|
|
45
|
+
* Handle connection errors with JSON output.
|
|
46
|
+
*/
|
|
47
|
+
private handleConnectionErrorJson;
|
|
48
|
+
/**
|
|
49
|
+
* Output JSON result for headless mode.
|
|
50
|
+
*/
|
|
51
|
+
private outputJsonResult;
|
|
52
|
+
/**
|
|
53
|
+
* Wait for task completion in headless mode.
|
|
54
|
+
* Listens for task:completed or task:error events before returning.
|
|
55
|
+
*/
|
|
56
|
+
private waitForTaskCompletion;
|
|
19
57
|
}
|
|
58
|
+
export {};
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ConnectionError, ConnectionFailedError, InstanceCrashedError, NoInstanceRunningError, } from '../../core/domain/errors/connection-error.js';
|
|
3
4
|
import { formatError } from '../../utils/error-handler.js';
|
|
4
5
|
import { getSandboxEnvironmentName, isSandboxEnvironment, isSandboxNetworkError } from '../../utils/sandbox-detector.js';
|
|
6
|
+
import { ToolName } from '../cipher/tools/index.js';
|
|
7
|
+
import { InlineAgent } from '../process/inline-agent-executor.js';
|
|
8
|
+
import { HeadlessTerminal } from '../terminal/headless-terminal.js';
|
|
5
9
|
import { createTransportClientFactory } from '../transport/transport-client-factory.js';
|
|
10
|
+
const CurateOperationSchema = z.object({
|
|
11
|
+
filePath: z.string(),
|
|
12
|
+
path: z.string(),
|
|
13
|
+
status: z.enum(['success', 'failed']),
|
|
14
|
+
type: z.enum(['ADD', 'UPDATE', 'MERGE', 'DELETE']),
|
|
15
|
+
});
|
|
16
|
+
const CurateResultSchema = z.object({
|
|
17
|
+
result: z
|
|
18
|
+
.object({
|
|
19
|
+
applied: z.array(CurateOperationSchema).optional(),
|
|
20
|
+
})
|
|
21
|
+
.optional(),
|
|
22
|
+
});
|
|
6
23
|
export class CurateUseCase {
|
|
7
24
|
terminal;
|
|
8
25
|
trackingService;
|
|
@@ -12,27 +29,27 @@ export class CurateUseCase {
|
|
|
12
29
|
this.trackingService = options.trackingService;
|
|
13
30
|
this.transportClientFactoryCreator = options.transportClientFactoryCreator ?? createTransportClientFactory;
|
|
14
31
|
}
|
|
15
|
-
async run({ context, files, verbose = false }) {
|
|
32
|
+
async run({ context, files, format = 'text', headless = false, verbose = false, }) {
|
|
16
33
|
await this.trackingService.track('mem:curate', { status: 'started' });
|
|
17
34
|
const hasContext = Boolean(context?.trim());
|
|
18
35
|
const hasFiles = Boolean(files?.length);
|
|
19
36
|
if (!hasContext && !hasFiles) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
if (format === 'json') {
|
|
38
|
+
this.outputJsonResult({ message: 'Either a context argument or file reference is required.', status: 'error' });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
this.terminal.log('Either a context argument or file reference is required.');
|
|
42
|
+
this.terminal.log('Usage:');
|
|
43
|
+
this.terminal.log(' brv curate "your context here"');
|
|
44
|
+
this.terminal.log(' brv curate @src/file.ts');
|
|
45
|
+
this.terminal.log(' brv curate "context with files" @src/file.ts');
|
|
46
|
+
}
|
|
25
47
|
return;
|
|
26
48
|
}
|
|
27
49
|
const resolvedContent = context?.trim() ? context : '';
|
|
28
50
|
let client;
|
|
29
51
|
try {
|
|
30
|
-
|
|
31
|
-
if (verbose) {
|
|
32
|
-
this.terminal.log('Discovering running instance...');
|
|
33
|
-
}
|
|
34
|
-
const { client: connectedClient } = await transportClientFactory.connect();
|
|
35
|
-
client = connectedClient;
|
|
52
|
+
client = await this.createClient({ headless, verbose });
|
|
36
53
|
if (verbose) {
|
|
37
54
|
this.terminal.log(`Connected to instance (clientId: ${client.getClientId()})`);
|
|
38
55
|
}
|
|
@@ -45,11 +62,25 @@ export class CurateUseCase {
|
|
|
45
62
|
taskId,
|
|
46
63
|
type: 'curate',
|
|
47
64
|
});
|
|
48
|
-
|
|
65
|
+
if (headless) {
|
|
66
|
+
// In headless mode, wait for the in-process task to complete
|
|
67
|
+
await this.waitForTaskCompletion(client, taskId, format);
|
|
68
|
+
}
|
|
69
|
+
else if (format === 'json') {
|
|
70
|
+
this.outputJsonResult({ message: 'Context queued for processing', status: 'queued', taskId });
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.terminal.log('✓ Context queued for processing.');
|
|
74
|
+
}
|
|
49
75
|
await this.trackingService.track('mem:curate', { status: 'finished' });
|
|
50
76
|
}
|
|
51
77
|
catch (error) {
|
|
52
|
-
|
|
78
|
+
if (format === 'json') {
|
|
79
|
+
this.handleConnectionErrorJson(error);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
this.handleConnectionError(error);
|
|
83
|
+
}
|
|
53
84
|
await this.trackingService.track('mem:curate', { message: formatError(error), status: 'error' });
|
|
54
85
|
}
|
|
55
86
|
finally {
|
|
@@ -58,6 +89,18 @@ export class CurateUseCase {
|
|
|
58
89
|
}
|
|
59
90
|
}
|
|
60
91
|
}
|
|
92
|
+
async createClient(options) {
|
|
93
|
+
if (options.headless) {
|
|
94
|
+
const inlineAgent = await InlineAgent.create();
|
|
95
|
+
return inlineAgent.transportClient;
|
|
96
|
+
}
|
|
97
|
+
const transportClientFactory = this.transportClientFactoryCreator();
|
|
98
|
+
if (options.verbose) {
|
|
99
|
+
this.terminal.log('Discovering running instance...');
|
|
100
|
+
}
|
|
101
|
+
const { client: connectedClient } = await transportClientFactory.connect();
|
|
102
|
+
return connectedClient;
|
|
103
|
+
}
|
|
61
104
|
handleConnectionError(error) {
|
|
62
105
|
if (error instanceof NoInstanceRunningError) {
|
|
63
106
|
// Check if running in sandbox environment
|
|
@@ -100,4 +143,122 @@ export class CurateUseCase {
|
|
|
100
143
|
const message = error instanceof Error ? error.message : String(error);
|
|
101
144
|
this.terminal.log(`Unexpected error: ${message}`);
|
|
102
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Handle connection errors with JSON output.
|
|
148
|
+
*/
|
|
149
|
+
handleConnectionErrorJson(error) {
|
|
150
|
+
let errorMessage = 'An unexpected error occurred';
|
|
151
|
+
if (error instanceof NoInstanceRunningError) {
|
|
152
|
+
errorMessage = 'No ByteRover instance is running. Start one with: brv';
|
|
153
|
+
}
|
|
154
|
+
else if (error instanceof InstanceCrashedError) {
|
|
155
|
+
errorMessage = 'ByteRover instance has crashed. Please restart with: brv';
|
|
156
|
+
}
|
|
157
|
+
else if (error instanceof ConnectionFailedError) {
|
|
158
|
+
errorMessage = `Failed to connect to ByteRover instance: ${error.message}`;
|
|
159
|
+
}
|
|
160
|
+
else if (error instanceof ConnectionError) {
|
|
161
|
+
errorMessage = `Connection error: ${error.message}`;
|
|
162
|
+
}
|
|
163
|
+
else if (error instanceof Error) {
|
|
164
|
+
errorMessage = error.message;
|
|
165
|
+
}
|
|
166
|
+
this.outputJsonResult({ message: errorMessage, status: 'error' });
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Output JSON result for headless mode.
|
|
170
|
+
*/
|
|
171
|
+
outputJsonResult(result) {
|
|
172
|
+
const response = {
|
|
173
|
+
command: 'curate',
|
|
174
|
+
data: result,
|
|
175
|
+
success: result.status !== 'error',
|
|
176
|
+
timestamp: new Date().toISOString(),
|
|
177
|
+
};
|
|
178
|
+
if (this.terminal instanceof HeadlessTerminal) {
|
|
179
|
+
this.terminal.writeFinalResponse(response);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
this.terminal.log(JSON.stringify(response));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Wait for task completion in headless mode.
|
|
187
|
+
* Listens for task:completed or task:error events before returning.
|
|
188
|
+
*/
|
|
189
|
+
async waitForTaskCompletion(client, taskId, format) {
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
let completed = false;
|
|
192
|
+
const operations = [];
|
|
193
|
+
const timeout = setTimeout(() => {
|
|
194
|
+
if (!completed) {
|
|
195
|
+
completed = true;
|
|
196
|
+
cleanup();
|
|
197
|
+
if (format === 'json') {
|
|
198
|
+
this.outputJsonResult({ message: 'Task timed out after 5 minutes', status: 'error' });
|
|
199
|
+
resolve();
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
reject(new Error('Task timed out after 5 minutes'));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}, 5 * 60 * 1000);
|
|
206
|
+
const unsubscribers = [
|
|
207
|
+
client.on('llmservice:toolResult', (payload) => {
|
|
208
|
+
if (payload.success && payload.toolName === ToolName.CURATE && payload.result) {
|
|
209
|
+
try {
|
|
210
|
+
const parsed = CurateResultSchema.parse(JSON.parse(payload.result));
|
|
211
|
+
for (const op of parsed.result?.applied ?? []) {
|
|
212
|
+
if (op.status === 'success') {
|
|
213
|
+
operations.push(op);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
// Ignore parse errors
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}),
|
|
222
|
+
client.on('task:completed', (payload) => {
|
|
223
|
+
if (payload.taskId === taskId && !completed) {
|
|
224
|
+
completed = true;
|
|
225
|
+
cleanup();
|
|
226
|
+
if (format === 'json') {
|
|
227
|
+
this.outputJsonResult({
|
|
228
|
+
message: 'Context curated successfully',
|
|
229
|
+
operations: operations.length > 0 ? operations : undefined,
|
|
230
|
+
status: 'completed',
|
|
231
|
+
taskId,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
for (const op of operations) {
|
|
236
|
+
this.terminal.log(` ${op.type.toLowerCase()} ${op.filePath}`);
|
|
237
|
+
}
|
|
238
|
+
this.terminal.log('✓ Context curated successfully.');
|
|
239
|
+
}
|
|
240
|
+
resolve();
|
|
241
|
+
}
|
|
242
|
+
}),
|
|
243
|
+
client.on('task:error', (payload) => {
|
|
244
|
+
if (payload.taskId === taskId && !completed) {
|
|
245
|
+
completed = true;
|
|
246
|
+
cleanup();
|
|
247
|
+
if (format === 'json') {
|
|
248
|
+
this.outputJsonResult({ message: payload.error.message, status: 'error' });
|
|
249
|
+
resolve();
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
reject(new Error(payload.error.message));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}),
|
|
256
|
+
() => clearTimeout(timeout),
|
|
257
|
+
];
|
|
258
|
+
const cleanup = () => {
|
|
259
|
+
for (const unsub of unsubscribers)
|
|
260
|
+
unsub();
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
}
|
|
103
264
|
}
|
|
@@ -13,9 +13,19 @@ import type { ITeamService } from '../../core/interfaces/i-team-service.js';
|
|
|
13
13
|
import type { ITerminal } from '../../core/interfaces/i-terminal.js';
|
|
14
14
|
import type { ITokenStore } from '../../core/interfaces/i-token-store.js';
|
|
15
15
|
import type { ITrackingService } from '../../core/interfaces/i-tracking-service.js';
|
|
16
|
-
import type { IInitUseCase } from '../../core/interfaces/usecase/i-init-use-case.js';
|
|
16
|
+
import type { IInitUseCase, InitUseCaseRunOptions } from '../../core/interfaces/usecase/i-init-use-case.js';
|
|
17
17
|
import { type Agent } from '../../core/domain/entities/agent.js';
|
|
18
18
|
import { BrvConfig } from '../../core/domain/entities/brv-config.js';
|
|
19
|
+
/**
|
|
20
|
+
* Structured init result for JSON output.
|
|
21
|
+
*/
|
|
22
|
+
export interface InitResult {
|
|
23
|
+
configPath?: string;
|
|
24
|
+
error?: string;
|
|
25
|
+
spaceName?: string;
|
|
26
|
+
status: 'cancelled' | 'error' | 'success';
|
|
27
|
+
teamName?: string;
|
|
28
|
+
}
|
|
19
29
|
/**
|
|
20
30
|
* Represents a legacy config that exists but has version issues.
|
|
21
31
|
* Used to display config info during re-initialization prompt.
|
|
@@ -64,9 +74,19 @@ export declare class InitUseCase implements IInitUseCase {
|
|
|
64
74
|
chatLogPath: string;
|
|
65
75
|
cwd: string;
|
|
66
76
|
};
|
|
67
|
-
protected ensureAuthenticated(): Promise<AuthToken | undefined>;
|
|
77
|
+
protected ensureAuthenticated(format: 'json' | 'text'): Promise<AuthToken | undefined>;
|
|
68
78
|
protected fetchAndSelectSpace(token: AuthToken, team: Team): Promise<Space | undefined>;
|
|
69
79
|
protected fetchAndSelectTeam(token: AuthToken): Promise<Team | undefined>;
|
|
80
|
+
/**
|
|
81
|
+
* Fetch space by ID or name for headless mode.
|
|
82
|
+
* First tries to match by ID, then by name (case-insensitive).
|
|
83
|
+
*/
|
|
84
|
+
protected fetchSpaceById(token: AuthToken, team: Team, spaceIdOrName: string, format: 'json' | 'text'): Promise<Space | undefined>;
|
|
85
|
+
/**
|
|
86
|
+
* Fetch team by ID or name for headless mode.
|
|
87
|
+
* First tries to match by ID, then by name (case-insensitive).
|
|
88
|
+
*/
|
|
89
|
+
protected fetchTeamById(token: AuthToken, teamIdOrName: string, format: 'json' | 'text'): Promise<Team | undefined>;
|
|
70
90
|
protected getExistingConfig(): Promise<BrvConfig | LegacyProjectConfigInfo | undefined>;
|
|
71
91
|
protected initializeMemoryContextDir(name: string, initFn: () => Promise<string>): Promise<void>;
|
|
72
92
|
/**
|
|
@@ -90,9 +110,7 @@ export declare class InitUseCase implements IInitUseCase {
|
|
|
90
110
|
protected promptForSpaceSelection(spaces: Space[]): Promise<Space | undefined>;
|
|
91
111
|
protected promptForTeamSelection(teams: Team[]): Promise<Team | undefined>;
|
|
92
112
|
protected removeAceDirectory(baseDir?: string): Promise<void>;
|
|
93
|
-
run(options:
|
|
94
|
-
force: boolean;
|
|
95
|
-
}): Promise<void>;
|
|
113
|
+
run(options: InitUseCaseRunOptions): Promise<void>;
|
|
96
114
|
protected syncFromRemoteOrInitialize(params: {
|
|
97
115
|
projectConfig: {
|
|
98
116
|
spaceId: string;
|
|
@@ -101,4 +119,8 @@ export declare class InitUseCase implements IInitUseCase {
|
|
|
101
119
|
token: AuthToken;
|
|
102
120
|
}): Promise<void>;
|
|
103
121
|
private logSuccess;
|
|
122
|
+
/**
|
|
123
|
+
* Output JSON result for headless mode.
|
|
124
|
+
*/
|
|
125
|
+
private outputJsonResult;
|
|
104
126
|
}
|
|
@@ -5,6 +5,7 @@ import { ACE_DIR, BRV_CONFIG_VERSION, BRV_DIR, DEFAULT_BRANCH, PROJECT_CONFIG_FI
|
|
|
5
5
|
import { AGENT_VALUES } from '../../core/domain/entities/agent.js';
|
|
6
6
|
import { BrvConfig } from '../../core/domain/entities/brv-config.js';
|
|
7
7
|
import { BrvConfigVersionError } from '../../core/domain/errors/brv-config-version-error.js';
|
|
8
|
+
import { HeadlessTerminal } from '../terminal/headless-terminal.js';
|
|
8
9
|
import { WorkspaceDetectorService } from '../workspace/workspace-detector-service.js';
|
|
9
10
|
export class InitUseCase {
|
|
10
11
|
cogitPullService;
|
|
@@ -88,21 +89,31 @@ export class InitUseCase {
|
|
|
88
89
|
cwd: result.cwd,
|
|
89
90
|
};
|
|
90
91
|
}
|
|
91
|
-
async ensureAuthenticated() {
|
|
92
|
+
async ensureAuthenticated(format) {
|
|
92
93
|
const token = await this.tokenStore.load();
|
|
93
94
|
if (token === undefined) {
|
|
94
|
-
|
|
95
|
+
if (format === 'json') {
|
|
96
|
+
this.outputJsonResult({ error: 'Not authenticated. Run login first.', status: 'error' });
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
this.terminal.log('Not authenticated. Please run "/login" first.');
|
|
100
|
+
}
|
|
95
101
|
return undefined;
|
|
96
102
|
}
|
|
97
103
|
if (!token.isValid()) {
|
|
98
|
-
|
|
104
|
+
if (format === 'json') {
|
|
105
|
+
this.outputJsonResult({ error: 'Authentication token expired. Run login again.', status: 'error' });
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
this.terminal.log('Authentication token expired. Please run "/login" again.');
|
|
109
|
+
}
|
|
99
110
|
return undefined;
|
|
100
111
|
}
|
|
101
112
|
return token;
|
|
102
113
|
}
|
|
103
114
|
async fetchAndSelectSpace(token, team) {
|
|
104
115
|
this.terminal.actionStart('Fetching all spaces');
|
|
105
|
-
const { spaces } = await this.spaceService.getSpaces(token.
|
|
116
|
+
const { spaces } = await this.spaceService.getSpaces(token.sessionKey, team.id, { fetchAll: true });
|
|
106
117
|
this.terminal.actionStop();
|
|
107
118
|
if (spaces.length === 0) {
|
|
108
119
|
this.terminal.error(`No spaces found in team "${team.getDisplayName()}"\nPlease visit ${getCurrentConfig().webAppUrl} to create your first space for ${team.getDisplayName()}.`);
|
|
@@ -113,7 +124,7 @@ export class InitUseCase {
|
|
|
113
124
|
}
|
|
114
125
|
async fetchAndSelectTeam(token) {
|
|
115
126
|
this.terminal.actionStart('Fetching all teams');
|
|
116
|
-
const { teams } = await this.teamService.getTeams(token.
|
|
127
|
+
const { teams } = await this.teamService.getTeams(token.sessionKey, { fetchAll: true });
|
|
117
128
|
this.terminal.actionStop();
|
|
118
129
|
if (teams.length === 0) {
|
|
119
130
|
this.terminal.error(`No teams found.\nPlease visit ${getCurrentConfig().webAppUrl} to create your first team.`);
|
|
@@ -122,6 +133,82 @@ export class InitUseCase {
|
|
|
122
133
|
this.terminal.log();
|
|
123
134
|
return this.promptForTeamSelection(teams);
|
|
124
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Fetch space by ID or name for headless mode.
|
|
138
|
+
* First tries to match by ID, then by name (case-insensitive).
|
|
139
|
+
*/
|
|
140
|
+
async fetchSpaceById(token, team, spaceIdOrName, format) {
|
|
141
|
+
this.terminal.actionStart('Fetching space');
|
|
142
|
+
try {
|
|
143
|
+
const { spaces } = await this.spaceService.getSpaces(token.sessionKey, team.id, { fetchAll: true });
|
|
144
|
+
this.terminal.actionStop();
|
|
145
|
+
// First try to find by ID
|
|
146
|
+
let space = spaces.find((s) => s.id === spaceIdOrName);
|
|
147
|
+
// If not found by ID, try to find by name (case-insensitive)
|
|
148
|
+
if (!space) {
|
|
149
|
+
space = spaces.find((s) => s.name.toLowerCase() === spaceIdOrName.toLowerCase());
|
|
150
|
+
}
|
|
151
|
+
if (!space) {
|
|
152
|
+
if (format === 'json') {
|
|
153
|
+
this.outputJsonResult({ error: `Space "${spaceIdOrName}" not found in team "${team.name}"`, status: 'error' });
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
this.terminal.error(`Space "${spaceIdOrName}" not found in team "${team.name}"`);
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
return space;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
this.terminal.actionStop();
|
|
164
|
+
const message = error instanceof Error ? error.message : 'Failed to fetch space';
|
|
165
|
+
if (format === 'json') {
|
|
166
|
+
this.outputJsonResult({ error: message, status: 'error' });
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.terminal.error(message);
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Fetch team by ID or name for headless mode.
|
|
176
|
+
* First tries to match by ID, then by name (case-insensitive).
|
|
177
|
+
*/
|
|
178
|
+
async fetchTeamById(token, teamIdOrName, format) {
|
|
179
|
+
this.terminal.actionStart('Fetching team');
|
|
180
|
+
try {
|
|
181
|
+
const { teams } = await this.teamService.getTeams(token.sessionKey, { fetchAll: true });
|
|
182
|
+
this.terminal.actionStop();
|
|
183
|
+
// First try to find by ID
|
|
184
|
+
let team = teams.find((t) => t.id === teamIdOrName);
|
|
185
|
+
// If not found by ID, try to find by name (case-insensitive)
|
|
186
|
+
if (!team) {
|
|
187
|
+
team = teams.find((t) => t.name.toLowerCase() === teamIdOrName.toLowerCase());
|
|
188
|
+
}
|
|
189
|
+
if (!team) {
|
|
190
|
+
if (format === 'json') {
|
|
191
|
+
this.outputJsonResult({ error: `Team "${teamIdOrName}" not found`, status: 'error' });
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
this.terminal.error(`Team "${teamIdOrName}" not found`);
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
return team;
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
this.terminal.actionStop();
|
|
202
|
+
const message = error instanceof Error ? error.message : 'Failed to fetch team';
|
|
203
|
+
if (format === 'json') {
|
|
204
|
+
this.outputJsonResult({ error: message, status: 'error' });
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
this.terminal.error(message);
|
|
208
|
+
}
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
125
212
|
async getExistingConfig() {
|
|
126
213
|
const exists = await this.projectConfigStore.exists();
|
|
127
214
|
if (!exists)
|
|
@@ -266,37 +353,80 @@ export class InitUseCase {
|
|
|
266
353
|
await rm(acePath, { force: true, recursive: true });
|
|
267
354
|
}
|
|
268
355
|
async run(options) {
|
|
356
|
+
const format = options.format ?? 'text';
|
|
357
|
+
const isHeadless = Boolean(options.teamId && options.spaceId);
|
|
269
358
|
try {
|
|
270
359
|
await this.trackingService.track('init', { status: 'started' });
|
|
271
|
-
const authToken = await this.ensureAuthenticated();
|
|
360
|
+
const authToken = await this.ensureAuthenticated(format);
|
|
272
361
|
if (!authToken)
|
|
273
362
|
return;
|
|
274
363
|
const existingConfig = await this.getExistingConfig();
|
|
275
364
|
if (existingConfig) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
365
|
+
// In headless mode with force, always cleanup
|
|
366
|
+
// In headless mode without force, fail
|
|
367
|
+
// In interactive mode, prompt for confirmation
|
|
368
|
+
if (isHeadless) {
|
|
369
|
+
if (options.force) {
|
|
370
|
+
await this.cleanupBeforeReInitialization();
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
if (format === 'json') {
|
|
374
|
+
this.outputJsonResult({
|
|
375
|
+
error: 'Project already initialized. Use --force to re-initialize.',
|
|
376
|
+
status: 'error',
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
this.terminal.error('Project already initialized. Use --force to re-initialize.');
|
|
381
|
+
}
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
280
384
|
}
|
|
281
385
|
else {
|
|
282
|
-
|
|
283
|
-
|
|
386
|
+
const shouldCleanup = options.force ? true : await this.confirmReInitialization(existingConfig);
|
|
387
|
+
if (shouldCleanup) {
|
|
388
|
+
await this.cleanupBeforeReInitialization();
|
|
389
|
+
this.terminal.log('\n');
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
if (format === 'json') {
|
|
393
|
+
this.outputJsonResult({ status: 'cancelled' });
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
this.terminal.log('\nCancelled. Project configuration unchanged.');
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
284
400
|
}
|
|
285
401
|
}
|
|
286
402
|
this.terminal.log('Initializing ByteRover project...\n');
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
403
|
+
let selectedTeam;
|
|
404
|
+
let selectedSpace;
|
|
405
|
+
if (isHeadless && options.teamId && options.spaceId) {
|
|
406
|
+
// Headless mode: fetch team and space by ID
|
|
407
|
+
selectedTeam = await this.fetchTeamById(authToken, options.teamId, format);
|
|
408
|
+
if (!selectedTeam)
|
|
409
|
+
return;
|
|
410
|
+
selectedSpace = await this.fetchSpaceById(authToken, selectedTeam, options.spaceId, format);
|
|
411
|
+
if (!selectedSpace)
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
// Interactive mode: prompt for selection
|
|
416
|
+
selectedTeam = await this.fetchAndSelectTeam(authToken);
|
|
417
|
+
if (!selectedTeam)
|
|
418
|
+
return;
|
|
419
|
+
selectedSpace = await this.fetchAndSelectSpace(authToken, selectedTeam);
|
|
420
|
+
if (!selectedSpace)
|
|
421
|
+
return;
|
|
422
|
+
// Handle ACE deprecation - check for existing ACE folder and offer removal
|
|
423
|
+
const aceExists = await this.aceDirectoryExists();
|
|
424
|
+
if (aceExists) {
|
|
425
|
+
const shouldRemoveAce = await this.promptAceDeprecationRemoval();
|
|
426
|
+
if (shouldRemoveAce) {
|
|
427
|
+
await this.removeAceDirectory();
|
|
428
|
+
this.terminal.log('✓ ACE folder removed');
|
|
429
|
+
}
|
|
300
430
|
}
|
|
301
431
|
}
|
|
302
432
|
// Sync from remote or initialize context tree with templates
|
|
@@ -304,8 +434,11 @@ export class InitUseCase {
|
|
|
304
434
|
projectConfig: { spaceId: selectedSpace.id, teamId: selectedTeam.id },
|
|
305
435
|
token: authToken,
|
|
306
436
|
});
|
|
307
|
-
|
|
308
|
-
|
|
437
|
+
let selectedAgent = 'Claude Code';
|
|
438
|
+
if (!isHeadless) {
|
|
439
|
+
this.terminal.log();
|
|
440
|
+
selectedAgent = await this.promptForAgentSelection();
|
|
441
|
+
}
|
|
309
442
|
const { chatLogPath, cwd } = this.detectWorkspacesForAgent(selectedAgent);
|
|
310
443
|
this.terminal.log(`✓ Detected workspace: ${cwd}`);
|
|
311
444
|
const config = BrvConfig.fromSpace({
|
|
@@ -315,18 +448,35 @@ export class InitUseCase {
|
|
|
315
448
|
space: selectedSpace,
|
|
316
449
|
});
|
|
317
450
|
await this.projectConfigStore.write(config);
|
|
318
|
-
|
|
319
|
-
|
|
451
|
+
if (!isHeadless) {
|
|
452
|
+
this.terminal.log();
|
|
453
|
+
await this.installConnectorForAgent(selectedAgent);
|
|
454
|
+
}
|
|
320
455
|
await this.trackingService.track('space:init');
|
|
321
|
-
|
|
456
|
+
if (format === 'json') {
|
|
457
|
+
this.outputJsonResult({
|
|
458
|
+
configPath: join(process.cwd(), BRV_DIR, PROJECT_CONFIG_FILE),
|
|
459
|
+
spaceName: selectedSpace.getDisplayName(),
|
|
460
|
+
status: 'success',
|
|
461
|
+
teamName: selectedTeam.name,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
this.logSuccess(selectedSpace);
|
|
466
|
+
}
|
|
322
467
|
await this.trackingService.track('init', { status: 'finished' });
|
|
323
468
|
}
|
|
324
469
|
catch (error) {
|
|
325
470
|
// Stop action if it's in progress
|
|
326
471
|
this.terminal.actionStop();
|
|
327
|
-
const initErr =
|
|
328
|
-
await this.trackingService.track('init', { message: initErr
|
|
329
|
-
|
|
472
|
+
const initErr = error instanceof Error ? error.message : 'Unknown error';
|
|
473
|
+
await this.trackingService.track('init', { message: `Initialization failed: ${initErr}`, status: 'error' });
|
|
474
|
+
if (format === 'json') {
|
|
475
|
+
this.outputJsonResult({ error: initErr, status: 'error' });
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
this.terminal.error(`Initialization failed: ${initErr}`);
|
|
479
|
+
}
|
|
330
480
|
}
|
|
331
481
|
}
|
|
332
482
|
async syncFromRemoteOrInitialize(params) {
|
|
@@ -334,7 +484,6 @@ export class InitUseCase {
|
|
|
334
484
|
this.terminal.actionStart('Syncing from ByteRover...');
|
|
335
485
|
try {
|
|
336
486
|
const coGitSnapshot = await this.cogitPullService.pull({
|
|
337
|
-
accessToken: params.token.accessToken,
|
|
338
487
|
branch: DEFAULT_BRANCH,
|
|
339
488
|
sessionKey: params.token.sessionKey,
|
|
340
489
|
spaceId: params.projectConfig.spaceId,
|
|
@@ -369,4 +518,21 @@ export class InitUseCase {
|
|
|
369
518
|
this.terminal.log(`✓ Configuration saved to: ${BRV_DIR}/${PROJECT_CONFIG_FILE}`);
|
|
370
519
|
this.terminal.log("NOTE: It's recommended to add .brv/ to your .gitignore file since ByteRover already takes care of memory/context versioning for you.");
|
|
371
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* Output JSON result for headless mode.
|
|
523
|
+
*/
|
|
524
|
+
outputJsonResult(result) {
|
|
525
|
+
const response = {
|
|
526
|
+
command: 'init',
|
|
527
|
+
data: result,
|
|
528
|
+
success: result.status === 'success',
|
|
529
|
+
timestamp: new Date().toISOString(),
|
|
530
|
+
};
|
|
531
|
+
if (this.terminal instanceof HeadlessTerminal) {
|
|
532
|
+
this.terminal.writeFinalResponse(response);
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
this.terminal.log(JSON.stringify(response));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
372
538
|
}
|