byterover-cli 1.5.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.
Files changed (73) hide show
  1. package/README.md +132 -11
  2. package/dist/core/domain/errors/headless-prompt-error.d.ts +11 -0
  3. package/dist/core/domain/errors/headless-prompt-error.js +18 -0
  4. package/dist/core/interfaces/i-cogit-pull-service.d.ts +0 -1
  5. package/dist/core/interfaces/i-memory-retrieval-service.d.ts +0 -1
  6. package/dist/core/interfaces/i-memory-storage-service.d.ts +0 -2
  7. package/dist/core/interfaces/i-space-service.d.ts +1 -2
  8. package/dist/core/interfaces/i-team-service.d.ts +1 -2
  9. package/dist/core/interfaces/i-user-service.d.ts +1 -2
  10. package/dist/core/interfaces/usecase/i-curate-use-case.d.ts +2 -0
  11. package/dist/core/interfaces/usecase/i-init-use-case.d.ts +9 -3
  12. package/dist/core/interfaces/usecase/i-login-use-case.d.ts +4 -1
  13. package/dist/core/interfaces/usecase/i-pull-use-case.d.ts +5 -3
  14. package/dist/core/interfaces/usecase/i-push-use-case.d.ts +6 -4
  15. package/dist/core/interfaces/usecase/i-query-use-case.d.ts +2 -0
  16. package/dist/core/interfaces/usecase/i-status-use-case.d.ts +1 -0
  17. package/dist/infra/cipher/agent/service-initializer.d.ts +1 -1
  18. package/dist/infra/cipher/agent/service-initializer.js +0 -1
  19. package/dist/infra/cipher/http/internal-llm-http-service.d.ts +0 -1
  20. package/dist/infra/cipher/http/internal-llm-http-service.js +1 -2
  21. package/dist/infra/cogit/http-cogit-pull-service.js +1 -1
  22. package/dist/infra/cogit/http-cogit-push-service.js +0 -1
  23. package/dist/infra/http/authenticated-http-client.d.ts +1 -3
  24. package/dist/infra/http/authenticated-http-client.js +1 -5
  25. package/dist/infra/memory/http-memory-retrieval-service.js +1 -1
  26. package/dist/infra/memory/http-memory-storage-service.js +2 -2
  27. package/dist/infra/process/inline-agent-executor.d.ts +32 -0
  28. package/dist/infra/process/inline-agent-executor.js +259 -0
  29. package/dist/infra/space/http-space-service.d.ts +1 -1
  30. package/dist/infra/space/http-space-service.js +2 -2
  31. package/dist/infra/storage/token-store.d.ts +4 -3
  32. package/dist/infra/storage/token-store.js +6 -5
  33. package/dist/infra/team/http-team-service.d.ts +1 -1
  34. package/dist/infra/team/http-team-service.js +2 -2
  35. package/dist/infra/terminal/headless-terminal.d.ts +91 -0
  36. package/dist/infra/terminal/headless-terminal.js +211 -0
  37. package/dist/infra/usecase/curate-use-case.d.ts +40 -1
  38. package/dist/infra/usecase/curate-use-case.js +176 -15
  39. package/dist/infra/usecase/init-use-case.d.ts +27 -5
  40. package/dist/infra/usecase/init-use-case.js +200 -34
  41. package/dist/infra/usecase/login-use-case.d.ts +10 -8
  42. package/dist/infra/usecase/login-use-case.js +35 -2
  43. package/dist/infra/usecase/pull-use-case.d.ts +19 -5
  44. package/dist/infra/usecase/pull-use-case.js +71 -13
  45. package/dist/infra/usecase/push-use-case.d.ts +18 -5
  46. package/dist/infra/usecase/push-use-case.js +81 -14
  47. package/dist/infra/usecase/query-use-case.d.ts +21 -0
  48. package/dist/infra/usecase/query-use-case.js +114 -29
  49. package/dist/infra/usecase/space-list-use-case.js +1 -1
  50. package/dist/infra/usecase/space-switch-use-case.js +2 -2
  51. package/dist/infra/usecase/status-use-case.d.ts +36 -0
  52. package/dist/infra/usecase/status-use-case.js +185 -48
  53. package/dist/infra/user/http-user-service.d.ts +1 -1
  54. package/dist/infra/user/http-user-service.js +2 -2
  55. package/dist/oclif/commands/curate.d.ts +6 -1
  56. package/dist/oclif/commands/curate.js +24 -3
  57. package/dist/oclif/commands/init.d.ts +18 -0
  58. package/dist/oclif/commands/init.js +129 -0
  59. package/dist/oclif/commands/login.d.ts +9 -0
  60. package/dist/oclif/commands/login.js +45 -0
  61. package/dist/oclif/commands/pull.d.ts +16 -0
  62. package/dist/oclif/commands/pull.js +78 -0
  63. package/dist/oclif/commands/push.d.ts +17 -0
  64. package/dist/oclif/commands/push.js +87 -0
  65. package/dist/oclif/commands/query.d.ts +6 -1
  66. package/dist/oclif/commands/query.js +29 -4
  67. package/dist/oclif/commands/status.d.ts +5 -1
  68. package/dist/oclif/commands/status.js +17 -5
  69. package/dist/tui/hooks/use-auth-polling.js +1 -1
  70. package/dist/utils/environment-detector.d.ts +15 -0
  71. package/dist/utils/environment-detector.js +62 -1
  72. package/oclif.manifest.json +287 -5
  73. package/package.json +1 -1
