@compilr-dev/cli 0.8.0 → 0.8.1

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.
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
@@ -3719,6 +3719,12 @@ export class ReplV2 {
3719
3719
  // Only run main() when executed directly (not when imported)
3720
3720
  if (import.meta.url === `file://${process.argv[1]}`) {
3721
3721
  const repl = new ReplV2();
3722
+ // Restore the hardware cursor on exit (overlays/menus hide it for nav-only
3723
+ // screens; don't leave the user's terminal cursor invisible after quit).
3724
+ process.on('exit', () => {
3725
+ if (process.stdout.isTTY)
3726
+ process.stdout.write('\x1b[?25h');
3727
+ });
3722
3728
  // Handle SIGINT — restore raw mode before exit so the parent shell
3723
3729
  // doesn't inherit a broken terminal state.
3724
3730
  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 { getTeamCheckpointer, recordTeamActivity } from '../multi-agent/index.js';
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 currentProject = getCurrentProject();
69
- const projectId = currentProject?.id ?? null;
70
- const store = getTeamCheckpointer().getArtifactStore(projectId);
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
- terminal.showCursor();
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
- * Loads project-level instructions from COMPILR.md or CLAUDE.md files.
5
- * Provides size guards and warnings for large files.
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
- * @param options - Configuration options
33
- * @returns Result with content and metadata
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 declare function formatBytes(bytes: number): string;
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
- * Loads project-level instructions from COMPILR.md or CLAUDE.md files.
5
- * Provides size guards and warnings for large files.
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
- * @param options - Configuration options
31
- * @returns Result with content and metadata
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 function formatBytes(bytes) {
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.0",
3
+ "version": "0.8.1",
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.5.9",
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.30",
61
+ "@compilr-dev/factory": "^0.1.35",
62
62
  "@compilr-dev/logger": "^0.1.0",
63
- "@compilr-dev/sdk": "^0.11.0",
63
+ "@compilr-dev/sdk": "^0.14.0",
64
64
  "@compilr-dev/ui-core": "^0.0.1",
65
65
  "@modelcontextprotocol/sdk": "^1.23.0",
66
66
  "ansi-escapes": "^7.3.0",