codeep 2.20.0 → 2.21.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/dist/acp/server.js +8 -4
- package/dist/renderer/App.js +0 -1
- package/dist/renderer/Input.d.ts +0 -1
- package/dist/renderer/Input.js +0 -1
- package/dist/renderer/commands.js +2 -3
- package/dist/renderer/components/Export.js +0 -2
- package/dist/renderer/components/Login.d.ts +0 -1
- package/dist/renderer/components/Login.js +0 -2
- package/dist/renderer/components/Logout.js +0 -2
- package/dist/renderer/components/Settings.d.ts +3 -0
- package/dist/renderer/components/Settings.js +0 -12
- package/dist/renderer/main.js +35 -56
- package/dist/utils/agent.js +5 -1
- package/dist/utils/codeepCloud.d.ts +30 -4
- package/dist/utils/codeepCloud.js +71 -19
- package/dist/utils/diffPreview.js +0 -1
- package/dist/utils/git.js +0 -1
- package/dist/utils/mcpStreamableHttp.d.ts +0 -1
- package/dist/utils/mcpStreamableHttp.js +0 -3
- package/dist/utils/personalities.js +0 -1
- package/dist/utils/skillBundles.js +0 -4
- package/dist/utils/smartContext.js +0 -18
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -2
- package/dist/renderer/components/Permission.d.ts +0 -24
- package/dist/renderer/components/Permission.js +0 -113
package/dist/acp/server.js
CHANGED
|
@@ -869,13 +869,17 @@ export function startAcpServer() {
|
|
|
869
869
|
transport.error(msg.id, -32602, `Unknown sessionId: ${params.sessionId}`);
|
|
870
870
|
return;
|
|
871
871
|
}
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
872
|
+
const { describeSyncFailure } = await import('../utils/codeepCloud.js');
|
|
873
|
+
const sync = await pullPersonalities();
|
|
874
|
+
if (!sync.ok) {
|
|
875
|
+
// The old contract collapsed every failure into one message that also
|
|
876
|
+
// covered "not linked", so a client could not tell an expired session
|
|
877
|
+
// from an unreachable server. Say which.
|
|
878
|
+
transport.error(msg.id, -32001, `Personality sync failed — ${describeSyncFailure(sync.reason)}.`);
|
|
875
879
|
return;
|
|
876
880
|
}
|
|
877
881
|
const list = personalityListResult(params.sessionId);
|
|
878
|
-
const result = { updated, ...list };
|
|
882
|
+
const result = { updated: sync.count, ...list };
|
|
879
883
|
transport.respond(msg.id, result);
|
|
880
884
|
}
|
|
881
885
|
// ── session/prompt ──────────────────────────────────────────────────────────
|
package/dist/renderer/App.js
CHANGED
|
@@ -1393,7 +1393,6 @@ export class App {
|
|
|
1393
1393
|
mentionItemCount: this.mention.items.length,
|
|
1394
1394
|
});
|
|
1395
1395
|
const layout = chatLayout(height, panelHeight);
|
|
1396
|
-
const mainHeight = layout.mainHeight;
|
|
1397
1396
|
const headerHeight = width >= 60 && height >= 16 ? 2 : 0;
|
|
1398
1397
|
const messagesStart = Math.min(layout.messagesEnd, headerHeight);
|
|
1399
1398
|
const messagesEnd = layout.messagesEnd;
|
package/dist/renderer/Input.d.ts
CHANGED
package/dist/renderer/Input.js
CHANGED
|
@@ -2102,7 +2102,6 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2102
2102
|
}
|
|
2103
2103
|
case 'profile': {
|
|
2104
2104
|
const subCmd = args[0]?.toLowerCase();
|
|
2105
|
-
const profileName = args[1] || args[0]; // /profile save name OR /profile name
|
|
2106
2105
|
if (!subCmd || subCmd === 'list') {
|
|
2107
2106
|
const profiles = listProfiles();
|
|
2108
2107
|
if (profiles.length === 0) {
|
|
@@ -2165,9 +2164,9 @@ Describe what this skill does. The agent reads this body verbatim when it invoke
|
|
|
2165
2164
|
}
|
|
2166
2165
|
case 'sync': {
|
|
2167
2166
|
const subCmd = args[0]?.toLowerCase() || 'all';
|
|
2168
|
-
const { pushLearning,
|
|
2167
|
+
const { pushLearning, pushProfiles, pullProfiles } = await import('../utils/codeepCloud.js');
|
|
2169
2168
|
const { getSyncToken } = await import('../config/index.js');
|
|
2170
|
-
const { loadGlobalPreferences
|
|
2169
|
+
const { loadGlobalPreferences } = await import('../utils/learning.js');
|
|
2171
2170
|
if (!getSyncToken()) {
|
|
2172
2171
|
ctx.app.notify('Not linked to codeep.dev. Run: codeep account');
|
|
2173
2172
|
break;
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
* Export panel component
|
|
3
3
|
*/
|
|
4
4
|
import { fg, style } from '../ansi.js';
|
|
5
|
-
// Primary color: #f02a30 (Codeep red)
|
|
6
|
-
const PRIMARY_COLOR = fg.rgb(240, 42, 48);
|
|
7
5
|
const FORMATS = [
|
|
8
6
|
{ id: 'md', name: 'Markdown', desc: 'Formatted with headers and separators' },
|
|
9
7
|
{ id: 'json', name: 'JSON', desc: 'Structured data format' },
|
|
@@ -14,13 +14,11 @@ const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
|
|
|
14
14
|
*/
|
|
15
15
|
export class LoginScreen {
|
|
16
16
|
screen;
|
|
17
|
-
input;
|
|
18
17
|
editor;
|
|
19
18
|
options;
|
|
20
19
|
showKey = false;
|
|
21
20
|
constructor(screen, input, options) {
|
|
22
21
|
this.screen = screen;
|
|
23
|
-
this.input = input;
|
|
24
22
|
this.editor = new LineEditor();
|
|
25
23
|
this.options = options;
|
|
26
24
|
}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settings screen component
|
|
3
3
|
*/
|
|
4
|
-
import { fg } from '../ansi.js';
|
|
5
4
|
import { config } from '../../config/index.js';
|
|
6
5
|
import { updateRateLimits } from '../../utils/ratelimit.js';
|
|
7
|
-
// Primary color: #f02a30 (Codeep red)
|
|
8
|
-
const PRIMARY_COLOR = fg.rgb(240, 42, 48);
|
|
9
|
-
const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
|
|
10
6
|
/**
|
|
11
7
|
* Write a value to the config for a given setting.
|
|
12
8
|
*
|
|
@@ -295,14 +291,6 @@ export const SETTINGS = [
|
|
|
295
291
|
/**
|
|
296
292
|
* Format value for display
|
|
297
293
|
*/
|
|
298
|
-
function formatValue(setting) {
|
|
299
|
-
const value = setting.getValue();
|
|
300
|
-
if (setting.type === 'select' && setting.options) {
|
|
301
|
-
const option = setting.options.find(o => o.value === value);
|
|
302
|
-
return option ? option.label : String(value);
|
|
303
|
-
}
|
|
304
|
-
return String(value);
|
|
305
|
-
}
|
|
306
294
|
/**
|
|
307
295
|
* Handle settings key
|
|
308
296
|
* Returns: { handled: boolean, close: boolean, notify?: string }
|
package/dist/renderer/main.js
CHANGED
|
@@ -9,7 +9,6 @@ import { App } from './App.js';
|
|
|
9
9
|
import { Screen } from './Screen.js';
|
|
10
10
|
import { Input } from './Input.js';
|
|
11
11
|
import { LoginScreen, renderProviderSelect } from './components/Login.js';
|
|
12
|
-
import { renderPermissionScreen, getPermissionOptions } from './components/Permission.js';
|
|
13
12
|
import { chat, setProjectContext } from '../api/index.js';
|
|
14
13
|
import { getZaiVisionConfig, getMinimaxMcpConfig, callZaiVisionApi, callMinimaxApi } from '../utils/mcpIntegration.js';
|
|
15
14
|
import { config, loadApiKey, loadAllApiKeys, getCurrentProvider, autoSaveSession, startNewSession, getCurrentSessionId, loadSession, listSessionsWithInfo, deleteSession, hasReadPermission, hasWritePermission, setProjectPermission, initializeAsProject, isManuallyInitializedProject, setApiKey, setProvider, getGithubId, } from '../config/index.js';
|
|
@@ -384,49 +383,6 @@ async function showLoginFlow() {
|
|
|
384
383
|
renderCurrentStep();
|
|
385
384
|
});
|
|
386
385
|
}
|
|
387
|
-
// ─── Permission flow (full-screen, pre-app) ───────────────────────────────────
|
|
388
|
-
async function showPermissionFlow() {
|
|
389
|
-
return new Promise((resolve) => {
|
|
390
|
-
const screen = new Screen();
|
|
391
|
-
const input = new Input();
|
|
392
|
-
let selectedIndex = 0;
|
|
393
|
-
const options = getPermissionOptions();
|
|
394
|
-
const isProject = isProjectDirectory(projectPath);
|
|
395
|
-
const currentPermission = hasWritePermission(projectPath)
|
|
396
|
-
? 'write'
|
|
397
|
-
: hasReadPermission(projectPath)
|
|
398
|
-
? 'read'
|
|
399
|
-
: 'none';
|
|
400
|
-
screen.init();
|
|
401
|
-
input.start();
|
|
402
|
-
const cleanup = () => { input.stop(); screen.cleanup(); };
|
|
403
|
-
const render = () => {
|
|
404
|
-
renderPermissionScreen(screen, {
|
|
405
|
-
projectPath, isProject, currentPermission,
|
|
406
|
-
onSelect: () => { }, onCancel: () => { },
|
|
407
|
-
}, selectedIndex);
|
|
408
|
-
};
|
|
409
|
-
input.onKey((event) => {
|
|
410
|
-
if (event.key === 'up') {
|
|
411
|
-
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
412
|
-
render();
|
|
413
|
-
}
|
|
414
|
-
else if (event.key === 'down') {
|
|
415
|
-
selectedIndex = Math.min(options.length - 1, selectedIndex + 1);
|
|
416
|
-
render();
|
|
417
|
-
}
|
|
418
|
-
else if (event.key === 'enter') {
|
|
419
|
-
cleanup();
|
|
420
|
-
resolve(options[selectedIndex]);
|
|
421
|
-
}
|
|
422
|
-
else if (event.key === 'escape') {
|
|
423
|
-
cleanup();
|
|
424
|
-
resolve('none');
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
render();
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
386
|
// ─── Session picker ───────────────────────────────────────────────────────────
|
|
431
387
|
function showSessionPickerInline() {
|
|
432
388
|
const sessions = listSessionsWithInfo(projectPath);
|
|
@@ -544,16 +500,32 @@ Commands (in chat):
|
|
|
544
500
|
// the user profile. Web-edited personalities replace their local copy
|
|
545
501
|
// after a safety backup; commands/profile retain additive merge rules.
|
|
546
502
|
const { pullPersonalities, pullCommands, pullUserProfile, getLastPersonalityPullBackupCount } = await import('../utils/codeepCloud.js');
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
503
|
+
// Report all three outcomes, not just the interesting one. Printing only
|
|
504
|
+
// on count > 0 made a failed sync look identical to a sync with nothing
|
|
505
|
+
// new — silence meant either, and the user could not tell which.
|
|
506
|
+
const { describeSyncFailure } = await import('../utils/codeepCloud.js');
|
|
507
|
+
const personalities = await pullPersonalities();
|
|
508
|
+
if (!personalities.ok) {
|
|
509
|
+
console.log(` Could not pull agents — ${describeSyncFailure(personalities.reason)}.`);
|
|
510
|
+
}
|
|
511
|
+
else if (personalities.count > 0) {
|
|
512
|
+
console.log(` Pulled ${personalities.count} personalit${personalities.count === 1 ? 'y' : 'ies'}.`);
|
|
550
513
|
const backups = getLastPersonalityPullBackupCount();
|
|
551
514
|
if (backups > 0)
|
|
552
515
|
console.log(` Backed up ${backups} replaced local cop${backups === 1 ? 'y' : 'ies'} in ~/.codeep/backups/personalities/.`);
|
|
553
516
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
517
|
+
else if (personalities.removed === 0) {
|
|
518
|
+
console.log(' Agents already up to date.');
|
|
519
|
+
}
|
|
520
|
+
if (personalities.ok && personalities.removed > 0) {
|
|
521
|
+
console.log(` Removed ${personalities.removed} agent${personalities.removed === 1 ? '' : 's'} deleted on codeep.dev (backed up first).`);
|
|
522
|
+
}
|
|
523
|
+
const commands = await pullCommands();
|
|
524
|
+
if (!commands.ok) {
|
|
525
|
+
console.log(` Could not pull custom commands — ${describeSyncFailure(commands.reason)}.`);
|
|
526
|
+
}
|
|
527
|
+
else if (commands.count > 0) {
|
|
528
|
+
console.log(` Pulled ${commands.count} custom command${commands.count === 1 ? '' : 's'}.`);
|
|
557
529
|
}
|
|
558
530
|
const profPulled = await pullUserProfile();
|
|
559
531
|
if (profPulled === 1) {
|
|
@@ -597,13 +569,20 @@ Commands (in chat):
|
|
|
597
569
|
}
|
|
598
570
|
// Also push portable personal config — personalities + commands + profile.
|
|
599
571
|
const { pushPersonalities, pushCommands, pushUserProfile } = await import('../utils/codeepCloud.js');
|
|
600
|
-
const
|
|
601
|
-
|
|
602
|
-
|
|
572
|
+
const { describeSyncFailure } = await import('../utils/codeepCloud.js');
|
|
573
|
+
const personalities = await pushPersonalities();
|
|
574
|
+
if (!personalities.ok) {
|
|
575
|
+
console.log(` Could not push agents — ${describeSyncFailure(personalities.reason)}.`);
|
|
576
|
+
}
|
|
577
|
+
else if (personalities.count > 0) {
|
|
578
|
+
console.log(` Pushed ${personalities.count} personalit${personalities.count === 1 ? 'y' : 'ies'}.`);
|
|
579
|
+
}
|
|
580
|
+
const commands = await pushCommands();
|
|
581
|
+
if (!commands.ok) {
|
|
582
|
+
console.log(` Could not push custom commands — ${describeSyncFailure(commands.reason)}.`);
|
|
603
583
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
console.log(` Pushed ${cCount} custom command${cCount === 1 ? '' : 's'}.`);
|
|
584
|
+
else if (commands.count > 0) {
|
|
585
|
+
console.log(` Pushed ${commands.count} custom command${commands.count === 1 ? '' : 's'}.`);
|
|
607
586
|
}
|
|
608
587
|
if (await pushUserProfile()) {
|
|
609
588
|
console.log(' Pushed your profile (about you).');
|
package/dist/utils/agent.js
CHANGED
|
@@ -205,7 +205,11 @@ export async function runAgent(prompt, projectContext, options = {}) {
|
|
|
205
205
|
// Start history session for undo support. Skipped for nested (delegated)
|
|
206
206
|
// runs so we don't reset the parent's currentSession singleton — the
|
|
207
207
|
// sub-agent's actions still record into the parent's open session.
|
|
208
|
-
|
|
208
|
+
// The return value is unused — startSession's point here is the side
|
|
209
|
+
// effect of opening the history session. Binding it hid that from
|
|
210
|
+
// noUnusedLocals, so the dead binding is gone and the call stays.
|
|
211
|
+
if (!opts.nested)
|
|
212
|
+
startSession(prompt, projectContext.root || process.cwd());
|
|
209
213
|
// Task planning phase (if enabled)
|
|
210
214
|
// Use planning for complex keywords or multi-word prompts
|
|
211
215
|
let taskPlan = null;
|
|
@@ -84,12 +84,37 @@ declare function readFileBundle(kind: 'personalities' | 'commands'): Record<stri
|
|
|
84
84
|
* files. Only writes files that don't already exist (additive merge —
|
|
85
85
|
* never clobber local edits). Returns the count of newly written files. */
|
|
86
86
|
declare function writeFileBundle(kind: 'personalities' | 'commands', items: Record<string, string>): number;
|
|
87
|
+
/** Apply the server's explicit deletion list.
|
|
88
|
+
*
|
|
89
|
+
* Only names the server named. Absence from `items` is deliberately NOT a
|
|
90
|
+
* deletion signal: an expired session, the wrong account, or a truncated
|
|
91
|
+
* response all yield an empty `items`, and deleting on absence would wipe
|
|
92
|
+
* every local agent. Project-scoped agents in `.codeep/personalities/` are
|
|
93
|
+
* not cloud-owned and are never touched — only the global directory is.
|
|
94
|
+
* Every removal is backed up first, and a failed backup cancels the delete. */
|
|
95
|
+
declare function applyPersonalityTombstones(deleted: readonly string[]): number;
|
|
87
96
|
declare function writePulledPersonalityBundle(items: Record<string, string>): number;
|
|
88
|
-
|
|
97
|
+
/** Why a sync attempt produced nothing. Reported so a silent failure cannot
|
|
98
|
+
* look like a successful no-op — the two were indistinguishable when every
|
|
99
|
+
* path returned `null`, and a user watching `codeep account sync` print
|
|
100
|
+
* nothing had no way to tell which had happened. */
|
|
101
|
+
export type SyncFailure = 'not-linked' | 'unreachable' | 'rejected' | 'malformed';
|
|
102
|
+
/** Success carries a count (which may legitimately be 0 — nothing new), plus
|
|
103
|
+
* how many local agents the server's tombstone list removed. */
|
|
104
|
+
export type SyncResult = {
|
|
105
|
+
ok: true;
|
|
106
|
+
count: number;
|
|
107
|
+
removed: number;
|
|
108
|
+
} | {
|
|
109
|
+
ok: false;
|
|
110
|
+
reason: SyncFailure;
|
|
111
|
+
};
|
|
112
|
+
export declare function describeSyncFailure(reason: SyncFailure): string;
|
|
113
|
+
export declare const pullPersonalities: () => Promise<SyncResult>;
|
|
89
114
|
export declare const getLastPersonalityPullBackupCount: () => number;
|
|
90
|
-
export declare const pushPersonalities: () => Promise<
|
|
91
|
-
export declare const pullCommands: () => Promise<
|
|
92
|
-
export declare const pushCommands: () => Promise<
|
|
115
|
+
export declare const pushPersonalities: () => Promise<SyncResult>;
|
|
116
|
+
export declare const pullCommands: () => Promise<SyncResult>;
|
|
117
|
+
export declare const pushCommands: () => Promise<SyncResult>;
|
|
93
118
|
/**
|
|
94
119
|
* Sync session conversation history to codeep.dev.
|
|
95
120
|
* Only user/assistant messages are sent — system messages are filtered out.
|
|
@@ -184,4 +209,5 @@ export declare const _globalDirForTest: typeof globalDir;
|
|
|
184
209
|
export declare const _readFileBundleForTest: typeof readFileBundle;
|
|
185
210
|
export declare const _writeFileBundleForTest: typeof writeFileBundle;
|
|
186
211
|
export declare const _writePulledPersonalityBundleForTest: typeof writePulledPersonalityBundle;
|
|
212
|
+
export declare const _applyPersonalityTombstonesForTest: typeof applyPersonalityTombstones;
|
|
187
213
|
export {};
|
|
@@ -301,6 +301,52 @@ function writeFileBundle(kind, items) {
|
|
|
301
301
|
* file so web edits actually take effect, but every divergent local body is
|
|
302
302
|
* first copied to ~/.codeep/backups/personalities/. */
|
|
303
303
|
let lastPersonalityPullBackupCount = 0;
|
|
304
|
+
/** Copy an about-to-be-replaced-or-removed personality into the backup dir.
|
|
305
|
+
* Shared so a deletion is backed up by exactly the same rules as an overwrite
|
|
306
|
+
* — nothing local is ever lost without a copy first. */
|
|
307
|
+
function backupLocalPersonality(name, body) {
|
|
308
|
+
const backupDir = join(homedir(), '.codeep', 'backups', 'personalities');
|
|
309
|
+
if (!existsSync(backupDir))
|
|
310
|
+
mkdirSync(backupDir, { recursive: true });
|
|
311
|
+
const suffix = new Date().toISOString().replace(/[:.]/g, '-');
|
|
312
|
+
let backupPath = join(backupDir, `${name}-${suffix}.md`);
|
|
313
|
+
let collision = 1;
|
|
314
|
+
while (existsSync(backupPath)) {
|
|
315
|
+
backupPath = join(backupDir, `${name}-${suffix}-${collision++}.md`);
|
|
316
|
+
}
|
|
317
|
+
writeFileSync(backupPath, body);
|
|
318
|
+
lastPersonalityPullBackupCount++;
|
|
319
|
+
}
|
|
320
|
+
/** Apply the server's explicit deletion list.
|
|
321
|
+
*
|
|
322
|
+
* Only names the server named. Absence from `items` is deliberately NOT a
|
|
323
|
+
* deletion signal: an expired session, the wrong account, or a truncated
|
|
324
|
+
* response all yield an empty `items`, and deleting on absence would wipe
|
|
325
|
+
* every local agent. Project-scoped agents in `.codeep/personalities/` are
|
|
326
|
+
* not cloud-owned and are never touched — only the global directory is.
|
|
327
|
+
* Every removal is backed up first, and a failed backup cancels the delete. */
|
|
328
|
+
function applyPersonalityTombstones(deleted) {
|
|
329
|
+
const dir = globalDir('personalities');
|
|
330
|
+
if (!existsSync(dir))
|
|
331
|
+
return 0;
|
|
332
|
+
let removed = 0;
|
|
333
|
+
for (const name of deleted) {
|
|
334
|
+
if (typeof name !== 'string' || !/^[a-z0-9][a-z0-9-]*$/.test(name) || name.length > 64)
|
|
335
|
+
continue;
|
|
336
|
+
const filePath = join(dir, `${name}.md`);
|
|
337
|
+
if (!existsSync(filePath))
|
|
338
|
+
continue;
|
|
339
|
+
try {
|
|
340
|
+
backupLocalPersonality(name, readFileSync(filePath, 'utf8'));
|
|
341
|
+
unlinkSync(filePath);
|
|
342
|
+
removed++;
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
// A failed backup must not become a deletion — leave the file alone.
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return removed;
|
|
349
|
+
}
|
|
304
350
|
function writePulledPersonalityBundle(items) {
|
|
305
351
|
lastPersonalityPullBackupCount = 0;
|
|
306
352
|
const dir = globalDir('personalities');
|
|
@@ -317,17 +363,7 @@ function writePulledPersonalityBundle(items) {
|
|
|
317
363
|
const local = readFileSync(filePath, 'utf8');
|
|
318
364
|
if (local === body)
|
|
319
365
|
continue;
|
|
320
|
-
|
|
321
|
-
if (!existsSync(backupDir))
|
|
322
|
-
mkdirSync(backupDir, { recursive: true });
|
|
323
|
-
let suffix = new Date().toISOString().replace(/[:.]/g, '-');
|
|
324
|
-
let backupPath = join(backupDir, `${name}-${suffix}.md`);
|
|
325
|
-
let collision = 1;
|
|
326
|
-
while (existsSync(backupPath)) {
|
|
327
|
-
backupPath = join(backupDir, `${name}-${suffix}-${collision++}.md`);
|
|
328
|
-
}
|
|
329
|
-
writeFileSync(backupPath, local);
|
|
330
|
-
lastPersonalityPullBackupCount++;
|
|
366
|
+
backupLocalPersonality(name, local);
|
|
331
367
|
}
|
|
332
368
|
// Same-directory rename is atomic on supported local filesystems: a
|
|
333
369
|
// crash cannot leave a half-written active personality.
|
|
@@ -348,39 +384,54 @@ function writePulledPersonalityBundle(items) {
|
|
|
348
384
|
}
|
|
349
385
|
return written;
|
|
350
386
|
}
|
|
387
|
+
export function describeSyncFailure(reason) {
|
|
388
|
+
switch (reason) {
|
|
389
|
+
case 'not-linked': return 'not linked to codeep.dev — run: codeep account';
|
|
390
|
+
case 'unreachable': return "couldn't reach codeep.dev";
|
|
391
|
+
case 'rejected': return 'codeep.dev refused the request — try signing in again';
|
|
392
|
+
case 'malformed': return 'codeep.dev sent a response this version cannot read';
|
|
393
|
+
}
|
|
394
|
+
}
|
|
351
395
|
async function pullBundle(kind) {
|
|
352
396
|
const syncToken = getSyncToken();
|
|
353
397
|
if (!syncToken)
|
|
354
|
-
return
|
|
398
|
+
return { ok: false, reason: 'not-linked' };
|
|
355
399
|
const res = await fetchWithRetry(`${API_BASE}/api/${kind}`, { headers: { 'x-sync-token': syncToken } });
|
|
356
400
|
if (!res?.ok)
|
|
357
|
-
return
|
|
401
|
+
return { ok: false, reason: 'unreachable' };
|
|
358
402
|
try {
|
|
359
403
|
const data = await res.json();
|
|
360
404
|
if (!data.ok)
|
|
361
|
-
return
|
|
362
|
-
|
|
405
|
+
return { ok: false, reason: 'rejected' };
|
|
406
|
+
const count = kind === 'personalities'
|
|
363
407
|
? writePulledPersonalityBundle(data.items ?? {})
|
|
364
408
|
: writeFileBundle(kind, data.items ?? {});
|
|
409
|
+
// A missing `deleted` field means "no deletions" — never "delete
|
|
410
|
+
// everything". Older servers simply omit it, and a client that treated the
|
|
411
|
+
// omission as a full tombstone list would empty the user's agent folder.
|
|
412
|
+
const removed = kind === 'personalities' && Array.isArray(data.deleted)
|
|
413
|
+
? applyPersonalityTombstones(data.deleted)
|
|
414
|
+
: 0;
|
|
415
|
+
return { ok: true, count, removed };
|
|
365
416
|
}
|
|
366
417
|
catch {
|
|
367
|
-
return
|
|
418
|
+
return { ok: false, reason: 'malformed' };
|
|
368
419
|
}
|
|
369
420
|
}
|
|
370
421
|
async function pushBundle(kind) {
|
|
371
422
|
const syncToken = getSyncToken();
|
|
372
423
|
if (!syncToken)
|
|
373
|
-
return
|
|
424
|
+
return { ok: false, reason: 'not-linked' };
|
|
374
425
|
const items = readFileBundle(kind);
|
|
375
426
|
const count = Object.keys(items).length;
|
|
376
427
|
if (count === 0)
|
|
377
|
-
return 0;
|
|
428
|
+
return { ok: true, count: 0, removed: 0 };
|
|
378
429
|
const res = await fetchWithRetry(`${API_BASE}/api/${kind}`, {
|
|
379
430
|
method: 'POST',
|
|
380
431
|
headers: { 'Content-Type': 'application/json', 'x-sync-token': syncToken },
|
|
381
432
|
body: JSON.stringify({ items }),
|
|
382
433
|
});
|
|
383
|
-
return res?.ok ? count :
|
|
434
|
+
return res?.ok ? { ok: true, count, removed: 0 } : { ok: false, reason: 'unreachable' };
|
|
384
435
|
}
|
|
385
436
|
export const pullPersonalities = () => pullBundle('personalities');
|
|
386
437
|
export const getLastPersonalityPullBackupCount = () => lastPersonalityPullBackupCount;
|
|
@@ -699,3 +750,4 @@ export const _globalDirForTest = globalDir;
|
|
|
699
750
|
export const _readFileBundleForTest = readFileBundle;
|
|
700
751
|
export const _writeFileBundleForTest = writeFileBundle;
|
|
701
752
|
export const _writePulledPersonalityBundleForTest = writePulledPersonalityBundle;
|
|
753
|
+
export const _applyPersonalityTombstonesForTest = applyPersonalityTombstones;
|
|
@@ -15,7 +15,6 @@ export function generateDiff(oldContent, newContent, contextLines = 3) {
|
|
|
15
15
|
let oldIdx = 0;
|
|
16
16
|
let newIdx = 0;
|
|
17
17
|
let currentHunk = null;
|
|
18
|
-
let pendingContext = [];
|
|
19
18
|
for (const [oldMatch, newMatch] of lcs) {
|
|
20
19
|
// Handle deletions
|
|
21
20
|
while (oldIdx < oldMatch) {
|
package/dist/utils/git.js
CHANGED
|
@@ -286,7 +286,6 @@ export function generateCommitMessage(prompt, actions) {
|
|
|
286
286
|
const hasWrites = actions.some(a => a.type === 'write');
|
|
287
287
|
const hasEdits = actions.some(a => a.type === 'edit');
|
|
288
288
|
const hasDeletes = actions.some(a => a.type === 'delete');
|
|
289
|
-
const hasCommands = actions.some(a => a.type === 'command');
|
|
290
289
|
// Determine prefix
|
|
291
290
|
let prefix = 'chore';
|
|
292
291
|
// Check prompt for common patterns
|
|
@@ -32,7 +32,6 @@ export declare class StreamableHttpClient {
|
|
|
32
32
|
private notificationAbort;
|
|
33
33
|
private stopped;
|
|
34
34
|
/** True after the server has set a session id (i.e. it tracks state). */
|
|
35
|
-
private get hasServerSession();
|
|
36
35
|
constructor(opts: StreamableHttpOptions);
|
|
37
36
|
/**
|
|
38
37
|
* Issue a JSON-RPC frame as POST. Reply may be a single JSON response
|
|
@@ -28,9 +28,6 @@ export class StreamableHttpClient {
|
|
|
28
28
|
notificationAbort = null;
|
|
29
29
|
stopped = false;
|
|
30
30
|
/** True after the server has set a session id (i.e. it tracks state). */
|
|
31
|
-
get hasServerSession() {
|
|
32
|
-
return this.sessionId !== null;
|
|
33
|
-
}
|
|
34
31
|
constructor(opts) {
|
|
35
32
|
this.opts = opts;
|
|
36
33
|
}
|
|
@@ -648,7 +648,6 @@ export function isPersonalityToolCallAllowed(personality, toolCall, registeredMc
|
|
|
648
648
|
if (tool === 'execute_command') {
|
|
649
649
|
if (personality.tools?.includes('terminal'))
|
|
650
650
|
return true;
|
|
651
|
-
const command = commandName(toolCall);
|
|
652
651
|
if (personality.tools?.includes('git') && isRestrictedGitCommandAllowed(toolCall))
|
|
653
652
|
return true;
|
|
654
653
|
if (personality.tools?.includes('tests') && isTestCommand(toolCall))
|
|
@@ -52,12 +52,10 @@ export function parseFrontmatter(raw) {
|
|
|
52
52
|
return { meta: {}, body: normalised };
|
|
53
53
|
const meta = {};
|
|
54
54
|
const lines = match[1].split('\n');
|
|
55
|
-
let currentKey = null;
|
|
56
55
|
let currentList = null;
|
|
57
56
|
for (const rawLine of lines) {
|
|
58
57
|
const line = rawLine.replace(/\s+$/, '');
|
|
59
58
|
if (!line.trim()) {
|
|
60
|
-
currentKey = null;
|
|
61
59
|
currentList = null;
|
|
62
60
|
continue;
|
|
63
61
|
}
|
|
@@ -75,7 +73,6 @@ export function parseFrontmatter(raw) {
|
|
|
75
73
|
let value = kv[2];
|
|
76
74
|
if (value === '') {
|
|
77
75
|
// Empty → expecting a block list below
|
|
78
|
-
currentKey = key;
|
|
79
76
|
currentList = [];
|
|
80
77
|
meta[key] = currentList;
|
|
81
78
|
continue;
|
|
@@ -89,7 +86,6 @@ export function parseFrontmatter(raw) {
|
|
|
89
86
|
value = stripQuotes(value);
|
|
90
87
|
}
|
|
91
88
|
meta[key] = value;
|
|
92
|
-
currentKey = null;
|
|
93
89
|
currentList = null;
|
|
94
90
|
}
|
|
95
91
|
return { meta, body: match[2].trimStart() };
|
|
@@ -9,24 +9,6 @@ import { logger } from './logger.js';
|
|
|
9
9
|
const MAX_CONTEXT_SIZE = 50000;
|
|
10
10
|
const MAX_FILES = 15;
|
|
11
11
|
// File extensions we care about
|
|
12
|
-
const CODE_EXTENSIONS = new Set([
|
|
13
|
-
'.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
14
|
-
'.py', '.pyw',
|
|
15
|
-
'.go',
|
|
16
|
-
'.rs',
|
|
17
|
-
'.php', '.phtml',
|
|
18
|
-
'.java', '.kt', '.scala',
|
|
19
|
-
'.cs', '.fs',
|
|
20
|
-
'.rb',
|
|
21
|
-
'.swift',
|
|
22
|
-
'.c', '.cpp', '.h', '.hpp',
|
|
23
|
-
'.vue', '.svelte',
|
|
24
|
-
'.css', '.scss', '.less',
|
|
25
|
-
'.html', '.htm',
|
|
26
|
-
'.json', '.yaml', '.yml', '.toml',
|
|
27
|
-
'.sql',
|
|
28
|
-
'.md',
|
|
29
|
-
]);
|
|
30
12
|
/**
|
|
31
13
|
* Extract imports/requires from file content
|
|
32
14
|
*/
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "2.
|
|
1
|
+
export declare const VERSION = "2.21.0";
|
package/dist/version.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// AUTO-GENERATED by scripts/gen-version.js — do not edit by hand.
|
|
2
2
|
// Baked from package.json at build time so the bun-compiled binary reports
|
|
3
3
|
// the right version (it has no package.json on disk to read at runtime).
|
|
4
|
-
export const VERSION = '2.
|
|
4
|
+
export const VERSION = '2.21.0';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.21.0",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@napi-rs/keyring": "^1.3.0",
|
|
43
43
|
"clipboardy": "^4.0.0",
|
|
44
44
|
"conf": "^13.1.0",
|
|
45
|
-
"js-yaml": "^4.1
|
|
45
|
+
"js-yaml": "^4.3.1",
|
|
46
46
|
"open": "^10.0.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Permission screen for granting folder access
|
|
3
|
-
*/
|
|
4
|
-
import { Screen } from '../Screen';
|
|
5
|
-
export type PermissionLevel = 'none' | 'read' | 'write';
|
|
6
|
-
export interface PermissionOptions {
|
|
7
|
-
projectPath: string;
|
|
8
|
-
isProject: boolean;
|
|
9
|
-
currentPermission: PermissionLevel;
|
|
10
|
-
onSelect: (permission: PermissionLevel) => void;
|
|
11
|
-
onCancel: () => void;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Render permission screen
|
|
15
|
-
*/
|
|
16
|
-
export declare function renderPermissionScreen(screen: Screen, options: PermissionOptions, selectedIndex: number): void;
|
|
17
|
-
/**
|
|
18
|
-
* Get permission options array for easy indexing
|
|
19
|
-
*/
|
|
20
|
-
export declare function getPermissionOptions(): PermissionLevel[];
|
|
21
|
-
/**
|
|
22
|
-
* Truncate path for display
|
|
23
|
-
*/
|
|
24
|
-
export declare function truncatePath(path: string, maxLen: number): string;
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Permission screen for granting folder access
|
|
3
|
-
*/
|
|
4
|
-
import { fg, style } from '../ansi.js';
|
|
5
|
-
import { createBox, centerBox } from './Box.js';
|
|
6
|
-
// Primary color: #f02a30 (Codeep red)
|
|
7
|
-
const PRIMARY_COLOR = fg.rgb(240, 42, 48);
|
|
8
|
-
const PRIMARY_BRIGHT = fg.rgb(255, 80, 85);
|
|
9
|
-
/**
|
|
10
|
-
* Render permission screen
|
|
11
|
-
*/
|
|
12
|
-
export function renderPermissionScreen(screen, options, selectedIndex) {
|
|
13
|
-
const { width, height } = screen.getSize();
|
|
14
|
-
screen.clear();
|
|
15
|
-
// Title
|
|
16
|
-
const title = '═══ Folder Access ═══';
|
|
17
|
-
const titleX = Math.floor((width - title.length) / 2);
|
|
18
|
-
screen.write(titleX, 1, title, PRIMARY_COLOR + style.bold);
|
|
19
|
-
// Box
|
|
20
|
-
const boxWidth = Math.min(60, width - 4);
|
|
21
|
-
const boxHeight = 14;
|
|
22
|
-
const { x: boxX, y: boxY } = centerBox(width, height, boxWidth, boxHeight);
|
|
23
|
-
const boxLines = createBox({
|
|
24
|
-
x: boxX,
|
|
25
|
-
y: boxY,
|
|
26
|
-
width: boxWidth,
|
|
27
|
-
height: boxHeight,
|
|
28
|
-
style: 'rounded',
|
|
29
|
-
borderColor: PRIMARY_COLOR,
|
|
30
|
-
});
|
|
31
|
-
for (const line of boxLines) {
|
|
32
|
-
screen.writeLine(line.y, line.text, line.style);
|
|
33
|
-
}
|
|
34
|
-
// Content
|
|
35
|
-
const contentX = boxX + 3;
|
|
36
|
-
let contentY = boxY + 2;
|
|
37
|
-
// Project path
|
|
38
|
-
const displayPath = truncatePath(options.projectPath, boxWidth - 8);
|
|
39
|
-
screen.write(contentX, contentY, 'Project:', fg.gray);
|
|
40
|
-
screen.write(contentX + 9, contentY, displayPath, fg.white);
|
|
41
|
-
contentY += 2;
|
|
42
|
-
// Description
|
|
43
|
-
if (options.isProject) {
|
|
44
|
-
screen.write(contentX, contentY, 'This looks like a project folder.', fg.white);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
screen.write(contentX, contentY, 'Grant access to enable AI assistance.', fg.white);
|
|
48
|
-
}
|
|
49
|
-
contentY += 2;
|
|
50
|
-
// Options
|
|
51
|
-
const permissionOptions = [
|
|
52
|
-
{
|
|
53
|
-
level: 'read',
|
|
54
|
-
label: 'Read Only',
|
|
55
|
-
desc: 'AI can read files, no modifications'
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
level: 'write',
|
|
59
|
-
label: 'Read & Write',
|
|
60
|
-
desc: 'AI can read and modify files (Agent mode)'
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
level: 'none',
|
|
64
|
-
label: 'No Access',
|
|
65
|
-
desc: 'Chat without project context'
|
|
66
|
-
},
|
|
67
|
-
];
|
|
68
|
-
for (let i = 0; i < permissionOptions.length; i++) {
|
|
69
|
-
const opt = permissionOptions[i];
|
|
70
|
-
const isSelected = i === selectedIndex;
|
|
71
|
-
const prefix = isSelected ? '► ' : ' ';
|
|
72
|
-
// Label
|
|
73
|
-
const labelStyle = isSelected ? PRIMARY_BRIGHT + style.bold : fg.white;
|
|
74
|
-
screen.write(contentX, contentY, prefix + opt.label, labelStyle);
|
|
75
|
-
// Description on same line
|
|
76
|
-
const descX = contentX + 20;
|
|
77
|
-
screen.write(descX, contentY, opt.desc, fg.gray);
|
|
78
|
-
contentY++;
|
|
79
|
-
}
|
|
80
|
-
// Current permission indicator
|
|
81
|
-
contentY++;
|
|
82
|
-
if (options.currentPermission !== 'none') {
|
|
83
|
-
screen.write(contentX, contentY, `Current: ${options.currentPermission}`, fg.yellow);
|
|
84
|
-
}
|
|
85
|
-
// Footer
|
|
86
|
-
const footerY = height - 2;
|
|
87
|
-
screen.write(2, footerY, '↑↓ Navigate | Enter Select | Esc Skip', fg.gray);
|
|
88
|
-
screen.showCursor(false);
|
|
89
|
-
screen.fullRender();
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Get permission options array for easy indexing
|
|
93
|
-
*/
|
|
94
|
-
export function getPermissionOptions() {
|
|
95
|
-
return ['read', 'write', 'none'];
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Truncate path for display
|
|
99
|
-
*/
|
|
100
|
-
export function truncatePath(path, maxLen) {
|
|
101
|
-
if (path.length <= maxLen)
|
|
102
|
-
return path;
|
|
103
|
-
const parts = path.split('/');
|
|
104
|
-
let result = parts[parts.length - 1];
|
|
105
|
-
for (let i = parts.length - 2; i >= 0; i--) {
|
|
106
|
-
const newResult = parts[i] + '/' + result;
|
|
107
|
-
if (newResult.length + 3 > maxLen) {
|
|
108
|
-
return '.../' + result;
|
|
109
|
-
}
|
|
110
|
-
result = newResult;
|
|
111
|
-
}
|
|
112
|
-
return result;
|
|
113
|
-
}
|