@@ -5,24 +5,26 @@ import type { ITerminal } from '../../core/interfaces/i-terminal.js';
5
5
  import type { ITokenStore } from '../../core/interfaces/i-token-store.js';
6
6
  import type { ITrackingService } from '../../core/interfaces/i-tracking-service.js';
7
7
  import type { IUserService } from '../../core/interfaces/i-user-service.js';
8
- import type { ILoginUseCase } from '../../core/interfaces/usecase/i-login-use-case.js';
8
+ import type { ILoginUseCase, LoginUseCaseRunOptions } from '../../core/interfaces/usecase/i-login-use-case.js';
9
9
  export interface LoginUseCaseOptions {
10
- authService: IAuthService;
11
- browserLauncher: IBrowserLauncher;
12
- callbackHandler: ICallbackHandler;
10
+ authService?: IAuthService;
11
+ browserLauncher?: IBrowserLauncher;
12
+ callbackHandler?: ICallbackHandler;
13
13
  terminal: ITerminal;
14
14
  tokenStore: ITokenStore;
15
15
  trackingService: ITrackingService;
16
16
  userService: IUserService;
17
17
  }
18
18
  export declare class LoginUseCase implements ILoginUseCase {
19
- private readonly authService;
20
- private readonly browserLauncher;
21
- private readonly callbackHandler;
19
+ private readonly authService?;
20
+ private readonly browserLauncher?;
21
+ private readonly callbackHandler?;
22
22
  private readonly terminal;
23
23
  private readonly tokenStore;
24
24
  private readonly trackingService;
25
25
  private readonly userService;
26
26
  constructor(options: LoginUseCaseOptions);
27
- run(): Promise<void>;
27
+ run({ apiKey }?: LoginUseCaseRunOptions): Promise<void>;
28
+ runLoginManually(): Promise<void>;
29
+ runLoginWithApiKey(apiKey: string): Promise<void>;
28
30
  }
@@ -17,7 +17,13 @@ export class LoginUseCase {
17
17
  this.trackingService = options.trackingService;
18
18
  this.userService = options.userService;
19
19
  }
