@compilr-dev/cli 0.8.0 → 0.8.2
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/dist/compilr-diff-companion.vsix +0 -0
- package/dist/index.js +8 -0
- package/dist/repl-v2.js +30 -4
- package/dist/tools/platform-adapter.js +18 -117
- package/dist/ui/overlay-manager.js +22 -2
- package/dist/utils/project-memory.d.ts +8 -43
- package/dist/utils/project-memory.js +7 -114
- package/package.json +4 -4
|
Binary file
|
package/dist/index.js
CHANGED
|
@@ -885,6 +885,14 @@ async function main() {
|
|
|
885
885
|
// Show login overlay if not authenticated
|
|
886
886
|
showLogin,
|
|
887
887
|
});
|
|
888
|
+
// Always restore the hardware cursor on exit. Overlays/menus hide it while
|
|
889
|
+
// showing nav-only screens; without this, quitting (incl. Ctrl+C, which
|
|
890
|
+
// routes through process.exit → 'exit') from such a screen would leave the
|
|
891
|
+
// user's terminal cursor invisible.
|
|
892
|
+
process.on('exit', () => {
|
|
893
|
+
if (process.stdout.isTTY)
|
|
894
|
+
process.stdout.write('\x1b[?25h');
|
|
895
|
+
});
|
|
888
896
|
// Handle Ctrl+C gracefully
|
|
889
897
|
process.on('SIGINT', () => {
|
|
890
898
|
// Flush episode recorder before exit
|
package/dist/repl-v2.js
CHANGED
|
@@ -31,7 +31,7 @@ import { renderMascotWithLogo } from './ui/mascot/renderer.js';
|
|
|
31
31
|
import { getStartupHighlights } from './changelog/index.js';
|
|
32
32
|
import { registerCommands, executeCommand, allCommands, getAutocompleteCommands, setSkillCommandLoader, saveCurrentSession, saveCurrentTeam, loadProjectSession, archiveCurrentSession, convertMessagesToItems, } from './commands-v2/index.js';
|
|
33
33
|
import { getCustomCommandRegistry } from './commands/custom-registry.js';
|
|
34
|
-
import { getPlanModePrompt, createHandoffTool, createConsultTool, createDelegateTool, createDelegateBackgroundTool, } from '@compilr-dev/sdk';
|
|
34
|
+
import { getPlanModePrompt, createHandoffTool, createConsultTool, createDelegateTool, createDelegateBackgroundTool, classifyAgentError, } from '@compilr-dev/sdk';
|
|
35
35
|
import { getHandoffHandler, getConsultHandler, getDelegateHandler, getDelegateBackgroundHandler, } from './shared-handlers.js';
|
|
36
36
|
import { planRepository } from './db/repositories/index.js';
|
|
37
37
|
import { PlanApprovalOverlayV2, } from './ui/overlay/impl/plan-approval-overlay-v2.js';
|
|
@@ -144,6 +144,26 @@ ${JSON.stringify(state.messages, null, 2)}
|
|
|
144
144
|
log.error({ component: 'repl', err: error }, 'Failed to log agent request');
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
+
/**
|
|
148
|
+
* Map an agent/provider error to a terminal-friendly message. Classification is
|
|
149
|
+
* shared across hosts via the SDK (classifyAgentError); only the remediation
|
|
150
|
+
* wording is CLI-specific. The CLI checks for a *missing* key at startup, so this
|
|
151
|
+
* mainly catches an *invalid/expired* key surfacing as a runtime 401 mid-turn.
|
|
152
|
+
*/
|
|
153
|
+
function friendlyTurnError(err) {
|
|
154
|
+
switch (classifyAgentError(err).category) {
|
|
155
|
+
case 'auth':
|
|
156
|
+
return 'Invalid or missing API key for this provider. Set a valid key with /keys (or export the provider env var). No key? Restart with --provider ollama for free local models.';
|
|
157
|
+
case 'rate_limit':
|
|
158
|
+
return 'Rate limited by the provider. Wait a moment and try again.';
|
|
159
|
+
case 'overloaded':
|
|
160
|
+
return 'The provider is overloaded right now. Try again in a moment.';
|
|
161
|
+
case 'connection':
|
|
162
|
+
return "Can't reach the provider. Check your internet connection and try again.";
|
|
163
|
+
default:
|
|
164
|
+
return String(err);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
147
167
|
// =============================================================================
|
|
148
168
|
// REPL V2 Class
|
|
149
169
|
// =============================================================================
|
|
@@ -3035,7 +3055,7 @@ export class ReplV2 {
|
|
|
3035
3055
|
}
|
|
3036
3056
|
catch (err) {
|
|
3037
3057
|
if (!signal.aborted) {
|
|
3038
|
-
this.ui.print({ type: 'error', message:
|
|
3058
|
+
this.ui.print({ type: 'error', message: friendlyTurnError(err) });
|
|
3039
3059
|
}
|
|
3040
3060
|
this.ui.setAgentRunning(false);
|
|
3041
3061
|
this.ui.setThinking(false); // Ensure thinking indicator is cleared
|
|
@@ -3287,7 +3307,7 @@ export class ReplV2 {
|
|
|
3287
3307
|
}
|
|
3288
3308
|
catch (err) {
|
|
3289
3309
|
if (!signal.aborted) {
|
|
3290
|
-
this.ui.print({ type: 'error', message: `${label}
|
|
3310
|
+
this.ui.print({ type: 'error', message: `${label} ${friendlyTurnError(err)}` });
|
|
3291
3311
|
// Notify other terminals about failure
|
|
3292
3312
|
try {
|
|
3293
3313
|
const sid = getActiveTerminalSessionId();
|
|
@@ -3602,7 +3622,7 @@ export class ReplV2 {
|
|
|
3602
3622
|
}
|
|
3603
3623
|
catch (err) {
|
|
3604
3624
|
if (!signal.aborted) {
|
|
3605
|
-
this.ui.print({ type: 'error', message:
|
|
3625
|
+
this.ui.print({ type: 'error', message: friendlyTurnError(err) });
|
|
3606
3626
|
}
|
|
3607
3627
|
this.ui.setAgentRunning(false);
|
|
3608
3628
|
this.ui.setThinking(false); // Ensure thinking indicator is cleared
|
|
@@ -3719,6 +3739,12 @@ export class ReplV2 {
|
|
|
3719
3739
|
// Only run main() when executed directly (not when imported)
|
|
3720
3740
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
3721
3741
|
const repl = new ReplV2();
|
|
3742
|
+
// Restore the hardware cursor on exit (overlays/menus hide it for nav-only
|
|
3743
|
+
// screens; don't leave the user's terminal cursor invisible after quit).
|
|
3744
|
+
process.on('exit', () => {
|
|
3745
|
+
if (process.stdout.isTTY)
|
|
3746
|
+
process.stdout.write('\x1b[?25h');
|
|
3747
|
+
});
|
|
3722
3748
|
// Handle SIGINT — restore raw mode before exit so the parent shell
|
|
3723
3749
|
// doesn't inherit a broken terminal state.
|
|
3724
3750
|
process.on('SIGINT', () => {
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* wires CLI-specific hooks and services (anchors, artifacts, episodes),
|
|
6
6
|
* and delegates all 32 platform tool definitions to the SDK.
|
|
7
7
|
*/
|
|
8
|
-
import { createPlatformTools, createSQLiteRepositories, ProjectAnchorStore, } from '@compilr-dev/sdk';
|
|
8
|
+
import { createPlatformTools, createSQLiteRepositories, ProjectAnchorStore, FileArtifactService, } from '@compilr-dev/sdk';
|
|
9
9
|
import { createAllFactoryTools } from '@compilr-dev/factory';
|
|
10
10
|
import { getDb } from '../db/index.js';
|
|
11
11
|
import { getDataPath, getSessionsPath } from '../settings/paths.js';
|
|
@@ -16,7 +16,7 @@ import { getCurrentProject, setCurrentProject } from './project-db.js';
|
|
|
16
16
|
import { awardFirstProject, awardWorkItemCompletion } from '../games/coins.js';
|
|
17
17
|
import { getActiveSharedContext } from '@compilr-dev/sdk';
|
|
18
18
|
import { setAnchorStore } from '../anchors/index.js';
|
|
19
|
-
import {
|
|
19
|
+
import { recordTeamActivity, getSessionsPath as getCheckpointerSessionsPath } from '../multi-agent/index.js';
|
|
20
20
|
import { getGlobalEpisodeStore } from '../episodes/index.js';
|
|
21
21
|
// =============================================================================
|
|
22
22
|
// Repositories — SDK's SQLite implementations, backed by CLI's existing DB
|
|
@@ -63,123 +63,24 @@ const anchorService = anchorStore.toService();
|
|
|
63
63
|
// =============================================================================
|
|
64
64
|
// Artifact Service — wraps ArtifactStore from TeamCheckpointer
|
|
65
65
|
// =============================================================================
|
|
66
|
+
// Artifacts — file-backed service shared with Desktop via the SDK
|
|
67
|
+
// (FileArtifactService), pointed at the same sessions dir the checkpointer uses
|
|
68
|
+
// so artifact_* tools and team hydration read/write the same artifacts.json.
|
|
69
|
+
// We wrap save() to record team activity attributed to the ACTING agent
|
|
70
|
+
// (input.agent), which differs from the artifact's original creator on updates.
|
|
71
|
+
const baseArtifactService = new FileArtifactService({
|
|
72
|
+
sessionsDir: getCheckpointerSessionsPath(),
|
|
73
|
+
getProjectId: () => getCurrentProject()?.id ?? null,
|
|
74
|
+
});
|
|
66
75
|
const artifactService = {
|
|
67
|
-
save: (input) => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const agentId = input.agent ?? 'default';
|
|
72
|
-
const existing = store.getByName(input.name);
|
|
73
|
-
if (existing) {
|
|
74
|
-
const updated = store.update(existing.id, {
|
|
75
|
-
content: input.content,
|
|
76
|
-
summary: input.summary,
|
|
77
|
-
type: input.type,
|
|
78
|
-
});
|
|
79
|
-
if (!updated)
|
|
80
|
-
throw new Error(`Failed to update artifact "${input.name}"`);
|
|
81
|
-
getTeamCheckpointer().saveArtifactStore(currentProject?.id ?? null, store);
|
|
82
|
-
recordTeamActivity(agentId, 'artifact_updated', `updated artifact "${updated.name}"`);
|
|
83
|
-
const artifact = {
|
|
84
|
-
id: updated.id,
|
|
85
|
-
name: updated.name,
|
|
86
|
-
agent: updated.agent,
|
|
87
|
-
type: updated.type,
|
|
88
|
-
content: updated.content,
|
|
89
|
-
summary: updated.summary,
|
|
90
|
-
version: updated.version,
|
|
91
|
-
createdAt: updated.createdAt,
|
|
92
|
-
updatedAt: updated.updatedAt,
|
|
93
|
-
};
|
|
94
|
-
return Promise.resolve({ artifact, isUpdate: true });
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
const created = store.create({
|
|
98
|
-
name: input.name,
|
|
99
|
-
agent: agentId,
|
|
100
|
-
type: input.type,
|
|
101
|
-
content: input.content,
|
|
102
|
-
summary: input.summary,
|
|
103
|
-
});
|
|
104
|
-
getTeamCheckpointer().saveArtifactStore(currentProject?.id ?? null, store);
|
|
105
|
-
recordTeamActivity(agentId, 'artifact_created', `created artifact "${created.name}"`);
|
|
106
|
-
const artifact = {
|
|
107
|
-
id: created.id,
|
|
108
|
-
name: created.name,
|
|
109
|
-
agent: created.agent,
|
|
110
|
-
type: created.type,
|
|
111
|
-
content: created.content,
|
|
112
|
-
summary: created.summary,
|
|
113
|
-
version: created.version,
|
|
114
|
-
createdAt: created.createdAt,
|
|
115
|
-
updatedAt: created.updatedAt,
|
|
116
|
-
};
|
|
117
|
-
return Promise.resolve({ artifact, isUpdate: false });
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
getByName: (name) => {
|
|
121
|
-
const currentProject = getCurrentProject();
|
|
122
|
-
const store = getTeamCheckpointer().getArtifactStore(currentProject?.id ?? null);
|
|
123
|
-
const artifact = store.getByName(name);
|
|
124
|
-
if (!artifact)
|
|
125
|
-
return Promise.resolve(null);
|
|
126
|
-
const result = {
|
|
127
|
-
id: artifact.id,
|
|
128
|
-
name: artifact.name,
|
|
129
|
-
agent: artifact.agent,
|
|
130
|
-
type: artifact.type,
|
|
131
|
-
content: artifact.content,
|
|
132
|
-
summary: artifact.summary,
|
|
133
|
-
version: artifact.version,
|
|
134
|
-
createdAt: artifact.createdAt,
|
|
135
|
-
updatedAt: artifact.updatedAt,
|
|
136
|
-
};
|
|
137
|
-
return Promise.resolve(result);
|
|
138
|
-
},
|
|
139
|
-
list: (options) => {
|
|
140
|
-
const currentProject = getCurrentProject();
|
|
141
|
-
const store = getTeamCheckpointer().getArtifactStore(currentProject?.id ?? null);
|
|
142
|
-
let items = options?.search ? store.search(options.search) : store.list();
|
|
143
|
-
if (options?.type) {
|
|
144
|
-
items = items.filter(a => a.type === options.type);
|
|
145
|
-
}
|
|
146
|
-
if (options?.agent) {
|
|
147
|
-
items = items.filter(a => a.agent === options.agent);
|
|
148
|
-
}
|
|
149
|
-
const summaries = items.map(a => ({
|
|
150
|
-
id: a.id,
|
|
151
|
-
name: a.name,
|
|
152
|
-
agent: a.agent,
|
|
153
|
-
type: a.type,
|
|
154
|
-
summary: a.summary,
|
|
155
|
-
version: a.version,
|
|
156
|
-
updatedAt: a.updatedAt,
|
|
157
|
-
}));
|
|
158
|
-
return Promise.resolve(summaries);
|
|
159
|
-
},
|
|
160
|
-
delete: (name) => {
|
|
161
|
-
const currentProject = getCurrentProject();
|
|
162
|
-
const store = getTeamCheckpointer().getArtifactStore(currentProject?.id ?? null);
|
|
163
|
-
const artifact = store.getByName(name);
|
|
164
|
-
if (!artifact)
|
|
165
|
-
return Promise.resolve(null);
|
|
166
|
-
const deleted = store.delete(artifact.id);
|
|
167
|
-
if (!deleted)
|
|
168
|
-
return Promise.resolve(null);
|
|
169
|
-
getTeamCheckpointer().saveArtifactStore(currentProject?.id ?? null, store);
|
|
170
|
-
const result = {
|
|
171
|
-
id: artifact.id,
|
|
172
|
-
name: artifact.name,
|
|
173
|
-
agent: artifact.agent,
|
|
174
|
-
type: artifact.type,
|
|
175
|
-
content: artifact.content,
|
|
176
|
-
summary: artifact.summary,
|
|
177
|
-
version: artifact.version,
|
|
178
|
-
createdAt: artifact.createdAt,
|
|
179
|
-
updatedAt: artifact.updatedAt,
|
|
180
|
-
};
|
|
181
|
-
return Promise.resolve(result);
|
|
76
|
+
save: async (input) => {
|
|
77
|
+
const result = await baseArtifactService.save(input);
|
|
78
|
+
recordTeamActivity(input.agent ?? 'default', result.isUpdate ? 'artifact_updated' : 'artifact_created', `${result.isUpdate ? 'updated' : 'created'} artifact "${result.artifact.name}"`);
|
|
79
|
+
return result;
|
|
182
80
|
},
|
|
81
|
+
getByName: (name) => baseArtifactService.getByName(name),
|
|
82
|
+
list: (options) => baseArtifactService.list(options),
|
|
83
|
+
delete: (name) => baseArtifactService.delete(name),
|
|
183
84
|
};
|
|
184
85
|
// =============================================================================
|
|
185
86
|
// Episode Service — wraps FileEpisodeStore
|
|
@@ -176,6 +176,9 @@ export class OverlayManager {
|
|
|
176
176
|
process.stdout.write('\n');
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
+
// Only show the hardware cursor for overlays that actually take text input
|
|
180
|
+
// (they provide a cursorPosition). Nav-only overlays (menus, dashboards,
|
|
181
|
+
// selection lists) hide it so it doesn't blink, parked, below the screen.
|
|
179
182
|
if (content.cursorPosition) {
|
|
180
183
|
const { line, column } = content.cursorPosition;
|
|
181
184
|
const linesFromEnd = paddedLines.length - 1 - line;
|
|
@@ -183,8 +186,11 @@ export class OverlayManager {
|
|
|
183
186
|
ttyWrite(`\x1b[${String(linesFromEnd)}A`);
|
|
184
187
|
}
|
|
185
188
|
ttyWrite(`\x1b[${String(column)}G`);
|
|
189
|
+
terminal.showCursor();
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
terminal.hideCursor();
|
|
186
193
|
}
|
|
187
|
-
terminal.showCursor();
|
|
188
194
|
}
|
|
189
195
|
renderInlineOverlay(content, termWidth) {
|
|
190
196
|
this.clearOverlayRender();
|
|
@@ -206,7 +212,21 @@ export class OverlayManager {
|
|
|
206
212
|
for (const line of paddedLines) {
|
|
207
213
|
process.stdout.write(line + '\n');
|
|
208
214
|
}
|
|
209
|
-
|
|
215
|
+
// Same rule as fullscreen: cursor only for text-input overlays. Inline
|
|
216
|
+
// writes a trailing newline after every line, so the cursor sits one row
|
|
217
|
+
// below the last content line (hence no -1 in linesFromEnd).
|
|
218
|
+
if (content.cursorPosition) {
|
|
219
|
+
const { line, column } = content.cursorPosition;
|
|
220
|
+
const linesFromEnd = paddedLines.length - line;
|
|
221
|
+
if (linesFromEnd > 0) {
|
|
222
|
+
ttyWrite(`\x1b[${String(linesFromEnd)}A`);
|
|
223
|
+
}
|
|
224
|
+
ttyWrite(`\x1b[${String(column)}G`);
|
|
225
|
+
terminal.showCursor();
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
terminal.hideCursor();
|
|
229
|
+
}
|
|
210
230
|
}
|
|
211
231
|
// ===========================================================================
|
|
212
232
|
// Action processing
|
|
@@ -1,47 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Project Memory Loader
|
|
2
|
+
* Project Memory Loader (CLI)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
export interface ProjectMemoryResult {
|
|
8
|
-
/** Whether a memory file was found */
|
|
9
|
-
found: boolean;
|
|
10
|
-
/** The loaded content (may be truncated) */
|
|
11
|
-
content: string;
|
|
12
|
-
/** Path to the file that was loaded */
|
|
13
|
-
filePath: string | null;
|
|
14
|
-
/** Original file size in bytes */
|
|
15
|
-
originalSize: number;
|
|
16
|
-
/** Whether the content was truncated */
|
|
17
|
-
truncated: boolean;
|
|
18
|
-
/** Estimated token count (chars / 4) */
|
|
19
|
-
estimatedTokens: number;
|
|
20
|
-
}
|
|
21
|
-
export interface ProjectMemoryOptions {
|
|
22
|
-
/** Maximum content size in bytes before truncation (default: 100KB) */
|
|
23
|
-
maxSize?: number;
|
|
24
|
-
/** Size threshold for warning (default: 30KB) */
|
|
25
|
-
warnSize?: number;
|
|
26
|
-
/** Custom search directory (default: process.cwd()) */
|
|
27
|
-
cwd?: string;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Load project memory from COMPILR.md or CLAUDE.md
|
|
4
|
+
* Now a thin re-export of the SDK's shared loader (loadProjectContextFiles):
|
|
5
|
+
* priority search over COMPILR.md / .compilr/instructions.md / CLAUDE.md /
|
|
6
|
+
* .claude/instructions.md with size-guard truncation. Shared with Desktop.
|
|
31
7
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*/
|
|
35
|
-
export declare function loadProjectMemory(options?: ProjectMemoryOptions): ProjectMemoryResult;
|
|
36
|
-
/**
|
|
37
|
-
* Check if a project memory file exists without loading it
|
|
38
|
-
*/
|
|
39
|
-
export declare function hasProjectMemory(cwd?: string): boolean;
|
|
40
|
-
/**
|
|
41
|
-
* Get the size category for display purposes
|
|
42
|
-
*/
|
|
43
|
-
export declare function getSizeCategory(bytes: number): 'small' | 'medium' | 'large';
|
|
44
|
-
/**
|
|
45
|
-
* Format bytes as human-readable string
|
|
8
|
+
* The previous CLI-only display helpers (hasProjectMemory / getSizeCategory /
|
|
9
|
+
* formatBytes) had no callers and were removed.
|
|
46
10
|
*/
|
|
47
|
-
export
|
|
11
|
+
export { loadProjectContextFiles as loadProjectMemory } from '@compilr-dev/sdk';
|
|
12
|
+
export type { ProjectContextResult as ProjectMemoryResult, LoadProjectContextOptions as ProjectMemoryOptions, } from '@compilr-dev/sdk';
|
|
@@ -1,118 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Project Memory Loader
|
|
2
|
+
* Project Memory Loader (CLI)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
import * as fs from 'fs';
|
|
8
|
-
import * as path from 'path';
|
|
9
|
-
import { estimateTokens } from './token-tracker.js';
|
|
10
|
-
// =============================================================================
|
|
11
|
-
// Constants
|
|
12
|
-
// =============================================================================
|
|
13
|
-
/** Files to search for, in priority order */
|
|
14
|
-
const MEMORY_FILES = [
|
|
15
|
-
'COMPILR.md',
|
|
16
|
-
'.compilr/instructions.md',
|
|
17
|
-
'CLAUDE.md',
|
|
18
|
-
'.claude/instructions.md',
|
|
19
|
-
];
|
|
20
|
-
/** Default max size: 100KB */
|
|
21
|
-
const DEFAULT_MAX_SIZE = 100 * 1024;
|
|
22
|
-
/** Default warn size: 30KB */
|
|
23
|
-
const DEFAULT_WARN_SIZE = 30 * 1024;
|
|
24
|
-
// =============================================================================
|
|
25
|
-
// Main Function
|
|
26
|
-
// =============================================================================
|
|
27
|
-
/**
|
|
28
|
-
* Load project memory from COMPILR.md or CLAUDE.md
|
|
4
|
+
* Now a thin re-export of the SDK's shared loader (loadProjectContextFiles):
|
|
5
|
+
* priority search over COMPILR.md / .compilr/instructions.md / CLAUDE.md /
|
|
6
|
+
* .claude/instructions.md with size-guard truncation. Shared with Desktop.
|
|
29
7
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*/
|
|
33
|
-
export function loadProjectMemory(options = {}) {
|
|
34
|
-
const cwd = options.cwd ?? process.cwd();
|
|
35
|
-
const maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
|
|
36
|
-
// Search for memory file
|
|
37
|
-
for (const filename of MEMORY_FILES) {
|
|
38
|
-
const filePath = path.join(cwd, filename);
|
|
39
|
-
if (fs.existsSync(filePath)) {
|
|
40
|
-
try {
|
|
41
|
-
const stat = fs.statSync(filePath);
|
|
42
|
-
const originalSize = stat.size;
|
|
43
|
-
// Read file content
|
|
44
|
-
let content = fs.readFileSync(filePath, 'utf-8');
|
|
45
|
-
let truncated = false;
|
|
46
|
-
// Truncate if too large
|
|
47
|
-
if (content.length > maxSize) {
|
|
48
|
-
content = content.slice(0, maxSize);
|
|
49
|
-
// Try to truncate at a line boundary
|
|
50
|
-
const lastNewline = content.lastIndexOf('\n');
|
|
51
|
-
if (lastNewline > maxSize * 0.8) {
|
|
52
|
-
content = content.slice(0, lastNewline);
|
|
53
|
-
}
|
|
54
|
-
content += '\n\n[... truncated due to size ...]';
|
|
55
|
-
truncated = true;
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
found: true,
|
|
59
|
-
content,
|
|
60
|
-
filePath,
|
|
61
|
-
originalSize,
|
|
62
|
-
truncated,
|
|
63
|
-
estimatedTokens: estimateTokens(content),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
catch {
|
|
67
|
-
// File exists but couldn't be read, continue to next
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
// No memory file found
|
|
73
|
-
return {
|
|
74
|
-
found: false,
|
|
75
|
-
content: '',
|
|
76
|
-
filePath: null,
|
|
77
|
-
originalSize: 0,
|
|
78
|
-
truncated: false,
|
|
79
|
-
estimatedTokens: 0,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Check if a project memory file exists without loading it
|
|
84
|
-
*/
|
|
85
|
-
export function hasProjectMemory(cwd) {
|
|
86
|
-
const dir = cwd ?? process.cwd();
|
|
87
|
-
for (const filename of MEMORY_FILES) {
|
|
88
|
-
const filePath = path.join(dir, filename);
|
|
89
|
-
if (fs.existsSync(filePath)) {
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Get the size category for display purposes
|
|
97
|
-
*/
|
|
98
|
-
export function getSizeCategory(bytes) {
|
|
99
|
-
if (bytes < DEFAULT_WARN_SIZE) {
|
|
100
|
-
return 'small';
|
|
101
|
-
}
|
|
102
|
-
else if (bytes < DEFAULT_MAX_SIZE) {
|
|
103
|
-
return 'medium';
|
|
104
|
-
}
|
|
105
|
-
return 'large';
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Format bytes as human-readable string
|
|
8
|
+
* The previous CLI-only display helpers (hasProjectMemory / getSizeCategory /
|
|
9
|
+
* formatBytes) had no callers and were removed.
|
|
109
10
|
*/
|
|
110
|
-
export
|
|
111
|
-
if (bytes < 1024) {
|
|
112
|
-
return `${String(bytes)} B`;
|
|
113
|
-
}
|
|
114
|
-
else if (bytes < 1024 * 1024) {
|
|
115
|
-
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
116
|
-
}
|
|
117
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
118
|
-
}
|
|
11
|
+
export { loadProjectContextFiles as loadProjectMemory } from '@compilr-dev/sdk';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@compilr-dev/cli",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "AI-powered coding assistant CLI using @compilr-dev/agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,12 +55,12 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@anthropic-ai/sdk": "^0.74.0",
|
|
58
|
-
"@compilr-dev/agents": "^0.
|
|
58
|
+
"@compilr-dev/agents": "^0.6.0",
|
|
59
59
|
"@compilr-dev/agents-coding": "^1.0.4",
|
|
60
60
|
"@compilr-dev/editor-core": "^0.0.2",
|
|
61
|
-
"@compilr-dev/factory": "^0.1.
|
|
61
|
+
"@compilr-dev/factory": "^0.1.35",
|
|
62
62
|
"@compilr-dev/logger": "^0.1.0",
|
|
63
|
-
"@compilr-dev/sdk": "^0.
|
|
63
|
+
"@compilr-dev/sdk": "^0.15.0",
|
|
64
64
|
"@compilr-dev/ui-core": "^0.0.1",
|
|
65
65
|
"@modelcontextprotocol/sdk": "^1.23.0",
|
|
66
66
|
"ansi-escapes": "^7.3.0",
|