byterover-cli 0.1.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 (193) hide show
  1. package/README.md +781 -0
  2. package/bin/dev.cmd +4 -0
  3. package/bin/dev.js +7 -0
  4. package/bin/run.cmd +4 -0
  5. package/bin/run.js +7 -0
  6. package/dist/commands/add.d.ts +60 -0
  7. package/dist/commands/add.js +230 -0
  8. package/dist/commands/clear.d.ts +13 -0
  9. package/dist/commands/clear.js +57 -0
  10. package/dist/commands/complete.d.ts +108 -0
  11. package/dist/commands/complete.js +340 -0
  12. package/dist/commands/gen-rules.d.ts +26 -0
  13. package/dist/commands/gen-rules.js +89 -0
  14. package/dist/commands/init.d.ts +24 -0
  15. package/dist/commands/init.js +135 -0
  16. package/dist/commands/login.d.ts +22 -0
  17. package/dist/commands/login.js +103 -0
  18. package/dist/commands/push.d.ts +33 -0
  19. package/dist/commands/push.js +150 -0
  20. package/dist/commands/retrieve.d.ts +26 -0
  21. package/dist/commands/retrieve.js +101 -0
  22. package/dist/commands/space/list.d.ts +22 -0
  23. package/dist/commands/space/list.js +105 -0
  24. package/dist/commands/space/switch.d.ts +20 -0
  25. package/dist/commands/space/switch.js +110 -0
  26. package/dist/commands/status.d.ts +22 -0
  27. package/dist/commands/status.js +116 -0
  28. package/dist/config/auth.config.d.ts +32 -0
  29. package/dist/config/auth.config.js +35 -0
  30. package/dist/config/environment.d.ts +35 -0
  31. package/dist/config/environment.js +39 -0
  32. package/dist/constants.d.ts +11 -0
  33. package/dist/constants.js +12 -0
  34. package/dist/core/domain/entities/agent.d.ts +5 -0
  35. package/dist/core/domain/entities/agent.js +23 -0
  36. package/dist/core/domain/entities/auth-token.d.ts +43 -0
  37. package/dist/core/domain/entities/auth-token.js +70 -0
  38. package/dist/core/domain/entities/br-config.d.ts +25 -0
  39. package/dist/core/domain/entities/br-config.js +58 -0
  40. package/dist/core/domain/entities/bullet.d.ts +51 -0
  41. package/dist/core/domain/entities/bullet.js +94 -0
  42. package/dist/core/domain/entities/curator-output.d.ts +14 -0
  43. package/dist/core/domain/entities/curator-output.js +23 -0
  44. package/dist/core/domain/entities/delta-batch.d.ts +30 -0
  45. package/dist/core/domain/entities/delta-batch.js +52 -0
  46. package/dist/core/domain/entities/delta-operation.d.ts +31 -0
  47. package/dist/core/domain/entities/delta-operation.js +50 -0
  48. package/dist/core/domain/entities/event.d.ts +8 -0
  49. package/dist/core/domain/entities/event.js +15 -0
  50. package/dist/core/domain/entities/executor-output.d.ts +27 -0
  51. package/dist/core/domain/entities/executor-output.js +33 -0
  52. package/dist/core/domain/entities/memory.d.ts +55 -0
  53. package/dist/core/domain/entities/memory.js +90 -0
  54. package/dist/core/domain/entities/oauth-token-data.d.ts +13 -0
  55. package/dist/core/domain/entities/oauth-token-data.js +20 -0
  56. package/dist/core/domain/entities/playbook.d.ts +97 -0
  57. package/dist/core/domain/entities/playbook.js +275 -0
  58. package/dist/core/domain/entities/presigned-url.d.ts +9 -0
  59. package/dist/core/domain/entities/presigned-url.js +18 -0
  60. package/dist/core/domain/entities/presigned-urls-response.d.ts +10 -0
  61. package/dist/core/domain/entities/presigned-urls-response.js +18 -0
  62. package/dist/core/domain/entities/reflector-output.d.ts +38 -0
  63. package/dist/core/domain/entities/reflector-output.js +44 -0
  64. package/dist/core/domain/entities/retrieve-result.d.ts +35 -0
  65. package/dist/core/domain/entities/retrieve-result.js +35 -0
  66. package/dist/core/domain/entities/space.d.ts +24 -0
  67. package/dist/core/domain/entities/space.js +52 -0
  68. package/dist/core/domain/entities/team.d.ts +42 -0
  69. package/dist/core/domain/entities/team.js +89 -0
  70. package/dist/core/domain/entities/user.d.ts +20 -0
  71. package/dist/core/domain/entities/user.js +32 -0
  72. package/dist/core/domain/errors/ace-error.d.ts +34 -0
  73. package/dist/core/domain/errors/ace-error.js +53 -0
  74. package/dist/core/domain/errors/auth-error.d.ts +10 -0
  75. package/dist/core/domain/errors/auth-error.js +20 -0
  76. package/dist/core/domain/errors/discovery-error.d.ts +21 -0
  77. package/dist/core/domain/errors/discovery-error.js +33 -0
  78. package/dist/core/domain/errors/rule-error.d.ts +6 -0
  79. package/dist/core/domain/errors/rule-error.js +12 -0
  80. package/dist/core/interfaces/i-ace-prompt-builder.d.ts +48 -0
  81. package/dist/core/interfaces/i-ace-prompt-builder.js +1 -0
  82. package/dist/core/interfaces/i-auth-service.d.ts +35 -0
  83. package/dist/core/interfaces/i-auth-service.js +1 -0
  84. package/dist/core/interfaces/i-browser-launcher.d.ts +11 -0
  85. package/dist/core/interfaces/i-browser-launcher.js +1 -0
  86. package/dist/core/interfaces/i-bullet-content-store.d.ts +36 -0
  87. package/dist/core/interfaces/i-bullet-content-store.js +1 -0
  88. package/dist/core/interfaces/i-callback-handler.d.ts +35 -0
  89. package/dist/core/interfaces/i-callback-handler.js +1 -0
  90. package/dist/core/interfaces/i-delta-store.d.ts +15 -0
  91. package/dist/core/interfaces/i-delta-store.js +1 -0
  92. package/dist/core/interfaces/i-executor-output-store.d.ts +14 -0
  93. package/dist/core/interfaces/i-executor-output-store.js +1 -0
  94. package/dist/core/interfaces/i-file-service.d.ts +34 -0
  95. package/dist/core/interfaces/i-file-service.js +1 -0
  96. package/dist/core/interfaces/i-http-client.d.ts +33 -0
  97. package/dist/core/interfaces/i-http-client.js +1 -0
  98. package/dist/core/interfaces/i-memory-retrieval-service.d.ts +40 -0
  99. package/dist/core/interfaces/i-memory-retrieval-service.js +1 -0
  100. package/dist/core/interfaces/i-memory-storage-service.d.ts +55 -0
  101. package/dist/core/interfaces/i-memory-storage-service.js +1 -0
  102. package/dist/core/interfaces/i-oidc-discovery-service.d.ts +20 -0
  103. package/dist/core/interfaces/i-oidc-discovery-service.js +1 -0
  104. package/dist/core/interfaces/i-playbook-service.d.ts +69 -0
  105. package/dist/core/interfaces/i-playbook-service.js +1 -0
  106. package/dist/core/interfaces/i-playbook-store.d.ts +38 -0
  107. package/dist/core/interfaces/i-playbook-store.js +1 -0
  108. package/dist/core/interfaces/i-project-config-store.d.ts +26 -0
  109. package/dist/core/interfaces/i-project-config-store.js +1 -0
  110. package/dist/core/interfaces/i-reflection-store.d.ts +21 -0
  111. package/dist/core/interfaces/i-reflection-store.js +1 -0
  112. package/dist/core/interfaces/i-rule-template-service.d.ts +17 -0
  113. package/dist/core/interfaces/i-rule-template-service.js +4 -0
  114. package/dist/core/interfaces/i-rule-writer-service.d.ts +13 -0
  115. package/dist/core/interfaces/i-rule-writer-service.js +1 -0
  116. package/dist/core/interfaces/i-space-service.d.ts +28 -0
  117. package/dist/core/interfaces/i-space-service.js +1 -0
  118. package/dist/core/interfaces/i-team-service.d.ts +29 -0
  119. package/dist/core/interfaces/i-team-service.js +1 -0
  120. package/dist/core/interfaces/i-template-loader.d.ts +29 -0
  121. package/dist/core/interfaces/i-template-loader.js +1 -0
  122. package/dist/core/interfaces/i-token-store.d.ts +22 -0
  123. package/dist/core/interfaces/i-token-store.js +1 -0
  124. package/dist/core/interfaces/i-tracking-service.d.ts +21 -0
  125. package/dist/core/interfaces/i-tracking-service.js +1 -0
  126. package/dist/core/interfaces/i-user-service.d.ts +14 -0
  127. package/dist/core/interfaces/i-user-service.js +1 -0
  128. package/dist/index.d.ts +1 -0
  129. package/dist/index.js +1 -0
  130. package/dist/infra/ace/ace-file-utils.d.ts +46 -0
  131. package/dist/infra/ace/ace-file-utils.js +83 -0
  132. package/dist/infra/ace/ace-prompt-templates.d.ts +13 -0
  133. package/dist/infra/ace/ace-prompt-templates.js +177 -0
  134. package/dist/infra/ace/file-bullet-content-store.d.ts +27 -0
  135. package/dist/infra/ace/file-bullet-content-store.js +89 -0
  136. package/dist/infra/ace/file-delta-store.d.ts +9 -0
  137. package/dist/infra/ace/file-delta-store.js +26 -0
  138. package/dist/infra/ace/file-executor-output-store.d.ts +9 -0
  139. package/dist/infra/ace/file-executor-output-store.js +26 -0
  140. package/dist/infra/ace/file-playbook-store.d.ts +29 -0
  141. package/dist/infra/ace/file-playbook-store.js +107 -0
  142. package/dist/infra/ace/file-reflection-store.d.ts +10 -0
  143. package/dist/infra/ace/file-reflection-store.js +55 -0
  144. package/dist/infra/auth/oauth-service.d.ts +49 -0
  145. package/dist/infra/auth/oauth-service.js +126 -0
  146. package/dist/infra/auth/oidc-discovery-service.d.ts +51 -0
  147. package/dist/infra/auth/oidc-discovery-service.js +145 -0
  148. package/dist/infra/browser/system-browser-launcher.d.ts +10 -0
  149. package/dist/infra/browser/system-browser-launcher.js +18 -0
  150. package/dist/infra/config/file-config-store.d.ts +21 -0
  151. package/dist/infra/config/file-config-store.js +57 -0
  152. package/dist/infra/file/fs-file-service.d.ts +28 -0
  153. package/dist/infra/file/fs-file-service.js +57 -0
  154. package/dist/infra/http/authenticated-http-client.d.ts +46 -0
  155. package/dist/infra/http/authenticated-http-client.js +99 -0
  156. package/dist/infra/http/callback-handler.d.ts +13 -0
  157. package/dist/infra/http/callback-handler.js +24 -0
  158. package/dist/infra/http/callback-server.d.ts +18 -0
  159. package/dist/infra/http/callback-server.js +93 -0
  160. package/dist/infra/memory/http-memory-retrieval-service.d.ts +18 -0
  161. package/dist/infra/memory/http-memory-retrieval-service.js +63 -0
  162. package/dist/infra/memory/http-memory-storage-service.d.ts +18 -0
  163. package/dist/infra/memory/http-memory-storage-service.js +67 -0
  164. package/dist/infra/memory/memory-to-playbook-mapper.d.ts +33 -0
  165. package/dist/infra/memory/memory-to-playbook-mapper.js +51 -0
  166. package/dist/infra/playbook/file-playbook-service.d.ts +43 -0
  167. package/dist/infra/playbook/file-playbook-service.js +133 -0
  168. package/dist/infra/rule/agent-rule-config.d.ts +19 -0
  169. package/dist/infra/rule/agent-rule-config.js +77 -0
  170. package/dist/infra/rule/rule-template-service.d.ts +18 -0
  171. package/dist/infra/rule/rule-template-service.js +80 -0
  172. package/dist/infra/rule/rule-writer-service.d.ts +19 -0
  173. package/dist/infra/rule/rule-writer-service.js +43 -0
  174. package/dist/infra/space/http-space-service.d.ts +20 -0
  175. package/dist/infra/space/http-space-service.js +67 -0
  176. package/dist/infra/storage/keychain-token-store.d.ts +10 -0
  177. package/dist/infra/storage/keychain-token-store.js +40 -0
  178. package/dist/infra/team/http-team-service.d.ts +21 -0
  179. package/dist/infra/team/http-team-service.js +71 -0
  180. package/dist/infra/template/fs-template-loader.d.ts +33 -0
  181. package/dist/infra/template/fs-template-loader.js +62 -0
  182. package/dist/infra/tracking/mixpanel-tracking-service.d.ts +14 -0
  183. package/dist/infra/tracking/mixpanel-tracking-service.js +44 -0
  184. package/dist/infra/user/http-user-service.d.ts +12 -0
  185. package/dist/infra/user/http-user-service.js +26 -0
  186. package/dist/templates/README.md +103 -0
  187. package/dist/templates/base.md +3 -0
  188. package/dist/templates/sections/command-reference.md +141 -0
  189. package/dist/templates/sections/workflow.md +46 -0
  190. package/dist/utils/file-helpers.d.ts +15 -0
  191. package/dist/utils/file-helpers.js +45 -0
  192. package/oclif.manifest.json +476 -0
  193. package/package.json +82 -0