20
- async run() {
20
+ async run({ apiKey } = {}) {
21
+ return apiKey ? this.runLoginWithApiKey(apiKey) : this.runLoginManually();
22
+ }
23
+ async runLoginManually() {
24
+ if (!this.authService || !this.browserLauncher || !this.callbackHandler) {
25
+ throw new Error('OAuth services are required for manual login');
26
+ }
21
27
  try {
22
28
  await this.trackingService.track('auth:sign_in', { status: 'started' });
23
29
  this.terminal.log('Starting authentication process...');
@@ -53,7 +59,7 @@ export class LoginUseCase {
53
59
  // Wait for callback with 5 minute timeout
54
60
  const { code } = await this.callbackHandler.waitForCallback(authContext.state, 5 * 60 * 1000);
55
61
  const authTokenData = await this.authService.exchangeCodeForToken(code, authContext, redirectUri);
56
- const user = await this.userService.getCurrentUser(authTokenData.accessToken, authTokenData.sessionKey);
62
+ const user = await this.userService.getCurrentUser(authTokenData.sessionKey);
57
63
  const authToken = new AuthToken({
58
64
  accessToken: authTokenData.accessToken,
59
65
  expiresAt: authTokenData.expiresAt,
@@ -91,4 +97,31 @@ export class LoginUseCase {
91
97
  await this.callbackHandler.stop();
92
98
  }
93
99
  }
100
+ async runLoginWithApiKey(apiKey) {
101
+ try {
102
+ await this.trackingService.track('auth:sign_in', { status: 'started' });
103
+ this.terminal.log('Logging in...');
104
+ const user = await this.userService.getCurrentUser(apiKey);
105
+ // eslint-disable-next-line no-warning-comments
106
+ // TODO: accessToken, refreshToken, and tokenType do not appear to be necessary; consider removing them.
107
+ const authToken = new AuthToken({
108
+ accessToken: 'unnecessary',
109
+ expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // 30 days
110
+ refreshToken: 'unnecessary',
111
+ sessionKey: apiKey,
112
+ tokenType: 'unnecessary',
113
+ userEmail: user.email,
114
+ userId: user.id,
115
+ });
116
+ await this.tokenStore.save(authToken);
117
+ await this.trackingService.track('auth:sign_in', { status: 'finished' });
118
+ this.terminal.log(`Logged in as ${user.email}`);
119
+ }
120
+ catch (error) {
121
+ // Throw error to let oclif handle display
122
+ const errorMessage = error instanceof Error ? error.message : 'Authentication failed';
123
+ await this.trackingService.track('auth:sign_in', { message: errorMessage, status: 'error' });
124
+ this.terminal.log(errorMessage);
125
+ }
126
+ }
94
127
  }
@@ -7,7 +7,19 @@ import type { IProjectConfigStore } from '../../core/interfaces/i-project-config
7
7
  import type { ITerminal } from '../../core/interfaces/i-terminal.js';
8
8
  import type { ITokenStore } from '../../core/interfaces/i-token-store.js';
9
9
  import type { ITrackingService } from '../../core/interfaces/i-tracking-service.js';
10
- import type { IPullUseCase } from '../../core/interfaces/usecase/i-pull-use-case.js';
10
+ import type { IPullUseCase, PullUseCaseRunOptions } from '../../core/interfaces/usecase/i-pull-use-case.js';
11
+ /**
12
+ * Structured pull result for JSON output.
13
+ */
14
+ export interface PullResult {
15
+ added?: number;
16
+ branch?: string;
17
+ commitSha?: string;
18
+ deleted?: number;
19
+ edited?: number;
20
+ error?: string;
21
+ status: 'error' | 'local_changes' | 'success';
22
+ }
11
23
  export interface PullUseCaseOptions {
12
24
  cogitPullService: ICogitPullService;
13
25
  contextTreeSnapshotService: IContextTreeSnapshotService;
@@ -28,8 +40,10 @@ export declare class PullUseCase implements IPullUseCase {
28
40
  constructor(options: PullUseCaseOptions);
29
41
  protected checkLocalChanges(): Promise<boolean>;
30
42
  protected checkProjectInit(): Promise<BrvConfig>;
31
- run(options: {
32
- branch: string;
33
- }): Promise<void>;
34
- protected validateAuth(): Promise<AuthToken | undefined>;
43
+ run(options: PullUseCaseRunOptions): Promise<void>;
44
+ protected validateAuth(format: 'json' | 'text'): Promise<AuthToken | undefined>;
45
+ /**
46
+ * Output JSON result for headless mode.
47
+ */
48
+ private outputJsonResult;
35
49
  }
@@ -1,4 +1,5 @@
1
1
  import { WorkspaceNotInitializedError } from '../cipher/validation/workspace-validator.js';
2
+ import { HeadlessTerminal } from '../terminal/headless-terminal.js';
2
3
  export class PullUseCase {
3
4
  cogitPullService;
4
5
  contextTreeSnapshotService;
@@ -28,9 +29,10 @@ export class PullUseCase {
28
29
  return projectConfig;
29
30
  }
30
31
  async run(options) {
32
+ const format = options.format ?? 'text';
31
33
  try {
32
34
  await this.trackingService.track('mem:pull');
33
- const token = await this.validateAuth();
35
+ const token = await this.validateAuth(format);
34
36
  if (!token)
35
37
  return;
36
38
  const projectConfig = await this.checkProjectInit();
@@ -39,13 +41,20 @@ export class PullUseCase {
39
41
  const hasLocalChanges = await this.checkLocalChanges();
40
42
  this.terminal.actionStop();
41
43
  if (hasLocalChanges) {
42
- this.terminal.log('You have local changes that have not been pushed. Run "/push" first.');
44
+ if (format === 'json') {
45
+ this.outputJsonResult({
46
+ error: 'You have local changes that have not been pushed. Run push first.',
47
+ status: 'local_changes',
48
+ });
49
+ }
50
+ else {
51
+ this.terminal.log('You have local changes that have not been pushed. Run "/push" first.');
52
+ }
43
53
  return;
44
54
  }
45
55
  // Pull from CoGit
46
56
  this.terminal.log('Pulling from ByteRover...');
47
57
  const snapshot = await this.cogitPullService.pull({
48
- accessToken: token.accessToken,
49
58
  branch: options.branch,
50
59
  sessionKey: token.sessionKey,
51
60
  spaceId: projectConfig.spaceId,
@@ -57,33 +66,82 @@ export class PullUseCase {
57
66
  this.terminal.actionStop();
58
67
  // Update snapshot ONLY after successful sync
59
68
  await this.contextTreeSnapshotService.saveSnapshot();
60
- // Success message
61
- this.terminal.log('\n✓ Successfully pulled context tree from ByteRover memory storage!');
62
- this.terminal.log(` Branch: ${options.branch}`);
63
- this.terminal.log(` Commit: ${snapshot.commitSha.slice(0, 7)}`);
64
- this.terminal.log(` Added: ${syncResult.added.length}, Edited: ${syncResult.edited.length}, Deleted: ${syncResult.deleted.length}`);
69
+ if (format === 'json') {
70
+ this.outputJsonResult({
71
+ added: syncResult.added.length,
72
+ branch: options.branch,
73
+ commitSha: snapshot.commitSha,
74
+ deleted: syncResult.deleted.length,
75
+ edited: syncResult.edited.length,
76
+ status: 'success',
77
+ });
78
+ }
79
+ else {
80
+ // Success message
81
+ this.terminal.log('\n✓ Successfully pulled context tree from ByteRover memory storage!');
82
+ this.terminal.log(` Branch: ${options.branch}`);
83
+ this.terminal.log(` Commit: ${snapshot.commitSha.slice(0, 7)}`);
84
+ this.terminal.log(` Added: ${syncResult.added.length}, Edited: ${syncResult.edited.length}, Deleted: ${syncResult.deleted.length}`);
85
+ }
65
86
  }
66
87
  catch (error) {
67
88
  // Stop action if it's in progress
68
89
  this.terminal.actionStop();
69
90
  if (error instanceof WorkspaceNotInitializedError) {
70
- this.terminal.log('Project not initialized. Please run "/init" to select your team and workspace.');
91
+ if (format === 'json') {
92
+ this.outputJsonResult({ error: 'Project not initialized. Run init first.', status: 'error' });
93
+ }
94
+ else {
95
+ this.terminal.log('Project not initialized. Please run "/init" to select your team and workspace.');
96
+ }
71
97
  return;
72
98
  }
73
99
  const message = error instanceof Error ? error.message : 'Pull failed';
74
- this.terminal.error(`Failed to pull: ${message}`);
100
+ if (format === 'json') {
101
+ this.outputJsonResult({ error: message, status: 'error' });
102
+ }
103
+ else {
104
+ this.terminal.error(`Failed to pull: ${message}`);
105
+ }
75
106
  }
76
107
  }
77
- async validateAuth() {
108
+ async validateAuth(format) {
78
109
  const token = await this.tokenStore.load();
79
110
  if (token === undefined) {
80
- this.terminal.error('Not authenticated. Run "/login" first.');
111
+ if (format === 'json') {
112
+ this.outputJsonResult({ error: 'Not authenticated. Run login first.', status: 'error' });
113
+ }
114
+ else {
115
+ this.terminal.error('Not authenticated. Run "/login" first.');
116
+ }
81
117
  return undefined;
82
118
  }
83
119
  if (!token.isValid()) {
84
- this.terminal.error('Authentication token expired. Run "/login" again.');
120
+ if (format === 'json') {
121
+ this.outputJsonResult({ error: 'Authentication token expired. Run login again.', status: 'error' });
122
+ }
123
+ else {
124
+ this.terminal.error('Authentication token expired. Run "/login" again.');
125
+ }
85
126
  return undefined;
86
127
  }
87
128
  return token;
88
129
  }
130
+ /**
131
+ * Output JSON result for headless mode.
132
+ */
133
+ outputJsonResult(result) {
134
+ const response = {
135
+ command: 'pull',
136
+ data: result,
137
+ success: result.status === 'success',
138
+ timestamp: new Date().toISOString(),
139
+ };
140
+ if (this.terminal instanceof HeadlessTerminal) {
141
+ this.terminal.writeFinalResponse(response);
142
+ }
143
+ else {
144
+ this.terminal.log(JSON.stringify(response));
145
+ }
146
+ }
89
147
  }
@@ -5,7 +5,19 @@ import type { IProjectConfigStore } from '../../core/interfaces/i-project-config
5
5
  import type { ITerminal } from '../../core/interfaces/i-terminal.js';
6
6
  import type { ITokenStore } from '../../core/interfaces/i-token-store.js';
7
7
  import type { ITrackingService } from '../../core/interfaces/i-tracking-service.js';
8
- import type { IPushUseCase } from '../../core/interfaces/usecase/i-push-use-case.js';
8
+ import type { IPushUseCase, PushUseCaseRunOptions } from '../../core/interfaces/usecase/i-push-use-case.js';
9
+ /**
10
+ * Structured push result for JSON output.
11
+ */
12
+ export interface PushResult {
13
+ added?: number;
14
+ branch?: string;
15
+ deleted?: number;
16
+ edited?: number;
17
+ error?: string;
18
+ status: 'cancelled' | 'error' | 'no_changes' | 'success';
19
+ url?: string;
20
+ }
9
21
  export interface PushUseCaseOptions {
10
22
  cogitPushService: ICogitPushService;
11
23
  contextFileReader: IContextFileReader;
@@ -26,12 +38,13 @@ export declare class PushUseCase implements IPushUseCase {
26
38
  private readonly trackingService;
27
39
  private readonly webAppUrl;
28
40
  constructor(options: PushUseCaseOptions);
29
- run(options: {
30
- branch: string;
31
- skipConfirmation: boolean;
32
- }): Promise<void>;
41
+ run(options: PushUseCaseRunOptions): Promise<void>;
33
42
  private buildSpaceUrl;
34
43
  private checkProjectInit;
35
44
  private confirmPush;
45
+ /**
46
+ * Output JSON result for headless mode.
47
+ */
48
+ private outputJsonResult;
36
49
  private validateAuth;
37
50
  }
@@ -1,5 +1,6 @@
1
1
  import { WorkspaceNotInitializedError } from '../cipher/validation/workspace-validator.js';
2
2
  import { mapToPushContexts } from '../cogit/context-tree-to-push-context-mapper.js';
3
+ import { HeadlessTerminal } from '../terminal/headless-terminal.js';
3
4
  export class PushUseCase {
4
5
  cogitPushService;
5
6
  contextFileReader;
@@ -20,9 +21,10 @@ export class PushUseCase {
20
21
  this.webAppUrl = options.webAppUrl;
21
22
  }
22
23
  async run(options) {
24
+ const format = options.format ?? 'text';
23
25
  try {
24
26
  await this.trackingService.track('mem:push');
25
- const token = await this.validateAuth();
27
+ const token = await this.validateAuth(format);
26
28
  if (!token)
27
29
  return;
28
30
  const projectConfig = await this.checkProjectInit();
@@ -33,14 +35,24 @@ export class PushUseCase {
33
35
  if (contextTreeChanges.added.length === 0 &&
34
36
  contextTreeChanges.modified.length === 0 &&
35
37
  contextTreeChanges.deleted.length === 0) {
36
- this.terminal.log('No context changes to push.');
38
+ if (format === 'json') {
39
+ this.outputJsonResult({ status: 'no_changes' });
40
+ }
41
+ else {
42
+ this.terminal.log('No context changes to push.');
43
+ }
37
44
  return;
38
45
  }
39
46
  // Prompt for confirmation unless skipConfirmation is true
40
47
  if (!options.skipConfirmation) {
41
48
  const confirmed = await this.confirmPush(projectConfig, options.branch);
42
49
  if (!confirmed) {
43
- this.terminal.log('Push cancelled.');
50
+ if (format === 'json') {
51
+ this.outputJsonResult({ status: 'cancelled' });
52
+ }
53
+ else {
54
+ this.terminal.log('Push cancelled.');
55
+ }
44
56
  return;
45
57
  }
46
58
  }
@@ -57,7 +69,12 @@ export class PushUseCase {
57
69
  modifiedFiles,
58
70
  });
59
71
  if (pushContexts.length === 0) {
60
- this.terminal.log('\nNo valid context files to push.');
72
+ if (format === 'json') {
73
+ this.outputJsonResult({ status: 'no_changes' });
74
+ }
75
+ else {
76
+ this.terminal.log('\nNo valid context files to push.');
77
+ }
61
78
  return;
62
79
  }
63
80
  // Push to CoGit (with two-request SHA flow)
@@ -72,22 +89,45 @@ export class PushUseCase {
72
89
  });
73
90
  // Update snapshot ONLY after successful push
74
91
  await this.contextTreeSnapshotService.saveSnapshot();
75
- // Success message
76
- this.terminal.log('\n✓ Successfully pushed context tree to ByteRover memory storage!');
77
- this.terminal.log(` Branch: ${options.branch}`);
78
- this.terminal.log(` Added: ${addedFiles.length}, Edited: ${modifiedFiles.length}, Deleted: ${contextTreeChanges.deleted.length}`);
79
- this.terminal.log(` View: ${this.buildSpaceUrl(projectConfig.teamName, projectConfig.spaceName)}`);
92
+ const url = this.buildSpaceUrl(projectConfig.teamName, projectConfig.spaceName);
93
+ if (format === 'json') {
94
+ this.outputJsonResult({
95
+ added: addedFiles.length,
96
+ branch: options.branch,
97
+ deleted: contextTreeChanges.deleted.length,
98
+ edited: modifiedFiles.length,
99
+ status: 'success',
100
+ url,
101
+ });
102
+ }
103
+ else {
104
+ // Success message
105
+ this.terminal.log('\n✓ Successfully pushed context tree to ByteRover memory storage!');
106
+ this.terminal.log(` Branch: ${options.branch}`);
107
+ this.terminal.log(` Added: ${addedFiles.length}, Edited: ${modifiedFiles.length}, Deleted: ${contextTreeChanges.deleted.length}`);
108
+ this.terminal.log(` View: ${url}`);
109
+ }
80
110
  }
81
111
  catch (error) {
82
112
  // Stop action if it's in progress
83
113
  this.terminal.actionStop();
84
114
  if (error instanceof WorkspaceNotInitializedError) {
85
- this.terminal.log('Project not initialized. Please run "/init" to select your team and workspace.');
115
+ if (format === 'json') {
116
+ this.outputJsonResult({ error: 'Project not initialized. Run init first.', status: 'error' });
117
+ }
118
+ else {
119
+ this.terminal.log('Project not initialized. Please run "/init" to select your team and workspace.');
120
+ }
86
121
  return;
87
122
  }
88
123
  // For other errors, to properly display error before exit
89
124
  const message = error instanceof Error ? error.message : 'Push failed';
90
- this.terminal.error(`Failed to push: ${message}`);
125
+ if (format === 'json') {
126
+ this.outputJsonResult({ error: message, status: 'error' });
127
+ }
128
+ else {
129
+ this.terminal.error(`Failed to push: ${message}`);
130
+ }
91
131
  }
92
132
  }
93
133
  buildSpaceUrl(teamName, spaceName) {
@@ -109,14 +149,41 @@ export class PushUseCase {
109
149
  message: 'Push to ByteRover',
110
150
  });
111
151
  }
112
- async validateAuth() {
152
+ /**
153
+ * Output JSON result for headless mode.
154
+ */
155
+ outputJsonResult(result) {
156
+ const response = {
157
+ command: 'push',
158
+ data: result,
159
+ success: result.status === 'success',
160
+ timestamp: new Date().toISOString(),
161
+ };
162
+ if (this.terminal instanceof HeadlessTerminal) {
163
+ this.terminal.writeFinalResponse(response);
164
+ }
165
+ else {
166
+ this.terminal.log(JSON.stringify(response));
167
+ }
168
+ }
169
+ async validateAuth(format) {
113
170
  const token = await this.tokenStore.load();
114
171
  if (token === undefined) {
115
- this.terminal.error('Not authenticated. Run "/login" first.');
172
+ if (format === 'json') {
173
+ this.outputJsonResult({ error: 'Not authenticated. Run login first.', status: 'error' });
174
+ }
175
+ else {
176
+ this.terminal.error('Not authenticated. Run "/login" first.');
177
+ }
116
178
  return undefined;
117
179
  }
118
180
  if (!token.isValid()) {
119
- this.terminal.error('Authentication token expired. Run "/login" again.');
181
+ if (format === 'json') {
182
+ this.outputJsonResult({ error: 'Authentication token expired. Run login again.', status: 'error' });
183
+ }
184
+ else {
185
+ this.terminal.error('Authentication token expired. Run "/login" again.');
186
+ }
120
187
  return undefined;
121
188
  }
122
189
  return token;
@@ -4,6 +4,19 @@ import type { ITrackingService } from '../../core/interfaces/i-tracking-service.
4
4
  import type { IQueryUseCase, QueryUseCaseRunOptions } from '../../core/interfaces/usecase/i-query-use-case.js';
5
5
  import { CipherAgent } from '../cipher/agent/index.js';
6
6
  import { type TransportClientFactory } from '../transport/transport-client-factory.js';
7
+ /**
8
+ * Structured query result for JSON output.
9
+ */
10
+ export interface QueryResult {
11
+ error?: string;
12
+ result?: string;
13
+ status: 'completed' | 'error';
14
+ toolCalls?: Array<{
15
+ status: string;
16
+ summary: string;
17
+ tool: string;
18
+ }>;
19
+ }
7
20
  export type TransportClientFactoryCreator = () => TransportClientFactory;
8
21
  export interface QueryUseCaseOptions {
9
22
  terminal: ITerminal;
@@ -44,6 +57,14 @@ export declare class QueryUseCase implements IQueryUseCase {
44
57
  * Handle connection-related errors with user-friendly messages.
45
58
  */
46
59
  private handleConnectionError;
60
+ /**
61
+ * Handle connection errors with JSON output.
62
+ */
63
+ private handleConnectionErrorJson;
64
+ /**
65
+ * Output JSON result for headless mode.
66
+ */
67
+ private outputJsonResult;
47
68
  /**
48
69
  * Stream task results from the connected instance.
49
70
  */