byterover-cli 3.5.0 → 3.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/.env.production +4 -6
- package/dist/agent/core/interfaces/i-cipher-agent.d.ts +1 -0
- package/dist/agent/infra/agent/cipher-agent.d.ts +1 -0
- package/dist/agent/infra/agent/cipher-agent.js +1 -0
- package/dist/oclif/commands/curate/view.js +5 -25
- package/dist/oclif/commands/dream.d.ts +18 -0
- package/dist/oclif/commands/dream.js +230 -0
- package/dist/oclif/commands/query-log/summary.d.ts +18 -0
- package/dist/oclif/commands/query-log/summary.js +75 -0
- package/dist/oclif/commands/query-log/view.d.ts +23 -0
- package/dist/oclif/commands/query-log/view.js +95 -0
- package/dist/oclif/lib/time-filter.d.ts +10 -0
- package/dist/oclif/lib/time-filter.js +21 -0
- package/dist/server/config/environment.d.ts +10 -3
- package/dist/server/config/environment.js +34 -15
- package/dist/server/constants.d.ts +5 -0
- package/dist/server/constants.js +7 -0
- package/dist/server/core/domain/entities/query-log-entry.d.ts +61 -0
- package/dist/server/core/domain/entities/query-log-entry.js +40 -0
- package/dist/server/core/domain/transport/schemas.d.ts +108 -7
- package/dist/server/core/domain/transport/schemas.js +34 -2
- package/dist/server/core/interfaces/executor/i-query-executor.d.ts +23 -2
- package/dist/server/core/interfaces/i-terminal.d.ts +3 -0
- package/dist/server/core/interfaces/i-terminal.js +1 -0
- package/dist/server/core/interfaces/storage/i-query-log-store.d.ts +23 -0
- package/dist/server/core/interfaces/storage/i-query-log-store.js +2 -0
- package/dist/server/core/interfaces/usecase/i-query-log-summary-use-case.d.ts +44 -0
- package/dist/server/core/interfaces/usecase/i-query-log-summary-use-case.js +1 -0
- package/dist/server/core/interfaces/usecase/i-query-log-use-case.d.ts +13 -0
- package/dist/server/core/interfaces/usecase/i-query-log-use-case.js +3 -0
- package/dist/server/infra/daemon/agent-process.js +79 -9
- package/dist/server/infra/daemon/brv-server.js +74 -5
- package/dist/server/infra/dream/dream-lock-service.d.ts +37 -0
- package/dist/server/infra/dream/dream-lock-service.js +88 -0
- package/dist/server/infra/dream/dream-log-schema.d.ts +966 -0
- package/dist/server/infra/dream/dream-log-schema.js +57 -0
- package/dist/server/infra/dream/dream-log-store.d.ts +55 -0
- package/dist/server/infra/dream/dream-log-store.js +141 -0
- package/dist/server/infra/dream/dream-response-schemas.d.ts +219 -0
- package/dist/server/infra/dream/dream-response-schemas.js +38 -0
- package/dist/server/infra/dream/dream-state-schema.d.ts +67 -0
- package/dist/server/infra/dream/dream-state-schema.js +23 -0
- package/dist/server/infra/dream/dream-state-service.d.ts +38 -0
- package/dist/server/infra/dream/dream-state-service.js +91 -0
- package/dist/server/infra/dream/dream-trigger.d.ts +46 -0
- package/dist/server/infra/dream/dream-trigger.js +65 -0
- package/dist/server/infra/dream/dream-undo.d.ts +38 -0
- package/dist/server/infra/dream/dream-undo.js +293 -0
- package/dist/server/infra/dream/operations/consolidate.d.ts +52 -0
- package/dist/server/infra/dream/operations/consolidate.js +514 -0
- package/dist/server/infra/dream/operations/prune.d.ts +45 -0
- package/dist/server/infra/dream/operations/prune.js +362 -0
- package/dist/server/infra/dream/operations/synthesize.d.ts +37 -0
- package/dist/server/infra/dream/operations/synthesize.js +278 -0
- package/dist/server/infra/dream/parse-dream-response.d.ts +11 -0
- package/dist/server/infra/dream/parse-dream-response.js +35 -0
- package/dist/server/infra/executor/curate-executor.js +10 -0
- package/dist/server/infra/executor/dream-executor.d.ts +97 -0
- package/dist/server/infra/executor/dream-executor.js +431 -0
- package/dist/server/infra/executor/query-executor.d.ts +2 -2
- package/dist/server/infra/executor/query-executor.js +92 -22
- package/dist/server/infra/mcp/mcp-server.js +3 -0
- package/dist/server/infra/mcp/tools/brv-curate-tool.js +3 -7
- package/dist/server/infra/mcp/tools/brv-query-tool.js +3 -7
- package/dist/server/infra/mcp/tools/index.d.ts +1 -0
- package/dist/server/infra/mcp/tools/index.js +1 -0
- package/dist/server/infra/mcp/tools/shared-schema.d.ts +3 -0
- package/dist/server/infra/mcp/tools/shared-schema.js +17 -0
- package/dist/server/infra/process/feature-handlers.js +10 -6
- package/dist/server/infra/process/query-log-handler.d.ts +42 -0
- package/dist/server/infra/process/query-log-handler.js +150 -0
- package/dist/server/infra/process/task-router.d.ts +40 -0
- package/dist/server/infra/process/task-router.js +67 -9
- package/dist/server/infra/process/transport-handlers.d.ts +4 -0
- package/dist/server/infra/process/transport-handlers.js +1 -0
- package/dist/server/infra/storage/file-curate-log-store.js +1 -1
- package/dist/server/infra/storage/file-query-log-store.d.ts +81 -0
- package/dist/server/infra/storage/file-query-log-store.js +249 -0
- package/dist/server/infra/transport/handlers/config-handler.js +1 -1
- package/dist/server/infra/usecase/curate-log-use-case.js +7 -3
- package/dist/server/infra/usecase/query-log-summary-narrative-formatter.d.ts +15 -0
- package/dist/server/infra/usecase/query-log-summary-narrative-formatter.js +79 -0
- package/dist/server/infra/usecase/query-log-summary-use-case.d.ts +13 -0
- package/dist/server/infra/usecase/query-log-summary-use-case.js +217 -0
- package/dist/server/infra/usecase/query-log-use-case.d.ts +31 -0
- package/dist/server/infra/usecase/query-log-use-case.js +128 -0
- package/dist/server/utils/log-format-utils.d.ts +5 -0
- package/dist/server/utils/log-format-utils.js +23 -0
- package/dist/shared/transport/events/config-events.d.ts +1 -1
- package/oclif.manifest.json +439 -184
- package/package.json +1 -1
package/.env.production
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# ByteRover CLI - Production Environment Configuration
|
|
2
|
+
BRV_IAM_BASE_URL=https://iam.byterover.dev
|
|
3
|
+
BRV_COGIT_BASE_URL=https://v3-cgit.byterover.dev
|
|
4
4
|
BRV_GIT_REMOTE_BASE_URL=https://byterover.dev
|
|
5
|
-
|
|
6
|
-
BRV_LLM_API_BASE_URL=https://llm.byterover.dev
|
|
7
|
-
BRV_TOKEN_URL=https://iam.byterover.dev/api/v1/oidc/token
|
|
5
|
+
BRV_LLM_BASE_URL=https://llm.byterover.dev
|
|
8
6
|
BRV_WEB_APP_URL=https://app.byterover.dev
|
|
@@ -161,6 +161,7 @@ export declare class CipherAgent extends BaseAgent implements ICipherAgent {
|
|
|
161
161
|
*/
|
|
162
162
|
executeOnSession(sessionId: string, input: string, options?: {
|
|
163
163
|
executionContext?: ExecutionContext;
|
|
164
|
+
signal?: AbortSignal;
|
|
164
165
|
taskId?: string;
|
|
165
166
|
}): Promise<string>;
|
|
166
167
|
/**
|
|
@@ -3,28 +3,8 @@ import { resolveProject } from '../../../server/infra/project/resolve-project.js
|
|
|
3
3
|
import { FileCurateLogStore } from '../../../server/infra/storage/file-curate-log-store.js';
|
|
4
4
|
import { CurateLogUseCase } from '../../../server/infra/usecase/curate-log-use-case.js';
|
|
5
5
|
import { getProjectDataDir } from '../../../server/utils/path-utils.js';
|
|
6
|
+
import { parseTimeFilter } from '../../lib/time-filter.js';
|
|
6
7
|
const VALID_STATUSES = ['cancelled', 'completed', 'error', 'processing'];
|
|
7
|
-
const RELATIVE_TIME_PATTERN = /^(\d+)(m|h|d|w)$/;
|
|
8
|
-
/**
|
|
9
|
-
* Parse a time filter value into a UTC millisecond timestamp.
|
|
10
|
-
*
|
|
11
|
-
* Accepts:
|
|
12
|
-
* - Relative: "30m", "1h", "24h", "7d", "2w"
|
|
13
|
-
* - Absolute: ISO date "2024-01-15" or datetime "2024-01-15T12:00:00Z"
|
|
14
|
-
*
|
|
15
|
-
* Returns null when the value cannot be parsed.
|
|
16
|
-
*/
|
|
17
|
-
function parseTimeFilter(value) {
|
|
18
|
-
const relMatch = RELATIVE_TIME_PATTERN.exec(value);
|
|
19
|
-
if (relMatch) {
|
|
20
|
-
const amount = Number(relMatch[1]);
|
|
21
|
-
const unit = relMatch[2];
|
|
22
|
-
const multipliers = { d: 86_400_000, h: 3_600_000, m: 60_000, w: 604_800_000 };
|
|
23
|
-
return Date.now() - amount * multipliers[unit];
|
|
24
|
-
}
|
|
25
|
-
const ts = new Date(value).getTime();
|
|
26
|
-
return Number.isNaN(ts) ? null : ts;
|
|
27
|
-
}
|
|
28
8
|
export default class CurateView extends Command {
|
|
29
9
|
static args = {
|
|
30
10
|
id: Args.string({
|
|
@@ -47,7 +27,7 @@ export default class CurateView extends Command {
|
|
|
47
27
|
];
|
|
48
28
|
static flags = {
|
|
49
29
|
before: Flags.string({
|
|
50
|
-
description: 'Show entries started before this time (ISO date or relative: 1h, 24h, 7d, 2w)',
|
|
30
|
+
description: 'Show entries started before this time (ISO date or relative: 30m, 1h, 24h, 7d, 2w)',
|
|
51
31
|
}),
|
|
52
32
|
detail: Flags.boolean({
|
|
53
33
|
default: false,
|
|
@@ -64,7 +44,7 @@ export default class CurateView extends Command {
|
|
|
64
44
|
min: 1,
|
|
65
45
|
}),
|
|
66
46
|
since: Flags.string({
|
|
67
|
-
description: 'Show entries started after this time (ISO date or relative: 1h, 24h, 7d, 2w)',
|
|
47
|
+
description: 'Show entries started after this time (ISO date or relative: 30m, 1h, 24h, 7d, 2w)',
|
|
68
48
|
}),
|
|
69
49
|
status: Flags.string({
|
|
70
50
|
description: `Filter by status (can be repeated). Options: ${VALID_STATUSES.join(', ')}`,
|
|
@@ -96,8 +76,8 @@ export default class CurateView extends Command {
|
|
|
96
76
|
}
|
|
97
77
|
parseTime(value, flagName) {
|
|
98
78
|
const ts = parseTimeFilter(value);
|
|
99
|
-
if (ts ===
|
|
100
|
-
this.error(`Invalid time value for ${flagName}: "${value}". Use ISO date (2024-01-15) or relative (1h, 24h, 7d, 2w).`, { exit: 2 });
|
|
79
|
+
if (ts === undefined) {
|
|
80
|
+
this.error(`Invalid time value for ${flagName}: "${value}". Use ISO date (2024-01-15) or relative (30m, 1h, 24h, 7d, 2w).`, { exit: 2 });
|
|
101
81
|
}
|
|
102
82
|
return ts;
|
|
103
83
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { type DaemonClientOptions } from '../lib/daemon-client.js';
|
|
3
|
+
export default class Dream extends Command {
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
detach: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
timeout: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
undo: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
protected getDaemonClientOptions(): DaemonClientOptions;
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
private reportError;
|
|
16
|
+
private runUndo;
|
|
17
|
+
private submitTask;
|
|
18
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { BRV_DIR, CONTEXT_TREE_DIR } from '../../server/constants.js';
|
|
5
|
+
import { TransportStateEventNames } from '../../server/core/domain/transport/schemas.js';
|
|
6
|
+
import { FileContextTreeArchiveService } from '../../server/infra/context-tree/file-context-tree-archive-service.js';
|
|
7
|
+
import { FileContextTreeManifestService } from '../../server/infra/context-tree/file-context-tree-manifest-service.js';
|
|
8
|
+
import { DreamLogStore } from '../../server/infra/dream/dream-log-store.js';
|
|
9
|
+
import { DreamStateService } from '../../server/infra/dream/dream-state-service.js';
|
|
10
|
+
import { undoLastDream } from '../../server/infra/dream/dream-undo.js';
|
|
11
|
+
import { resolveProject } from '../../server/infra/project/resolve-project.js';
|
|
12
|
+
import { FileCurateLogStore } from '../../server/infra/storage/file-curate-log-store.js';
|
|
13
|
+
import { FileReviewBackupStore } from '../../server/infra/storage/file-review-backup-store.js';
|
|
14
|
+
import { getProjectDataDir } from '../../server/utils/path-utils.js';
|
|
15
|
+
import { TaskEvents } from '../../shared/transport/events/index.js';
|
|
16
|
+
import { formatConnectionError, hasLeakedHandles, providerMissingMessage, withDaemonRetry, } from '../lib/daemon-client.js';
|
|
17
|
+
import { writeJsonResponse } from '../lib/json-response.js';
|
|
18
|
+
import { DEFAULT_TIMEOUT_SECONDS, MAX_TIMEOUT_SECONDS, MIN_TIMEOUT_SECONDS, waitForTaskCompletion } from '../lib/task-client.js';
|
|
19
|
+
export default class Dream extends Command {
|
|
20
|
+
static description = 'Run background memory consolidation on the context tree';
|
|
21
|
+
static examples = [
|
|
22
|
+
'# Run dream (checks time, activity, and queue gates)',
|
|
23
|
+
'<%= config.bin %> <%= command.id %>',
|
|
24
|
+
'',
|
|
25
|
+
'# Force dream (skip time/activity/queue gates, lock still checked)',
|
|
26
|
+
'<%= config.bin %> <%= command.id %> --force',
|
|
27
|
+
'',
|
|
28
|
+
'# Revert the last dream',
|
|
29
|
+
'<%= config.bin %> <%= command.id %> --undo',
|
|
30
|
+
'',
|
|
31
|
+
'# Queue dream and exit immediately',
|
|
32
|
+
'<%= config.bin %> <%= command.id %> --detach',
|
|
33
|
+
'',
|
|
34
|
+
'# Force dream and exit immediately',
|
|
35
|
+
'<%= config.bin %> <%= command.id %> --force --detach',
|
|
36
|
+
'',
|
|
37
|
+
'# JSON output',
|
|
38
|
+
'<%= config.bin %> <%= command.id %> --format json',
|
|
39
|
+
];
|
|
40
|
+
static flags = {
|
|
41
|
+
detach: Flags.boolean({
|
|
42
|
+
default: false,
|
|
43
|
+
description: 'Queue task and exit without waiting for completion',
|
|
44
|
+
}),
|
|
45
|
+
force: Flags.boolean({
|
|
46
|
+
char: 'f',
|
|
47
|
+
default: false,
|
|
48
|
+
description: 'Skip time and activity gates (lock still checked)',
|
|
49
|
+
}),
|
|
50
|
+
format: Flags.string({
|
|
51
|
+
default: 'text',
|
|
52
|
+
description: 'Output format (text or json)',
|
|
53
|
+
options: ['text', 'json'],
|
|
54
|
+
}),
|
|
55
|
+
timeout: Flags.integer({
|
|
56
|
+
default: DEFAULT_TIMEOUT_SECONDS,
|
|
57
|
+
description: 'Maximum seconds to wait for task completion',
|
|
58
|
+
max: MAX_TIMEOUT_SECONDS,
|
|
59
|
+
min: MIN_TIMEOUT_SECONDS,
|
|
60
|
+
}),
|
|
61
|
+
undo: Flags.boolean({
|
|
62
|
+
default: false,
|
|
63
|
+
description: 'Revert the last dream',
|
|
64
|
+
}),
|
|
65
|
+
};
|
|
66
|
+
getDaemonClientOptions() {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
async run() {
|
|
70
|
+
const { flags: rawFlags } = await this.parse(Dream);
|
|
71
|
+
const format = rawFlags.format === 'json' ? 'json' : 'text';
|
|
72
|
+
if (rawFlags.undo) {
|
|
73
|
+
await this.runUndo(format);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
let providerContext;
|
|
77
|
+
try {
|
|
78
|
+
await withDaemonRetry(async (client, projectRoot, worktreeRoot) => {
|
|
79
|
+
const active = await client.requestWithAck(TransportStateEventNames.GET_PROVIDER_CONFIG);
|
|
80
|
+
providerContext = { activeModel: active.activeModel, activeProvider: active.activeProvider };
|
|
81
|
+
if (!active.activeProvider) {
|
|
82
|
+
throw new Error('No provider connected. Run "brv providers connect byterover" to use the free built-in provider, or connect another provider.');
|
|
83
|
+
}
|
|
84
|
+
if (active.providerKeyMissing) {
|
|
85
|
+
throw new Error(providerMissingMessage(active.activeProvider, active.authMethod));
|
|
86
|
+
}
|
|
87
|
+
await this.submitTask({
|
|
88
|
+
client,
|
|
89
|
+
detach: rawFlags.detach,
|
|
90
|
+
force: rawFlags.force,
|
|
91
|
+
format,
|
|
92
|
+
projectRoot,
|
|
93
|
+
timeout: rawFlags.timeout ?? DEFAULT_TIMEOUT_SECONDS,
|
|
94
|
+
worktreeRoot,
|
|
95
|
+
});
|
|
96
|
+
}, {
|
|
97
|
+
...this.getDaemonClientOptions(),
|
|
98
|
+
onRetry: format === 'text'
|
|
99
|
+
? (attempt, maxRetries) => this.log(`\nConnection lost. Restarting daemon... (attempt ${attempt}/${maxRetries})`)
|
|
100
|
+
: undefined,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.reportError(error, format, providerContext);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
reportError(error, format, providerContext) {
|
|
108
|
+
const errorMessage = error instanceof Error ? error.message : 'Dream failed';
|
|
109
|
+
if (format === 'json') {
|
|
110
|
+
writeJsonResponse({ command: 'dream', data: { error: errorMessage, status: 'error' }, success: false });
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.log(formatConnectionError(error, providerContext));
|
|
114
|
+
}
|
|
115
|
+
if (hasLeakedHandles(error)) {
|
|
116
|
+
// eslint-disable-next-line n/no-process-exit, unicorn/no-process-exit
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async runUndo(format) {
|
|
121
|
+
const projectRoot = resolveProject()?.projectRoot ?? process.cwd();
|
|
122
|
+
const brvDir = join(projectRoot, BRV_DIR);
|
|
123
|
+
const contextTreeDir = join(brvDir, CONTEXT_TREE_DIR);
|
|
124
|
+
const projectDataDir = getProjectDataDir(projectRoot);
|
|
125
|
+
try {
|
|
126
|
+
const result = await undoLastDream({
|
|
127
|
+
archiveService: new FileContextTreeArchiveService(),
|
|
128
|
+
contextTreeDir,
|
|
129
|
+
curateLogStore: new FileCurateLogStore({ baseDir: projectDataDir }),
|
|
130
|
+
dreamLogStore: new DreamLogStore({ baseDir: brvDir }),
|
|
131
|
+
dreamStateService: new DreamStateService({ baseDir: brvDir }),
|
|
132
|
+
manifestService: new FileContextTreeManifestService({ baseDirectory: projectRoot }),
|
|
133
|
+
projectRoot,
|
|
134
|
+
reviewBackupStore: new FileReviewBackupStore(brvDir),
|
|
135
|
+
});
|
|
136
|
+
if (format === 'json') {
|
|
137
|
+
writeJsonResponse({ command: 'dream', data: { ...result, status: 'undone' }, success: true });
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.log(`Undone dream ${result.dreamId}`);
|
|
141
|
+
this.log(` Restored: ${result.restoredFiles.length} files`);
|
|
142
|
+
this.log(` Deleted: ${result.deletedFiles.length} files`);
|
|
143
|
+
this.log(` Restored archives: ${result.restoredArchives.length} files`);
|
|
144
|
+
if (result.errors.length > 0) {
|
|
145
|
+
this.log(` Errors: ${result.errors.length}`);
|
|
146
|
+
for (const e of result.errors) {
|
|
147
|
+
this.log(` - ${e}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
const message = error instanceof Error ? error.message : 'Undo failed';
|
|
154
|
+
if (format === 'json') {
|
|
155
|
+
writeJsonResponse({ command: 'dream', data: { error: message, status: 'error' }, success: false });
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
this.log(`Undo failed: ${message}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async submitTask(props) {
|
|
163
|
+
const { client, detach, force, format, projectRoot, timeout, worktreeRoot } = props;
|
|
164
|
+
const taskId = randomUUID();
|
|
165
|
+
const taskPayload = {
|
|
166
|
+
content: force ? 'Memory consolidation (force)' : 'Memory consolidation',
|
|
167
|
+
...(force ? { force: true } : {}),
|
|
168
|
+
...(projectRoot ? { projectPath: projectRoot } : {}),
|
|
169
|
+
taskId,
|
|
170
|
+
type: 'dream',
|
|
171
|
+
...(worktreeRoot ? { worktreeRoot } : {}),
|
|
172
|
+
};
|
|
173
|
+
if (detach) {
|
|
174
|
+
if (timeout !== DEFAULT_TIMEOUT_SECONDS && format !== 'json') {
|
|
175
|
+
this.log('Note: --timeout has no effect with --detach');
|
|
176
|
+
}
|
|
177
|
+
const ack = await client.requestWithAck(TaskEvents.CREATE, taskPayload);
|
|
178
|
+
const { logId } = ack;
|
|
179
|
+
if (format === 'json') {
|
|
180
|
+
writeJsonResponse({
|
|
181
|
+
command: 'dream',
|
|
182
|
+
data: { logId, message: 'Dream queued for processing', status: 'queued', taskId },
|
|
183
|
+
success: true,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const logSuffix = logId ? ` (Log: ${logId})` : '';
|
|
188
|
+
this.log(`✓ Dream queued for processing.${logSuffix}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
const completionPromise = waitForTaskCompletion({
|
|
193
|
+
client,
|
|
194
|
+
command: 'dream',
|
|
195
|
+
format,
|
|
196
|
+
onCompleted: ({ logId, result, taskId: tid }) => {
|
|
197
|
+
const skipped = result?.startsWith('Dream skipped:');
|
|
198
|
+
if (format === 'json') {
|
|
199
|
+
writeJsonResponse({
|
|
200
|
+
command: 'dream',
|
|
201
|
+
data: skipped
|
|
202
|
+
? { reason: result, status: 'skipped', taskId: tid }
|
|
203
|
+
: { logId, result, status: 'completed', taskId: tid },
|
|
204
|
+
success: true,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
this.log(result ?? '');
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
onError: ({ error }) => {
|
|
212
|
+
if (format === 'json') {
|
|
213
|
+
writeJsonResponse({
|
|
214
|
+
command: 'dream',
|
|
215
|
+
data: { event: 'error', message: error.message, status: 'error' },
|
|
216
|
+
success: false,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
this.log(`Dream failed: ${error.message}`);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
taskId,
|
|
224
|
+
timeoutMs: timeout * 1000,
|
|
225
|
+
}, (msg) => this.log(msg));
|
|
226
|
+
await client.requestWithAck(TaskEvents.CREATE, taskPayload);
|
|
227
|
+
await completionPromise;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { QueryLogSummaryUseCase } from '../../../server/infra/usecase/query-log-summary-use-case.js';
|
|
3
|
+
export default class QueryLogSummary extends Command {
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
before: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
last: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
since: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
protected createDependencies(baseDir: string): {
|
|
13
|
+
useCase: QueryLogSummaryUseCase;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
private parseTime;
|
|
17
|
+
private resolveAfter;
|
|
18
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { resolveProject } from '../../../server/infra/project/resolve-project.js';
|
|
3
|
+
import { FileQueryLogStore } from '../../../server/infra/storage/file-query-log-store.js';
|
|
4
|
+
import { QueryLogSummaryUseCase } from '../../../server/infra/usecase/query-log-summary-use-case.js';
|
|
5
|
+
import { getProjectDataDir } from '../../../server/utils/path-utils.js';
|
|
6
|
+
import { parseTimeFilter } from '../../lib/time-filter.js';
|
|
7
|
+
const DEFAULT_WINDOW_MS = 24 * 60 * 60 * 1000; // last 24h
|
|
8
|
+
const FORMAT_OPTIONS = ['text', 'json', 'narrative'];
|
|
9
|
+
export default class QueryLogSummary extends Command {
|
|
10
|
+
static description = 'View aggregated query recall metrics (coverage, cache hit rate, top topics)';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> <%= command.id %>',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> --last 24h',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> --last 7d',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> --last 30d',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> --format json',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> --format narrative',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> --since 2026-04-01 --before 2026-04-03',
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
before: Flags.string({
|
|
22
|
+
description: 'Entries before (ISO date or relative: 1h, 24h, 7d, 2w)',
|
|
23
|
+
}),
|
|
24
|
+
format: Flags.string({
|
|
25
|
+
default: 'text',
|
|
26
|
+
description: 'Output format',
|
|
27
|
+
options: FORMAT_OPTIONS,
|
|
28
|
+
}),
|
|
29
|
+
last: Flags.string({
|
|
30
|
+
description: 'Relative time window (e.g., 1h, 24h, 7d, 30d). Default: 24h. Takes precedence over --since.',
|
|
31
|
+
}),
|
|
32
|
+
since: Flags.string({
|
|
33
|
+
description: 'Entries after (ISO date or relative: 1h, 24h, 7d, 2w)',
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
createDependencies(baseDir) {
|
|
37
|
+
const store = new FileQueryLogStore({ baseDir });
|
|
38
|
+
const useCase = new QueryLogSummaryUseCase({
|
|
39
|
+
queryLogStore: store,
|
|
40
|
+
terminal: { log: (m) => this.log(m ?? '') },
|
|
41
|
+
});
|
|
42
|
+
return { useCase };
|
|
43
|
+
}
|
|
44
|
+
async run() {
|
|
45
|
+
const { flags } = await this.parse(QueryLogSummary);
|
|
46
|
+
const after = this.resolveAfter(flags);
|
|
47
|
+
const before = flags.before ? this.parseTime(flags.before, '--before') : undefined;
|
|
48
|
+
const format = flags.format === 'json' || flags.format === 'narrative' ? flags.format : 'text';
|
|
49
|
+
const projectRoot = resolveProject()?.projectRoot ?? process.cwd();
|
|
50
|
+
const baseDir = getProjectDataDir(projectRoot);
|
|
51
|
+
const { useCase } = this.createDependencies(baseDir);
|
|
52
|
+
await useCase.run({ after, before, format });
|
|
53
|
+
}
|
|
54
|
+
parseTime(value, flagName) {
|
|
55
|
+
const ts = parseTimeFilter(value);
|
|
56
|
+
if (ts === undefined) {
|
|
57
|
+
this.error(`Invalid time value for ${flagName}: "${value}". Use ISO date (2024-01-15) or relative (30m, 1h, 24h, 7d, 2w).`, { exit: 2 });
|
|
58
|
+
}
|
|
59
|
+
return ts;
|
|
60
|
+
}
|
|
61
|
+
resolveAfter(flags) {
|
|
62
|
+
if (flags.last) {
|
|
63
|
+
return this.parseTime(flags.last, '--last');
|
|
64
|
+
}
|
|
65
|
+
if (flags.since) {
|
|
66
|
+
return this.parseTime(flags.since, '--since');
|
|
67
|
+
}
|
|
68
|
+
// Default to last 24h only when NO time flag is provided at all.
|
|
69
|
+
// If only --before is given, leave after undefined (all time before X).
|
|
70
|
+
if (!flags.before) {
|
|
71
|
+
return Date.now() - DEFAULT_WINDOW_MS;
|
|
72
|
+
}
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { QueryLogUseCase } from '../../../server/infra/usecase/query-log-use-case.js';
|
|
3
|
+
export default class QueryLogView extends Command {
|
|
4
|
+
static args: {
|
|
5
|
+
id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static flags: {
|
|
10
|
+
before: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
detail: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
since: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
status: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
tier: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
};
|
|
18
|
+
protected createDependencies(baseDir: string): {
|
|
19
|
+
useCase: QueryLogUseCase;
|
|
20
|
+
};
|
|
21
|
+
run(): Promise<void>;
|
|
22
|
+
private parseTime;
|
|
23
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Args, Command, Flags } from '@oclif/core';
|
|
2
|
+
import { QUERY_LOG_STATUSES, QUERY_LOG_TIERS } from '../../../server/core/domain/entities/query-log-entry.js';
|
|
3
|
+
import { resolveProject } from '../../../server/infra/project/resolve-project.js';
|
|
4
|
+
import { FileQueryLogStore } from '../../../server/infra/storage/file-query-log-store.js';
|
|
5
|
+
import { QueryLogUseCase } from '../../../server/infra/usecase/query-log-use-case.js';
|
|
6
|
+
import { getProjectDataDir } from '../../../server/utils/path-utils.js';
|
|
7
|
+
import { parseTimeFilter } from '../../lib/time-filter.js';
|
|
8
|
+
export default class QueryLogView extends Command {
|
|
9
|
+
static args = {
|
|
10
|
+
id: Args.string({
|
|
11
|
+
description: 'Query log entry ID to view in detail',
|
|
12
|
+
required: false,
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
static description = 'View query log history';
|
|
16
|
+
static examples = [
|
|
17
|
+
'<%= config.bin %> <%= command.id %>',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> qry-1712345678901',
|
|
19
|
+
'<%= config.bin %> <%= command.id %> --limit 20',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> --status completed',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> --status completed --status error',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> --tier 0 --tier 1',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> --since 1h',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> --since 2024-01-15',
|
|
25
|
+
'<%= config.bin %> <%= command.id %> --before 2024-02-01',
|
|
26
|
+
'<%= config.bin %> <%= command.id %> --detail',
|
|
27
|
+
'<%= config.bin %> <%= command.id %> --format json',
|
|
28
|
+
];
|
|
29
|
+
static flags = {
|
|
30
|
+
before: Flags.string({
|
|
31
|
+
description: 'Show entries started before this time (ISO date or relative: 30m, 1h, 24h, 7d, 2w)',
|
|
32
|
+
}),
|
|
33
|
+
detail: Flags.boolean({
|
|
34
|
+
default: false,
|
|
35
|
+
description: 'Show matched docs for each entry',
|
|
36
|
+
}),
|
|
37
|
+
format: Flags.string({
|
|
38
|
+
default: 'text',
|
|
39
|
+
description: 'Output format',
|
|
40
|
+
options: ['text', 'json'],
|
|
41
|
+
}),
|
|
42
|
+
limit: Flags.integer({
|
|
43
|
+
default: 10,
|
|
44
|
+
description: 'Maximum number of log entries to display',
|
|
45
|
+
min: 1,
|
|
46
|
+
}),
|
|
47
|
+
since: Flags.string({
|
|
48
|
+
description: 'Show entries started after this time (ISO date or relative: 30m, 1h, 24h, 7d, 2w)',
|
|
49
|
+
}),
|
|
50
|
+
status: Flags.string({
|
|
51
|
+
description: `Filter by status (can be repeated). Options: ${QUERY_LOG_STATUSES.join(', ')}`,
|
|
52
|
+
multiple: true,
|
|
53
|
+
options: QUERY_LOG_STATUSES,
|
|
54
|
+
}),
|
|
55
|
+
tier: Flags.string({
|
|
56
|
+
description: `Filter by resolution tier (can be repeated). Options: ${QUERY_LOG_TIERS.join(', ')}`,
|
|
57
|
+
multiple: true,
|
|
58
|
+
options: QUERY_LOG_TIERS.map(String),
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
61
|
+
createDependencies(baseDir) {
|
|
62
|
+
const store = new FileQueryLogStore({ baseDir });
|
|
63
|
+
const useCase = new QueryLogUseCase({
|
|
64
|
+
queryLogStore: store,
|
|
65
|
+
terminal: { log: (m) => this.log(m ?? '') },
|
|
66
|
+
});
|
|
67
|
+
return { useCase };
|
|
68
|
+
}
|
|
69
|
+
async run() {
|
|
70
|
+
const { args, flags } = await this.parse(QueryLogView);
|
|
71
|
+
const after = flags.since ? this.parseTime(flags.since, '--since') : undefined;
|
|
72
|
+
const before = flags.before ? this.parseTime(flags.before, '--before') : undefined;
|
|
73
|
+
const format = flags.format === 'json' ? 'json' : 'text';
|
|
74
|
+
const projectRoot = resolveProject()?.projectRoot ?? process.cwd();
|
|
75
|
+
const baseDir = getProjectDataDir(projectRoot);
|
|
76
|
+
const { useCase } = this.createDependencies(baseDir);
|
|
77
|
+
await useCase.run({
|
|
78
|
+
after,
|
|
79
|
+
before,
|
|
80
|
+
detail: flags.detail,
|
|
81
|
+
format,
|
|
82
|
+
id: args.id,
|
|
83
|
+
limit: flags.limit,
|
|
84
|
+
status: flags.status?.filter((s) => QUERY_LOG_STATUSES.includes(s)),
|
|
85
|
+
tier: flags.tier?.map(Number).filter((t) => QUERY_LOG_TIERS.includes(t)),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
parseTime(value, flagName) {
|
|
89
|
+
const ts = parseTimeFilter(value);
|
|
90
|
+
if (ts === undefined) {
|
|
91
|
+
this.error(`Invalid time value for ${flagName}: "${value}". Use ISO date (2024-01-15) or relative (30m, 1h, 24h, 7d, 2w).`, { exit: 2 });
|
|
92
|
+
}
|
|
93
|
+
return ts;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a time filter value into a UTC millisecond timestamp.
|
|
3
|
+
*
|
|
4
|
+
* Accepts:
|
|
5
|
+
* - Relative: "30m", "1h", "24h", "7d", "2w"
|
|
6
|
+
* - Absolute: ISO date "2024-01-15" or datetime "2024-01-15T12:00:00Z"
|
|
7
|
+
*
|
|
8
|
+
* Returns undefined when the value cannot be parsed.
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseTimeFilter(value: string): number | undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const RELATIVE_TIME_PATTERN = /^(\d+)(m|h|d|w)$/;
|
|
2
|
+
/**
|
|
3
|
+
* Parse a time filter value into a UTC millisecond timestamp.
|
|
4
|
+
*
|
|
5
|
+
* Accepts:
|
|
6
|
+
* - Relative: "30m", "1h", "24h", "7d", "2w"
|
|
7
|
+
* - Absolute: ISO date "2024-01-15" or datetime "2024-01-15T12:00:00Z"
|
|
8
|
+
*
|
|
9
|
+
* Returns undefined when the value cannot be parsed.
|
|
10
|
+
*/
|
|
11
|
+
export function parseTimeFilter(value) {
|
|
12
|
+
const relMatch = RELATIVE_TIME_PATTERN.exec(value);
|
|
13
|
+
if (relMatch) {
|
|
14
|
+
const amount = Number(relMatch[1]);
|
|
15
|
+
const unit = relMatch[2];
|
|
16
|
+
const multipliers = { d: 86_400_000, h: 3_600_000, m: 60_000, w: 604_800_000 };
|
|
17
|
+
return Date.now() - amount * multipliers[unit];
|
|
18
|
+
}
|
|
19
|
+
const ts = new Date(value).getTime();
|
|
20
|
+
return Number.isNaN(ts) ? undefined : ts;
|
|
21
|
+
}
|
|
@@ -5,16 +5,23 @@ type Environment = 'development' | 'production';
|
|
|
5
5
|
export declare const ENVIRONMENT: Environment;
|
|
6
6
|
/**
|
|
7
7
|
* Environment-specific configuration.
|
|
8
|
+
*
|
|
9
|
+
* Base URL vars (BRV_IAM_BASE_URL, BRV_COGIT_BASE_URL, BRV_LLM_BASE_URL)
|
|
10
|
+
* store only the root domain (e.g., http://localhost:8080).
|
|
11
|
+
*
|
|
12
|
+
* NOTE: The OIDC sub-path (/api/v1/oidc) is intentionally baked into the
|
|
13
|
+
* derived OIDC URLs below because it is a fixed, auth-specific structure
|
|
14
|
+
* that does not follow the general "API version at point of use" pattern.
|
|
8
15
|
*/
|
|
9
16
|
type EnvironmentConfig = {
|
|
10
|
-
apiBaseUrl: string;
|
|
11
17
|
authorizationUrl: string;
|
|
12
18
|
clientId: string;
|
|
13
|
-
|
|
19
|
+
cogitBaseUrl: string;
|
|
14
20
|
gitRemoteBaseUrl: string;
|
|
15
21
|
hubRegistryUrl: string;
|
|
22
|
+
iamBaseUrl: string;
|
|
16
23
|
issuerUrl: string;
|
|
17
|
-
|
|
24
|
+
llmBaseUrl: string;
|
|
18
25
|
scopes: string[];
|
|
19
26
|
tokenUrl: string;
|
|
20
27
|
webAppUrl: string;
|