@@ -0,0 +1,133 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { ACE_DIR, BR_DIR, DELTAS_DIR, EXECUTOR_OUTPUTS_DIR, REFLECTIONS_DIR } from '../../constants.js';
4
+ import { Playbook } from '../../core/domain/entities/playbook.js';
5
+ import { FilePlaybookStore } from '../ace/file-playbook-store.js';
6
+ /**
7
+ * File-based implementation of IPlaybookService.
8
+ * Provides high-level playbook operations including initialization,
9
+ * bullet management, delta application, and reflection tag processing.
10
+ */
11
+ export class FilePlaybookService {
12
+ static BULLETS_DIR = 'bullets';
13
+ static SUBDIRS = [REFLECTIONS_DIR, EXECUTOR_OUTPUTS_DIR, DELTAS_DIR, FilePlaybookService.BULLETS_DIR];
14
+ config;
15
+ playbookStore;
16
+ constructor(config = {}, playbookStore = new FilePlaybookStore()) {
17
+ this.config = config;
18
+ this.playbookStore = playbookStore;
19
+ }
20
+ async addOrUpdateBullet(params) {
21
+ // Validate input
22
+ if (!params.section || params.section.trim().length === 0) {
23
+ throw new Error('Section is required');
24
+ }
25
+ if (!params.content || params.content.trim().length === 0) {
26
+ throw new Error('Content is required');
27
+ }
28
+ const directory = params.directory ?? this.config.baseDirectory;
29
+ // Load existing playbook or create new one
30
+ let playbook = await this.playbookStore.load(directory);
31
+ if (!playbook) {
32
+ playbook = new Playbook();
33
+ }
34
+ let bullet;
35
+ // Determine operation type based on bulletId presence
36
+ if (params.bulletId) {
37
+ // UPDATE operation
38
+ const existingBullet = playbook.getBullet(params.bulletId);
39
+ if (!existingBullet) {
40
+ throw new Error(`Bullet with ID '${params.bulletId}' not found`);
41
+ }
42
+ const updatedBullet = playbook.updateBullet(params.bulletId, {
43
+ content: params.content,
44
+ metadata: params.metadata,
45
+ });
46
+ if (!updatedBullet) {
47
+ throw new Error(`Failed to update bullet '${params.bulletId}'`);
48
+ }
49
+ bullet = updatedBullet;
50
+ }
51
+ else {
52
+ // ADD operation
53
+ // Ensure metadata has at least one tag (required by Bullet entity)
54
+ const metadata = params.metadata ?? {
55
+ relatedFiles: [],
56
+ tags: ['manual'],
57
+ timestamp: new Date().toISOString(),
58
+ };
59
+ // If metadata is provided but tags are empty, add default tag
60
+ if (metadata.tags.length === 0) {
61
+ metadata.tags = ['manual'];
62
+ }
63
+ bullet = playbook.addBullet(params.section, params.content, undefined, metadata);
64
+ }
65
+ // Save updated playbook
66
+ await this.playbookStore.save(playbook, directory);
67
+ return bullet;
68
+ }
69
+ async applyDelta(params) {
70
+ const directory = params.directory ?? this.config.baseDirectory;
71
+ // Load existing playbook or create new one
72
+ let playbook = await this.playbookStore.load(directory);
73
+ if (!playbook) {
74
+ playbook = new Playbook();
75
+ }
76
+ // Apply delta operations
77
+ playbook.applyDelta(params.delta);
78
+ // Save updated playbook
79
+ await this.playbookStore.save(playbook, directory);
80
+ return {
81
+ operationsApplied: params.delta.getOperationCount(),
82
+ playbook,
83
+ };
84
+ }
85
+ async applyReflectionTags(params) {
86
+ const directory = params.directory ?? this.config.baseDirectory;
87
+ // Load playbook
88
+ const playbook = await this.playbookStore.load(directory);
89
+ if (!playbook) {
90
+ throw new Error('Playbook not found. Run `br init` to initialize.');
91
+ }
92
+ // Apply tags from reflection
93
+ let tagsApplied = 0;
94
+ for (const bulletTag of params.reflection.bulletTags) {
95
+ const { id, tag } = bulletTag;
96
+ // Check if bullet exists
97
+ const bullet = playbook.getBullet(id);
98
+ if (!bullet) {
99
+ // Skip non-existent bullets (might have been removed)
100
+ continue;
101
+ }
102
+ // Add tag to bullet (appends to tags array if not already present)
103
+ const updated = playbook.addTagToBullet(id, tag);
104
+ if (updated) {
105
+ tagsApplied++;
106
+ }
107
+ }
108
+ // Save updated playbook
109
+ await this.playbookStore.save(playbook, directory);
110
+ return {
111
+ playbook,
112
+ tagsApplied,
113
+ };
114
+ }
115
+ async initialize(directory) {
116
+ const baseDir = directory ?? this.config.baseDirectory ?? process.cwd();
117
+ const brDir = join(baseDir, BR_DIR);
118
+ const aceDir = join(brDir, ACE_DIR);
119
+ // Create .br/ace/ directory
120
+ await mkdir(aceDir, { recursive: true });
121
+ // Create subdirectories
122
+ await Promise.all(FilePlaybookService.SUBDIRS.map((subdir) => mkdir(join(aceDir, subdir), { recursive: true })));
123
+ // Check if playbook already exists
124
+ const exists = await this.playbookStore.exists(directory ?? this.config.baseDirectory);
125
+ if (exists) {
126
+ throw new Error('Playbook already exists. Use `br clear` to remove it first.');
127
+ }
128
+ // Create empty playbook
129
+ const playbook = new Playbook();
130
+ await this.playbookStore.save(playbook, directory ?? this.config.baseDirectory);
131
+ return join(aceDir, 'playbook.json');
132
+ }
133
+ }
@@ -0,0 +1,19 @@
1
+ import { type Agent } from '../../core/domain/entities/agent.js';
2
+ import { type WriteMode } from '../../core/interfaces/i-file-service.js';
3
+ /**
4
+ * Configuration for agent-specific rule files.
5
+ */
6
+ export type AgentRuleConfig = {
7
+ /**
8
+ * The file path where the agent's rules should be written.
9
+ */
10
+ filePath: string;
11
+ /**
12
+ * The write mode to use when writing the rule file.
13
+ */
14
+ writeMode: WriteMode;
15
+ };
16
+ /**
17
+ * Mapping of agents to their rule file configurations.
18
+ */
19
+ export declare const AGENT_RULE_CONFIGS: Record<Agent, AgentRuleConfig>;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Mapping of agents to their rule file configurations.
3
+ */
4
+ export const AGENT_RULE_CONFIGS = {
5
+ Amp: {
6
+ filePath: 'AGENTS.md',
7
+ writeMode: 'append',
8
+ },
9
+ 'Augment Code': {
10
+ filePath: '.augment/rules/agent-context-engineering.md',
11
+ writeMode: 'overwrite',
12
+ },
13
+ 'Claude Code': {
14
+ filePath: 'CLAUDE.md',
15
+ writeMode: 'append',
16
+ },
17
+ Cline: {
18
+ filePath: '.clinerules/agent-context-engineering.md',
19
+ writeMode: 'overwrite',
20
+ },
21
+ Codex: {
22
+ filePath: 'AGENTS.md',
23
+ writeMode: 'append',
24
+ },
25
+ Cursor: {
26
+ filePath: '.cursor/rules/agent-context-engineering.mdc',
27
+ writeMode: 'overwrite',
28
+ },
29
+ 'Gemini CLI': {
30
+ filePath: 'GEMINI.md',
31
+ writeMode: 'append',
32
+ },
33
+ 'Github Copilot': {
34
+ filePath: '.github/copilot-instructions.md',
35
+ writeMode: 'append',
36
+ },
37
+ Junie: {
38
+ filePath: '.junie/guidelines.md',
39
+ writeMode: 'append',
40
+ },
41
+ 'Kilo Code': {
42
+ filePath: '.kilocode/rules/agent-context-engineering.md',
43
+ writeMode: 'overwrite',
44
+ },
45
+ Kiro: {
46
+ filePath: '.kiro/steering/agent-context-engineering.md',
47
+ writeMode: 'overwrite',
48
+ },
49
+ Qoder: {
50
+ filePath: '.qoder/rules/agent-context-engineering.md',
51
+ writeMode: 'overwrite',
52
+ },
53
+ 'Qwen Code': {
54
+ filePath: 'QWEN.md',
55
+ writeMode: 'append',
56
+ },
57
+ 'Roo Code': {
58
+ filePath: '.roo/rules/agent-context-engineering.md',
59
+ writeMode: 'overwrite',
60
+ },
61
+ 'Trae.ai': {
62
+ filePath: 'project_rules.md',
63
+ writeMode: 'append',
64
+ },
65
+ Warp: {
66
+ filePath: 'WARP.md',
67
+ writeMode: 'append',
68
+ },
69
+ Windsurf: {
70
+ filePath: '.windsurf/rules/agent-context-engineering.md',
71
+ writeMode: 'overwrite',
72
+ },
73
+ Zed: {
74
+ filePath: 'agent-context-engineering.rules',
75
+ writeMode: 'overwrite',
76
+ },
77
+ };
@@ -0,0 +1,18 @@
1
+ import { type Agent } from '../../core/domain/entities/agent.js';
2
+ import { type IRuleTemplateService } from '../../core/interfaces/i-rule-template-service.js';
3
+ import { type ITemplateLoader } from '../../core/interfaces/i-template-loader.js';
4
+ /**
5
+ * Service for generating rule templates for different agents.
6
+ * Loads templates from external files and assembles them with agent-specific context.
7
+ */
8
+ export declare class RuleTemplateService implements IRuleTemplateService {
9
+ private readonly templateLoader;
10
+ constructor(templateLoader: ITemplateLoader);
11
+ /**
12
+ * Generates rule content for the specified agent by loading and assembling templates.
13
+ * @param agent The agent for which to generate rules.
14
+ * @returns Promise resolving to the rule content as a string.
15
+ * @throws Error if templates cannot be loaded or assembled.
16
+ */
17
+ generateRuleContent(agent: Agent): Promise<string>;
18
+ }
@@ -0,0 +1,80 @@
1
+ import { BR_RULE_TAG } from '../../core/interfaces/i-rule-template-service.js';
2
+ const guideHeaders = [
3
+ {
4
+ agent: 'Augment Code',
5
+ value: `---
6
+ type: "always_apply"
7
+ ---`,
8
+ },
9
+ {
10
+ agent: 'Cursor',
11
+ value: `---
12
+ description: ByteRover CLI Rules
13
+ alwaysApply: true
14
+ ---`,
15
+ },
16
+ {
17
+ agent: 'Kiro',
18
+ value: `---
19
+ inclusion: always
20
+ ---`,
21
+ },
22
+ {
23
+ agent: 'Qoder',
24
+ value: `---
25
+ trigger: always_on
26
+ alwaysApply: true
27
+ ---`,
28
+ },
29
+ {
30
+ agent: 'Windsurf',
31
+ value: `---
32
+ trigger: always_on
33
+ ---`,
34
+ },
35
+ ];
36
+ /**
37
+ * Service for generating rule templates for different agents.
38
+ * Loads templates from external files and assembles them with agent-specific context.
39
+ */
40
+ export class RuleTemplateService {
41
+ templateLoader;
42
+ constructor(templateLoader) {
43
+ this.templateLoader = templateLoader;
44
+ }
45
+ /**
46
+ * Generates rule content for the specified agent by loading and assembling templates.
47
+ * @param agent The agent for which to generate rules.
48
+ * @returns Promise resolving to the rule content as a string.
49
+ * @throws Error if templates cannot be loaded or assembled.
50
+ */
51
+ async generateRuleContent(agent) {
52
+ try {
53
+ // Load section templates
54
+ const workflow = await this.templateLoader.loadSection('workflow');
55
+ const commandReference = await this.templateLoader.loadSection('command-reference');
56
+ // Load base template
57
+ const baseTemplate = await this.templateLoader.loadTemplate('base.md');
58
+ // Assemble context for variable substitution
59
+ const context = {
60
+ /* eslint-disable camelcase */
61
+ agent_name: agent,
62
+ command_reference: commandReference,
63
+ workflow,
64
+ /* eslint-enable camelcase */
65
+ };
66
+ // Substitute variables and get content
67
+ const content = this.templateLoader.substituteVariables(baseTemplate, context);
68
+ // Add agent-specific header if available (from develop branch)
69
+ const header = guideHeaders.find((h) => h.agent === agent)?.value || '';
70
+ return `${header}
71
+ ${content}
72
+ ---
73
+ ${BR_RULE_TAG} ${agent}
74
+ `;
75
+ }
76
+ catch (error) {
77
+ throw new Error(`Failed to generate rule content for agent '${agent}': ${error instanceof Error ? error.message : String(error)}`);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,19 @@
1
+ import { type Agent } from '../../core/domain/entities/agent.js';
2
+ import { type IFileService } from '../../core/interfaces/i-file-service.js';
3
+ import { IRuleTemplateService } from '../../core/interfaces/i-rule-template-service.js';
4
+ import { type IRuleWriterService } from '../../core/interfaces/i-rule-writer-service.js';
5
+ /**
6
+ * Service for writing agent-specific rule files.
7
+ * Uses IFileService to write files and RuleTemplateService to generate content.
8
+ */
9
+ export declare class RuleWriterService implements IRuleWriterService {
10
+ private readonly fileService;
11
+ private readonly templateService;
12
+ /**
13
+ * Creates a new RuleWriterService.
14
+ * @param fileService The file service to use for writing files.
15
+ * @param templateService The template service to use for generating rule content.
16
+ */
17
+ constructor(fileService: IFileService, templateService: IRuleTemplateService);
18
+ writeRule(agent: Agent, force: boolean): Promise<void>;
19
+ }
@@ -0,0 +1,43 @@
1
+ import { RuleExistsError } from '../../core/domain/errors/rule-error.js';
2
+ import { BR_RULE_TAG } from '../../core/interfaces/i-rule-template-service.js';
3
+ import { AGENT_RULE_CONFIGS } from './agent-rule-config.js';
4
+ /**
5
+ * Service for writing agent-specific rule files.
6
+ * Uses IFileService to write files and RuleTemplateService to generate content.
7
+ */
8
+ export class RuleWriterService {
9
+ fileService;
10
+ templateService;
11
+ /**
12
+ * Creates a new RuleWriterService.
13
+ * @param fileService The file service to use for writing files.
14
+ * @param templateService The template service to use for generating rule content.
15
+ */
16
+ constructor(fileService, templateService) {
17
+ this.fileService = fileService;
18
+ this.templateService = templateService;
19
+ }
20
+ async writeRule(agent, force) {
21
+ const config = AGENT_RULE_CONFIGS[agent];
22
+ if (!config) {
23
+ throw new Error(`No configuration found for agent: ${agent}`);
24
+ }
25
+ const { filePath, writeMode } = config;
26
+ const fileExists = await this.fileService.exists(filePath);
27
+ // Throw an error if the file exists and force is not set
28
+ if (writeMode === 'overwrite' && fileExists && !force) {
29
+ throw new RuleExistsError();
30
+ }
31
+ if (writeMode === 'append' && fileExists && !force) {
32
+ const content = await this.fileService.read(filePath);
33
+ // Throw an error if the rule already exists
34
+ if (content.includes(BR_RULE_TAG)) {
35
+ throw new RuleExistsError();
36
+ }
37
+ }
38
+ // Generate rule content
39
+ const ruleContent = await this.templateService.generateRuleContent(agent);
40
+ // Write the rule file
41
+ await this.fileService.write(ruleContent, filePath, writeMode);
42
+ }
43
+ }
@@ -0,0 +1,20 @@
1
+ import type { ISpaceService } from '../../core/interfaces/i-space-service.js';
2
+ import { Space } from '../../core/domain/entities/space.js';
3
+ export type SpaceServiceConfig = {
4
+ apiBaseUrl: string;
5
+ timeout?: number;
6
+ };
7
+ export declare class HttpSpaceService implements ISpaceService {
8
+ private readonly config;
9
+ constructor(config: SpaceServiceConfig);
10
+ getSpaces(accessToken: string, sessionKey: string, teamId: string, option?: {
11
+ fetchAll?: boolean;
12
+ limit?: number;
13
+ offset?: number;
14
+ }): Promise<{
15
+ spaces: Space[];
16
+ total: number;
17
+ }>;
18
+ private fetchAllSpaces;
19
+ private mapToSpace;
20
+ }
@@ -0,0 +1,67 @@
1
+ import { Space } from '../../core/domain/entities/space.js';
2
+ import { AuthenticatedHttpClient } from '../http/authenticated-http-client.js';
3
+ export class HttpSpaceService {
4
+ config;
5
+ constructor(config) {
6
+ this.config = {
7
+ ...config,
8
+ timeout: 10_000, // Default 10 seconds timeout
9
+ };
10
+ }
11
+ async getSpaces(accessToken, sessionKey, teamId, option) {
12
+ try {
13
+ const httpClient = new AuthenticatedHttpClient(accessToken, sessionKey);
14
+ // Scenario 1: Fetch all automatically via auto-pagination
15
+ if (option?.fetchAll === true) {
16
+ return await this.fetchAllSpaces(httpClient, teamId);
17
+ }
18
+ // Scenario 2 & 3: Single request (with or without pagination params)
19
+ const params = new URLSearchParams();
20
+ params.append('team_id', teamId);
21
+ if (option?.limit !== undefined) {
22
+ params.append('limit', option.limit.toString());
23
+ }
24
+ if (option?.offset !== undefined) {
25
+ params.append('offset', option.offset.toString());
26
+ }
27
+ const url = `${this.config.apiBaseUrl}/spaces?${params.toString()}`;
28
+ const response = await httpClient.get(url, {
29
+ timeout: this.config.timeout,
30
+ });
31
+ return {
32
+ spaces: response.data.spaces.map((spaceData) => this.mapToSpace(spaceData)),
33
+ total: response.data.total,
34
+ };
35
+ }
36
+ catch (error) {
37
+ throw new Error(`Failed to fetch spaces: ${error.message}`);
38
+ }
39
+ }
40
+ async fetchAllSpaces(httpClient, teamId) {
41
+ const pageSize = 100; // Larger pages for fewer requests
42
+ let offset = 0;
43
+ let allSpaces = [];
44
+ let total = 0;
45
+ while (true) {
46
+ const params = new URLSearchParams({
47
+ limit: pageSize.toString(),
48
+ offset: offset.toString(),
49
+ team_id: teamId,
50
+ });
51
+ // eslint-disable-next-line no-await-in-loop
52
+ const response = await httpClient.get(`${this.config.apiBaseUrl}/spaces?${params.toString()}`, { timeout: this.config.timeout });
53
+ const pageSpaces = response.data.spaces.map((spaceData) => this.mapToSpace(spaceData));
54
+ allSpaces = [...allSpaces, ...pageSpaces];
55
+ total = response.data.total;
56
+ // Stop if we've fetched everything or got empty page
57
+ if (allSpaces.length >= total || pageSpaces.length === 0) {
58
+ break;
59
+ }
60
+ offset += pageSize;
61
+ }
62
+ return { spaces: allSpaces, total };
63
+ }
64
+ mapToSpace(spaceData) {
65
+ return new Space(spaceData.id, spaceData.name, spaceData.team_id, spaceData.team.name);
66
+ }
67
+ }
@@ -0,0 +1,10 @@
1
+ import type { ITokenStore } from '../../core/interfaces/i-token-store.js';
2
+ import { AuthToken } from '../../core/domain/entities/auth-token.js';
3
+ /**
4
+ * Token store implementation using the system keychain via the keytar library.
5
+ */
6
+ export declare class KeychainTokenStore implements ITokenStore {
7
+ clear(): Promise<void>;
8
+ load(): Promise<AuthToken | undefined>;
9
+ save(token: AuthToken): Promise<void>;
10
+ }
@@ -0,0 +1,40 @@
1
+ import keytar from 'keytar';
2
+ import { AuthToken } from '../../core/domain/entities/auth-token.js';
3
+ const SERVICE_NAME = 'byterover-cli';
4
+ const ACCOUNT_NAME = 'auth-token';
5
+ /**
6
+ * Token store implementation using the system keychain via the keytar library.
7
+ */
8
+ export class KeychainTokenStore {
9
+ async clear() {
10
+ try {
11
+ await keytar.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
12
+ }
13
+ catch {
14
+ // Ignore errors - token might not exist or keychain might be unavailable
15
+ }
16
+ }
17
+ async load() {
18
+ try {
19
+ const data = await keytar.getPassword(SERVICE_NAME, ACCOUNT_NAME);
20
+ if (data === null) {
21
+ return undefined;
22
+ }
23
+ const deserialized = JSON.parse(data);
24
+ return AuthToken.fromJson(deserialized);
25
+ }
26
+ catch {
27
+ // Return undefined on any error (missing token, invalid JSON, keychain errors)
28
+ return undefined;
29
+ }
30
+ }
31
+ async save(token) {
32
+ try {
33
+ const data = JSON.stringify(token);
34
+ await keytar.setPassword(SERVICE_NAME, ACCOUNT_NAME, data);
35
+ }
36
+ catch (error) {
37
+ throw new Error(`Failed to save token to keychain: ${error instanceof Error ? error.message : 'Unknown error'}`);
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,21 @@
1
+ import type { ITeamService } from '../../core/interfaces/i-team-service.js';
2
+ import { Team } from '../../core/domain/entities/team.js';
3
+ export type TeamServiceConfig = {
4
+ apiBaseUrl: string;
5
+ timeout?: number;
6
+ };
7
+ export declare class HttpTeamService implements ITeamService {
8
+ private readonly config;
9
+ constructor(config: TeamServiceConfig);
10
+ getTeams(accessToken: string, sessionKey: string, option?: {
11
+ fetchAll?: boolean;
12
+ isActive?: boolean;
13
+ limit?: number;
14
+ offset?: number;
15
+ }): Promise<{
16
+ teams: Team[];
17
+ total: number;
18
+ }>;
19
+ private fetchAllTeams;
20
+ private mapToTeam;
21
+ }
@@ -0,0 +1,71 @@
1
+ import { Team } from '../../core/domain/entities/team.js';
2
+ import { AuthenticatedHttpClient } from '../http/authenticated-http-client.js';
3
+ export class HttpTeamService {
4
+ config;
5
+ constructor(config) {
6
+ this.config = {
7
+ ...config,
8
+ timeout: 10_000, // Default 10 seconds timeout
9
+ };
10
+ }
11
+ async getTeams(accessToken, sessionKey, option) {
12
+ try {
13
+ const httpClient = new AuthenticatedHttpClient(accessToken, sessionKey);
14
+ // Scenario 1: Fetch all automatically via auto-pagination
15
+ if (option?.fetchAll === true) {
16
+ return await this.fetchAllTeams(httpClient, option?.isActive);
17
+ }
18
+ // Scenario 2 & 3: Single request (with or without pagination params)
19
+ const params = new URLSearchParams();
20
+ if (option?.limit !== undefined) {
21
+ params.append('limit', option.limit.toString());
22
+ }
23
+ if (option?.offset !== undefined) {
24
+ params.append('offset', option.offset.toString());
25
+ }
26
+ if (option?.isActive !== undefined) {
27
+ params.append('is_active', option.isActive.toString());
28
+ }
29
+ const url = `${this.config.apiBaseUrl}/teams${params.toString() ? `?${params.toString()}` : ''}`;
30
+ const response = await httpClient.get(url, {
31
+ timeout: this.config.timeout,
32
+ });
33
+ return {
34
+ teams: response.data.teams.map((teamData) => this.mapToTeam(teamData)),
35
+ total: response.data.total,
36
+ };
37
+ }
38
+ catch (error) {
39
+ throw new Error(`Failed to fetch teams: ${error.message}`);
40
+ }
41
+ }
42
+ async fetchAllTeams(httpClient, isActive) {
43
+ const pageSize = 100; // Larger pages for fewer requests
44
+ let offset = 0;
45
+ let allTeams = [];
46
+ let total = 0;
47
+ while (true) {
48
+ const params = new URLSearchParams({
49
+ limit: pageSize.toString(),
50
+ offset: offset.toString(),
51
+ });
52
+ if (isActive !== undefined) {
53
+ params.append('is_active', isActive.toString());
54
+ }
55
+ // eslint-disable-next-line no-await-in-loop
56
+ const response = await httpClient.get(`${this.config.apiBaseUrl}/teams?${params.toString()}`, { timeout: this.config.timeout });
57
+ const pageTeams = response.data.teams.map((teamData) => this.mapToTeam(teamData));
58
+ allTeams = [...allTeams, ...pageTeams];
59
+ total = response.data.total;
60
+ // Stop if we've fetched everything or got empty page
61
+ if (allTeams.length >= total || pageTeams.length === 0) {
62
+ break;
63
+ }
64
+ offset += pageSize;
65
+ }
66
+ return { teams: allTeams, total };
67
+ }
68
+ mapToTeam(teamData) {
69
+ return Team.fromJson(teamData);
70
+ }
71
+ }
@@ -0,0 +1,33 @@
1
+ import { type IFileService } from '../../core/interfaces/i-file-service.js';
2
+ import { type ITemplateLoader } from '../../core/interfaces/i-template-loader.js';
3
+ /**
4
+ * File system-based template loader.
5
+ * Loads templates from src/templates/ directory and performs variable substitution.
6
+ */
7
+ export declare class FsTemplateLoader implements ITemplateLoader {
8
+ private readonly fileService;
9
+ private readonly templatesDir;
10
+ constructor(fileService: IFileService);
11
+ /**
12
+ * Loads a section template from the sections/ directory.
13
+ * @param sectionName - Name of the section (e.g., 'workflow', 'command-reference')
14
+ * @returns Promise resolving to section content
15
+ * @throws Error if section file cannot be read
16
+ */
17
+ loadSection(sectionName: string): Promise<string>;
18
+ /**
19
+ * Loads a template file from the templates directory.
20
+ * @param templatePath - Relative path to template (e.g., 'base.md', 'sections/workflow.md')
21
+ * @returns Promise resolving to template content
22
+ * @throws Error if template file cannot be read
23
+ */
24
+ loadTemplate(templatePath: string): Promise<string>;
25
+ /**
26
+ * Substitutes variables in a template string.
27
+ * Replaces {{variable_name}} with corresponding values from context.
28
+ * @param template - Template string with {{variable}} placeholders
29
+ * @param context - Object mapping variable names to values
30
+ * @returns Template with variables replaced
31
+ */
32
+ substituteVariables(template: string, context: Record<string, string>): string;
33
+ }