@dmsdc-ai/aigentry-brain 0.0.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.
- package/bin/aigentry-brain-mcp.mjs +17 -0
- package/bin/aigentry-brain-setup.mjs +17 -0
- package/bin/aigentry-brain.mjs +17 -0
- package/dist/audit/AuditTrail.d.ts +15 -0
- package/dist/audit/AuditTrail.js +43 -0
- package/dist/audit/AuditTrail.js.map +1 -0
- package/dist/backend/GitBackend.d.ts +27 -0
- package/dist/backend/GitBackend.js +201 -0
- package/dist/backend/GitBackend.js.map +1 -0
- package/dist/backend/SyncBackend.d.ts +13 -0
- package/dist/backend/SyncBackend.js +2 -0
- package/dist/backend/SyncBackend.js.map +1 -0
- package/dist/cli/AutoUpdater.d.ts +7 -0
- package/dist/cli/AutoUpdater.js +275 -0
- package/dist/cli/AutoUpdater.js.map +1 -0
- package/dist/cli/ConfigStore.d.ts +17 -0
- package/dist/cli/ConfigStore.js +50 -0
- package/dist/cli/ConfigStore.js.map +1 -0
- package/dist/cli/GhIntegration.d.ts +55 -0
- package/dist/cli/GhIntegration.js +192 -0
- package/dist/cli/GhIntegration.js.map +1 -0
- package/dist/cli/Prerequisites.d.ts +48 -0
- package/dist/cli/Prerequisites.js +182 -0
- package/dist/cli/Prerequisites.js.map +1 -0
- package/dist/cli/SetupWizard.d.ts +26 -0
- package/dist/cli/SetupWizard.js +407 -0
- package/dist/cli/SetupWizard.js.map +1 -0
- package/dist/cli/braincli.d.ts +2 -0
- package/dist/cli/braincli.js +260 -0
- package/dist/cli/braincli.js.map +1 -0
- package/dist/cli/setup.d.ts +25 -0
- package/dist/cli/setup.js +238 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/context/ConfidenceGuard.d.ts +11 -0
- package/dist/context/ConfidenceGuard.js +32 -0
- package/dist/context/ConfidenceGuard.js.map +1 -0
- package/dist/context/ContextBudgetPolicy.d.ts +21 -0
- package/dist/context/ContextBudgetPolicy.js +136 -0
- package/dist/context/ContextBudgetPolicy.js.map +1 -0
- package/dist/context/ContextPacker.d.ts +30 -0
- package/dist/context/ContextPacker.js +219 -0
- package/dist/context/ContextPacker.js.map +1 -0
- package/dist/context/ContextRestoreService.d.ts +39 -0
- package/dist/context/ContextRestoreService.js +134 -0
- package/dist/context/ContextRestoreService.js.map +1 -0
- package/dist/contract/BrainContract.d.ts +93 -0
- package/dist/contract/BrainContract.js +245 -0
- package/dist/contract/BrainContract.js.map +1 -0
- package/dist/core/ContentDedup.d.ts +43 -0
- package/dist/core/ContentDedup.js +145 -0
- package/dist/core/ContentDedup.js.map +1 -0
- package/dist/core/EntityGraph.d.ts +66 -0
- package/dist/core/EntityGraph.js +196 -0
- package/dist/core/EntityGraph.js.map +1 -0
- package/dist/core/EntrySchema.d.ts +46 -0
- package/dist/core/EntrySchema.js +4 -0
- package/dist/core/EntrySchema.js.map +1 -0
- package/dist/core/GraphStore.d.ts +16 -0
- package/dist/core/GraphStore.js +29 -0
- package/dist/core/GraphStore.js.map +1 -0
- package/dist/core/MemoryDecay.d.ts +49 -0
- package/dist/core/MemoryDecay.js +113 -0
- package/dist/core/MemoryDecay.js.map +1 -0
- package/dist/core/ProfileFormat.d.ts +38 -0
- package/dist/core/ProfileFormat.js +254 -0
- package/dist/core/ProfileFormat.js.map +1 -0
- package/dist/core/ProfileManager.d.ts +80 -0
- package/dist/core/ProfileManager.js +269 -0
- package/dist/core/ProfileManager.js.map +1 -0
- package/dist/core/SchemaMigration.d.ts +12 -0
- package/dist/core/SchemaMigration.js +34 -0
- package/dist/core/SchemaMigration.js.map +1 -0
- package/dist/crypto/AgeCrypto.d.ts +26 -0
- package/dist/crypto/AgeCrypto.js +101 -0
- package/dist/crypto/AgeCrypto.js.map +1 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/BrainMcpServer.d.ts +13 -0
- package/dist/mcp/BrainMcpServer.js +223 -0
- package/dist/mcp/BrainMcpServer.js.map +1 -0
- package/dist/mcp/cli.d.ts +2 -0
- package/dist/mcp/cli.js +107 -0
- package/dist/mcp/cli.js.map +1 -0
- package/dist/metrics/ResumeMetrics.d.ts +20 -0
- package/dist/metrics/ResumeMetrics.js +46 -0
- package/dist/metrics/ResumeMetrics.js.map +1 -0
- package/dist/peers/PeerStore.d.ts +17 -0
- package/dist/peers/PeerStore.js +65 -0
- package/dist/peers/PeerStore.js.map +1 -0
- package/dist/policy/PolicyEngine.d.ts +16 -0
- package/dist/policy/PolicyEngine.js +70 -0
- package/dist/policy/PolicyEngine.js.map +1 -0
- package/dist/policy/PolicyEnvelope.d.ts +7 -0
- package/dist/policy/PolicyEnvelope.js +2 -0
- package/dist/policy/PolicyEnvelope.js.map +1 -0
- package/dist/policy/RetentionParser.d.ts +6 -0
- package/dist/policy/RetentionParser.js +48 -0
- package/dist/policy/RetentionParser.js.map +1 -0
- package/dist/shared/AtomicWrite.d.ts +1 -0
- package/dist/shared/AtomicWrite.js +11 -0
- package/dist/shared/AtomicWrite.js.map +1 -0
- package/dist/shared/Clock.d.ts +11 -0
- package/dist/shared/Clock.js +17 -0
- package/dist/shared/Clock.js.map +1 -0
- package/dist/shared/Config.d.ts +15 -0
- package/dist/shared/Config.js +67 -0
- package/dist/shared/Config.js.map +1 -0
- package/dist/shared/Encryption.d.ts +19 -0
- package/dist/shared/Encryption.js +47 -0
- package/dist/shared/Encryption.js.map +1 -0
- package/dist/shared/Logger.d.ts +12 -0
- package/dist/shared/Logger.js +40 -0
- package/dist/shared/Logger.js.map +1 -0
- package/dist/shared/Mutex.d.ts +7 -0
- package/dist/shared/Mutex.js +34 -0
- package/dist/shared/Mutex.js.map +1 -0
- package/dist/shared/SetupErrors.d.ts +31 -0
- package/dist/shared/SetupErrors.js +155 -0
- package/dist/shared/SetupErrors.js.map +1 -0
- package/dist/status/SyncStatusStore.d.ts +17 -0
- package/dist/status/SyncStatusStore.js +48 -0
- package/dist/status/SyncStatusStore.js.map +1 -0
- package/dist/sync/AutoCommitter.d.ts +17 -0
- package/dist/sync/AutoCommitter.js +68 -0
- package/dist/sync/AutoCommitter.js.map +1 -0
- package/dist/sync/ConflictResolver.d.ts +7 -0
- package/dist/sync/ConflictResolver.js +56 -0
- package/dist/sync/ConflictResolver.js.map +1 -0
- package/dist/sync/PushPullManager.d.ts +23 -0
- package/dist/sync/PushPullManager.js +155 -0
- package/dist/sync/PushPullManager.js.map +1 -0
- package/dist/sync/SyncEngine.d.ts +43 -0
- package/dist/sync/SyncEngine.js +145 -0
- package/dist/sync/SyncEngine.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const root = join(__dirname, '..');
|
|
9
|
+
const distEntry = join(root, 'dist', 'mcp', 'cli.js');
|
|
10
|
+
const srcEntry = join(root, 'src', 'mcp', 'cli.ts');
|
|
11
|
+
|
|
12
|
+
if (existsSync(distEntry)) {
|
|
13
|
+
await import(distEntry);
|
|
14
|
+
} else {
|
|
15
|
+
const tsx = join(root, 'node_modules', '.bin', 'tsx');
|
|
16
|
+
execFileSync(tsx, [srcEntry, ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const root = join(__dirname, '..');
|
|
9
|
+
const distEntry = join(root, 'dist', 'cli', 'setup.js');
|
|
10
|
+
const srcEntry = join(root, 'src', 'cli', 'setup.ts');
|
|
11
|
+
|
|
12
|
+
if (existsSync(distEntry)) {
|
|
13
|
+
await import(distEntry);
|
|
14
|
+
} else {
|
|
15
|
+
const tsx = join(root, 'node_modules', '.bin', 'tsx');
|
|
16
|
+
execFileSync(tsx, [srcEntry, ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const root = join(__dirname, '..');
|
|
9
|
+
const distEntry = join(root, 'dist', 'cli', 'braincli.js');
|
|
10
|
+
const srcEntry = join(root, 'src', 'cli', 'braincli.ts');
|
|
11
|
+
|
|
12
|
+
if (existsSync(distEntry)) {
|
|
13
|
+
await import(distEntry);
|
|
14
|
+
} else {
|
|
15
|
+
const tsx = join(root, 'node_modules', '.bin', 'tsx');
|
|
16
|
+
execFileSync(tsx, [srcEntry, ...process.argv.slice(2)], { stdio: 'inherit' });
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type AuditAction = 'append' | 'erase' | 'query' | 'sync.pull' | 'sync.push';
|
|
2
|
+
export interface AuditEvent {
|
|
3
|
+
ts: string;
|
|
4
|
+
action: AuditAction;
|
|
5
|
+
entry_id?: string;
|
|
6
|
+
device_id: string;
|
|
7
|
+
detail?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export declare class AuditTrail {
|
|
10
|
+
private filePath;
|
|
11
|
+
constructor(filePath: string);
|
|
12
|
+
log(event: AuditEvent): Promise<void>;
|
|
13
|
+
readAll(): Promise<AuditEvent[]>;
|
|
14
|
+
getFilePath(): string;
|
|
15
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { appendFile, readFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
export class AuditTrail {
|
|
4
|
+
filePath;
|
|
5
|
+
constructor(filePath) {
|
|
6
|
+
this.filePath = filePath;
|
|
7
|
+
}
|
|
8
|
+
async log(event) {
|
|
9
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
10
|
+
const line = JSON.stringify(event) + '\n';
|
|
11
|
+
await appendFile(this.filePath, line, 'utf8');
|
|
12
|
+
}
|
|
13
|
+
async readAll() {
|
|
14
|
+
let content;
|
|
15
|
+
try {
|
|
16
|
+
content = await readFile(this.filePath, 'utf8');
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
const e = err;
|
|
20
|
+
if (e.code === 'ENOENT') {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
const events = [];
|
|
26
|
+
for (const line of content.split('\n')) {
|
|
27
|
+
const trimmed = line.trim();
|
|
28
|
+
if (trimmed.length === 0)
|
|
29
|
+
continue;
|
|
30
|
+
try {
|
|
31
|
+
events.push(JSON.parse(trimmed));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Skip malformed lines
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return events;
|
|
38
|
+
}
|
|
39
|
+
getFilePath() {
|
|
40
|
+
return this.filePath;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=AuditTrail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuditTrail.js","sourceRoot":"","sources":["../../src/audit/AuditTrail.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,MAAM,OAAO,UAAU;IACb,QAAQ,CAAS;IAEzB,YAAY,QAAgB;QAC1B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAiB;QACzB,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;QAC1C,MAAM,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,OAAO;QACX,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,GAA4B,CAAC;YACvC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACxB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,MAAM,MAAM,GAAiB,EAAE,CAAC;QAChC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YACnC,IAAI,CAAC;gBACH,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAe,CAAC,CAAC;YACjD,CAAC;YAAC,MAAM,CAAC;gBACP,uBAAuB;YACzB,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type SimpleGit } from 'simple-git';
|
|
2
|
+
import type { SyncBackend } from './SyncBackend.js';
|
|
3
|
+
export declare class GitBackend implements SyncBackend {
|
|
4
|
+
private git;
|
|
5
|
+
private repoPath;
|
|
6
|
+
private branch;
|
|
7
|
+
private logger;
|
|
8
|
+
init(repoPath: string, remoteUrl?: string): Promise<void>;
|
|
9
|
+
private ensureGitignore;
|
|
10
|
+
pull(): Promise<void>;
|
|
11
|
+
/**
|
|
12
|
+
* TOFU (Trust-On-First-Use) multi-device recipient sync.
|
|
13
|
+
* After pull: if this device's key is not in recipients, add it and re-encrypt.
|
|
14
|
+
* Uses age-encryption npm — no sops binary needed.
|
|
15
|
+
*/
|
|
16
|
+
syncRecipients(): Promise<void>;
|
|
17
|
+
commit(message: string): Promise<void>;
|
|
18
|
+
push(): Promise<void>;
|
|
19
|
+
fetch(): Promise<void>;
|
|
20
|
+
rebaseAbort(): Promise<void>;
|
|
21
|
+
hasChanges(): Promise<boolean>;
|
|
22
|
+
readFileAtRef(ref: string, filePath: string): Promise<string>;
|
|
23
|
+
resetToRef(ref: string): Promise<void>;
|
|
24
|
+
getGit(): SimpleGit;
|
|
25
|
+
getRepoPath(): string;
|
|
26
|
+
getBranch(): string;
|
|
27
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import simpleGit from 'simple-git';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { createLogger } from '../shared/Logger.js';
|
|
6
|
+
import { readKeyFile, encryptBlock, decryptBlock, AgeDecryptError, } from '../crypto/AgeCrypto.js';
|
|
7
|
+
export class GitBackend {
|
|
8
|
+
git;
|
|
9
|
+
repoPath;
|
|
10
|
+
branch = 'main';
|
|
11
|
+
logger = createLogger('GitBackend');
|
|
12
|
+
async init(repoPath, remoteUrl) {
|
|
13
|
+
this.repoPath = repoPath;
|
|
14
|
+
const hasGitDir = existsSync(join(repoPath, '.git'));
|
|
15
|
+
if (!hasGitDir && remoteUrl) {
|
|
16
|
+
this.logger.info('Cloning remote repo', { remoteUrl, repoPath });
|
|
17
|
+
const parentGit = simpleGit();
|
|
18
|
+
await parentGit.clone(remoteUrl, repoPath, ['--depth', '1']);
|
|
19
|
+
this.git = simpleGit(repoPath);
|
|
20
|
+
await this.git.addConfig('user.email', 'brain@aigentry.local');
|
|
21
|
+
await this.git.addConfig('user.name', 'aigentry-brain');
|
|
22
|
+
}
|
|
23
|
+
else if (!hasGitDir) {
|
|
24
|
+
await mkdir(repoPath, { recursive: true });
|
|
25
|
+
this.git = simpleGit(repoPath);
|
|
26
|
+
await this.git.init();
|
|
27
|
+
await this.git.addConfig('user.email', 'brain@aigentry.local');
|
|
28
|
+
await this.git.addConfig('user.name', 'aigentry-brain');
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
this.git = simpleGit(repoPath);
|
|
32
|
+
if (remoteUrl) {
|
|
33
|
+
try {
|
|
34
|
+
await this.git.addRemote('origin', remoteUrl);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
this.logger.debug('Remote exists, updating URL', { remoteUrl });
|
|
38
|
+
await this.git.remote(['set-url', 'origin', remoteUrl]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Ensure .aigentry directory and .gitignore
|
|
43
|
+
const aigentryDir = join(repoPath, '.aigentry');
|
|
44
|
+
await mkdir(aigentryDir, { recursive: true });
|
|
45
|
+
await this.ensureGitignore(repoPath);
|
|
46
|
+
}
|
|
47
|
+
async ensureGitignore(repoPath) {
|
|
48
|
+
const gitignorePath = join(repoPath, '.gitignore');
|
|
49
|
+
const ignoreEntries = [
|
|
50
|
+
'.aigentry/sync-status.json',
|
|
51
|
+
'*.tmp',
|
|
52
|
+
'.DS_Store',
|
|
53
|
+
'*.yaml.bak',
|
|
54
|
+
];
|
|
55
|
+
let existing = '';
|
|
56
|
+
if (existsSync(gitignorePath)) {
|
|
57
|
+
existing = await readFile(gitignorePath, 'utf8');
|
|
58
|
+
}
|
|
59
|
+
const missing = ignoreEntries.filter(e => !existing.includes(e));
|
|
60
|
+
if (missing.length > 0) {
|
|
61
|
+
const newContent = existing.trimEnd() + '\n' + missing.join('\n') + '\n';
|
|
62
|
+
await writeFile(gitignorePath, newContent, 'utf8');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async pull() {
|
|
66
|
+
try {
|
|
67
|
+
await this.git.pull('origin', this.branch, { '--rebase': 'true' });
|
|
68
|
+
// TOFU: sync recipients after pull
|
|
69
|
+
await this.syncRecipients();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
const remotes = await this.git.remote([]);
|
|
73
|
+
if (!remotes) {
|
|
74
|
+
this.logger.debug('No remote configured, skipping pull');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.logger.warn('Pull failed', { error: String(err) });
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* TOFU (Trust-On-First-Use) multi-device recipient sync.
|
|
83
|
+
* After pull: if this device's key is not in recipients, add it and re-encrypt.
|
|
84
|
+
* Uses age-encryption npm — no sops binary needed.
|
|
85
|
+
*/
|
|
86
|
+
async syncRecipients() {
|
|
87
|
+
const encJsonPath = join(this.repoPath, 'memory-profile.enc.json');
|
|
88
|
+
// Read local machine's public key
|
|
89
|
+
let localKeyPair;
|
|
90
|
+
try {
|
|
91
|
+
localKeyPair = readKeyFile();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// No age key on this machine — skip
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Check if encrypted profile exists
|
|
98
|
+
if (!existsSync(encJsonPath))
|
|
99
|
+
return;
|
|
100
|
+
let encProfile;
|
|
101
|
+
try {
|
|
102
|
+
encProfile = JSON.parse(readFileSync(encJsonPath, 'utf-8'));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
// Skip if plaintext fallback or not age-block format
|
|
108
|
+
if (encProfile._aigentry?.format !== 'age-block')
|
|
109
|
+
return;
|
|
110
|
+
const recipients = encProfile._aigentry.recipients ?? [];
|
|
111
|
+
// Check if our key is already a recipient
|
|
112
|
+
if (recipients.includes(localKeyPair.recipient))
|
|
113
|
+
return;
|
|
114
|
+
// Add our key as a new recipient
|
|
115
|
+
this.logger.info('Adding local age key to recipients (TOFU)');
|
|
116
|
+
recipients.push(localKeyPair.recipient);
|
|
117
|
+
encProfile._aigentry.recipients = recipients;
|
|
118
|
+
// Try to re-encrypt all entries with updated recipients
|
|
119
|
+
try {
|
|
120
|
+
const reEncrypted = {};
|
|
121
|
+
for (const [id, ct] of Object.entries(encProfile.entries)) {
|
|
122
|
+
const plaintext = await decryptBlock(ct, localKeyPair.identity);
|
|
123
|
+
reEncrypted[id] = await encryptBlock(plaintext, recipients);
|
|
124
|
+
}
|
|
125
|
+
encProfile.entries = reEncrypted;
|
|
126
|
+
encProfile._aigentry.modified = new Date().toISOString();
|
|
127
|
+
this.logger.info('Re-encrypted entries with new recipient');
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (err instanceof AgeDecryptError) {
|
|
131
|
+
// Cannot decrypt — we're a new device, entries were encrypted before our key was added.
|
|
132
|
+
// Just save updated recipients list. An existing device will re-encrypt on next sync.
|
|
133
|
+
this.logger.info('Cannot decrypt (new device) — saved recipient key. Waiting for existing device to re-encrypt.');
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
this.logger.warn('Re-encryption failed', { error: String(err) });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Write updated profile
|
|
141
|
+
try {
|
|
142
|
+
await writeFile(encJsonPath, JSON.stringify(encProfile, null, 2), 'utf-8');
|
|
143
|
+
if (await this.hasChanges()) {
|
|
144
|
+
await this.commit('chore: add new device age key (TOFU)');
|
|
145
|
+
try {
|
|
146
|
+
await this.push();
|
|
147
|
+
this.logger.info('Pushed new device key to remote');
|
|
148
|
+
}
|
|
149
|
+
catch (pushErr) {
|
|
150
|
+
this.logger.debug('Could not push TOFU key (will sync later)', { error: String(pushErr) });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
this.logger.warn('Failed to save updated recipients', { error: String(err) });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async commit(message) {
|
|
159
|
+
await this.git.add('.');
|
|
160
|
+
try {
|
|
161
|
+
await this.git.commit(message);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
this.logger.debug('Nothing to commit');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async push() {
|
|
168
|
+
await this.git.push(['-u', 'origin', this.branch]);
|
|
169
|
+
}
|
|
170
|
+
async fetch() {
|
|
171
|
+
await this.git.fetch('origin');
|
|
172
|
+
}
|
|
173
|
+
async rebaseAbort() {
|
|
174
|
+
try {
|
|
175
|
+
await this.git.rebase({ '--abort': null });
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
this.logger.debug('Not in rebase state, skipping abort');
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async hasChanges() {
|
|
182
|
+
const status = await this.git.status();
|
|
183
|
+
return !status.isClean();
|
|
184
|
+
}
|
|
185
|
+
async readFileAtRef(ref, filePath) {
|
|
186
|
+
return this.git.show([`${ref}:${filePath}`]);
|
|
187
|
+
}
|
|
188
|
+
async resetToRef(ref) {
|
|
189
|
+
await this.git.reset(['--soft', ref]);
|
|
190
|
+
}
|
|
191
|
+
getGit() {
|
|
192
|
+
return this.git;
|
|
193
|
+
}
|
|
194
|
+
getRepoPath() {
|
|
195
|
+
return this.repoPath;
|
|
196
|
+
}
|
|
197
|
+
getBranch() {
|
|
198
|
+
return this.branch;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=GitBackend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GitBackend.js","sourceRoot":"","sources":["../../src/backend/GitBackend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface SyncBackend {
|
|
2
|
+
init(repoPath: string, remoteUrl?: string): Promise<void>;
|
|
3
|
+
pull(): Promise<void>;
|
|
4
|
+
commit(message: string): Promise<void>;
|
|
5
|
+
push(): Promise<void>;
|
|
6
|
+
fetch(): Promise<void>;
|
|
7
|
+
rebaseAbort(): Promise<void>;
|
|
8
|
+
hasChanges(): Promise<boolean>;
|
|
9
|
+
readFileAtRef(ref: string, filePath: string): Promise<string>;
|
|
10
|
+
resetToRef(ref: string): Promise<void>;
|
|
11
|
+
getRepoPath(): string;
|
|
12
|
+
getBranch(): string;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SyncBackend.js","sourceRoot":"","sources":["../../src/backend/SyncBackend.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, openSync, writeSync, closeSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { createLogger } from '../shared/Logger.js';
|
|
6
|
+
/** Write directly to terminal, bypassing npm's output suppression */
|
|
7
|
+
function log(msg) {
|
|
8
|
+
const data = msg + '\n';
|
|
9
|
+
for (const ttyPath of ['/dev/tty', 'CON']) {
|
|
10
|
+
try {
|
|
11
|
+
const fd = openSync(ttyPath, 'w');
|
|
12
|
+
writeSync(fd, data);
|
|
13
|
+
closeSync(fd);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
catch { /* try next */ }
|
|
17
|
+
}
|
|
18
|
+
process.stderr.write(data);
|
|
19
|
+
}
|
|
20
|
+
const logger = createLogger('AutoUpdater');
|
|
21
|
+
const PACKAGE_NAME = 'aigentry-brain';
|
|
22
|
+
const CACHE_FILE = join(homedir(), '.aigentry', '.update-cache.json');
|
|
23
|
+
const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
24
|
+
function readCache() {
|
|
25
|
+
try {
|
|
26
|
+
if (!existsSync(CACHE_FILE))
|
|
27
|
+
return null;
|
|
28
|
+
return JSON.parse(readFileSync(CACHE_FILE, 'utf8'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function writeCache(cache) {
|
|
35
|
+
try {
|
|
36
|
+
const dir = join(homedir(), '.aigentry');
|
|
37
|
+
mkdirSync(dir, { recursive: true });
|
|
38
|
+
writeFileSync(CACHE_FILE, JSON.stringify(cache), 'utf8');
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Best effort
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function getCurrentVersion() {
|
|
45
|
+
try {
|
|
46
|
+
const raw = execSync(`npm list -g ${PACKAGE_NAME} --json --depth=0 2>/dev/null`, { encoding: 'utf8', timeout: 10000 });
|
|
47
|
+
const parsed = JSON.parse(raw);
|
|
48
|
+
return parsed.dependencies?.[PACKAGE_NAME]?.version ?? '0.0.0';
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return '0.0.0';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Find the installation directory of aigentry-brain by reading MCP config.
|
|
56
|
+
* Returns the package root (parent of node_modules/aigentry-brain).
|
|
57
|
+
*/
|
|
58
|
+
function findInstallDir() {
|
|
59
|
+
try {
|
|
60
|
+
const mcpConfigPath = join(homedir(), '.claude', '.mcp.json');
|
|
61
|
+
if (!existsSync(mcpConfigPath))
|
|
62
|
+
return null;
|
|
63
|
+
const mcpConfig = JSON.parse(readFileSync(mcpConfigPath, 'utf8'));
|
|
64
|
+
const cliPath = mcpConfig?.mcpServers?.['aigentry-brain']?.args?.[0];
|
|
65
|
+
if (!cliPath)
|
|
66
|
+
return null;
|
|
67
|
+
// cliPath can be:
|
|
68
|
+
// bin/aigentry-brain-mcp.mjs (new: bin wrapper)
|
|
69
|
+
// dist/mcp/cli.js (legacy: direct dist reference)
|
|
70
|
+
// In both cases, resolve up to the aigentry-brain package root,
|
|
71
|
+
// then up to the parent project root (if installed as dependency).
|
|
72
|
+
let brainRoot;
|
|
73
|
+
if (cliPath.includes('/bin/')) {
|
|
74
|
+
// bin/aigentry-brain-mcp.mjs → 1 level up to package root
|
|
75
|
+
brainRoot = join(cliPath, '..');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// dist/mcp/cli.js → 3 levels up to package root
|
|
79
|
+
brainRoot = join(cliPath, '..', '..', '..');
|
|
80
|
+
}
|
|
81
|
+
// If this is inside node_modules, the parent project root is 2 levels up
|
|
82
|
+
if (brainRoot.includes('node_modules')) {
|
|
83
|
+
const projectRoot = join(brainRoot, '..', '..');
|
|
84
|
+
if (existsSync(join(projectRoot, 'package.json')))
|
|
85
|
+
return projectRoot;
|
|
86
|
+
}
|
|
87
|
+
// Otherwise brainRoot IS the project root (local dev)
|
|
88
|
+
if (existsSync(join(brainRoot, 'package.json')))
|
|
89
|
+
return brainRoot;
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if aigentry-brain was installed from GitHub (not npm registry).
|
|
98
|
+
*/
|
|
99
|
+
function isGitHubInstall() {
|
|
100
|
+
const installDir = findInstallDir();
|
|
101
|
+
if (!installDir)
|
|
102
|
+
return { isGithub: false, installDir: null };
|
|
103
|
+
try {
|
|
104
|
+
const pkgPath = join(installDir, 'package.json');
|
|
105
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
106
|
+
const dep = pkg.dependencies?.[PACKAGE_NAME] ?? '';
|
|
107
|
+
const isGithub = dep.includes('github:') || dep.includes('git+') || dep.includes('git://');
|
|
108
|
+
return { isGithub, installDir };
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return { isGithub: false, installDir: null };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function compareVersions(a, b) {
|
|
115
|
+
const pa = a.split('.').map(Number);
|
|
116
|
+
const pb = b.split('.').map(Number);
|
|
117
|
+
for (let i = 0; i < 3; i++) {
|
|
118
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0))
|
|
119
|
+
return 1;
|
|
120
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0))
|
|
121
|
+
return -1;
|
|
122
|
+
}
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check for updates and auto-install if available.
|
|
127
|
+
* Runs in background, never blocks the caller.
|
|
128
|
+
*/
|
|
129
|
+
export function checkAndUpdate(options) {
|
|
130
|
+
// Skip in CI/test environments
|
|
131
|
+
if (process.env.CI || process.env.VITEST || process.env.NODE_ENV === 'test') {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const cache = readCache();
|
|
135
|
+
const now = Date.now();
|
|
136
|
+
// Skip if checked recently
|
|
137
|
+
if (cache && (now - cache.lastCheck) < CHECK_INTERVAL_MS) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const doCheck = async () => {
|
|
141
|
+
try {
|
|
142
|
+
// Check if GitHub-based install
|
|
143
|
+
const { isGithub, installDir } = isGitHubInstall();
|
|
144
|
+
if (isGithub && installDir) {
|
|
145
|
+
// GitHub install: check latest commit hash
|
|
146
|
+
const latestHash = execSync('git ls-remote https://github.com/dmsdc-ai/aigentry-brain.git HEAD 2>/dev/null', { encoding: 'utf8', timeout: 15000 }).split('\t')[0]?.trim();
|
|
147
|
+
if (!latestHash) {
|
|
148
|
+
writeCache({ lastCheck: now, latestVersion: null });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Compare with installed commit
|
|
152
|
+
const installedPkgPath = join(installDir, 'node_modules', PACKAGE_NAME, 'package.json');
|
|
153
|
+
let needsUpdate = true;
|
|
154
|
+
try {
|
|
155
|
+
const installed = JSON.parse(readFileSync(installedPkgPath, 'utf8'));
|
|
156
|
+
// npm stores resolved git hash in _resolved field
|
|
157
|
+
if (installed._resolved?.includes(latestHash.slice(0, 7))) {
|
|
158
|
+
needsUpdate = false;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch { /* assume needs update */ }
|
|
162
|
+
if (needsUpdate) {
|
|
163
|
+
writeCache({ lastCheck: now, latestVersion: latestHash.slice(0, 7) });
|
|
164
|
+
logger.info(`Updating ${PACKAGE_NAME} from GitHub`);
|
|
165
|
+
log(`[${PACKAGE_NAME}] Updating from GitHub...`);
|
|
166
|
+
try {
|
|
167
|
+
execSync(`npm install --install-links ${PACKAGE_NAME}@github:dmsdc-ai/aigentry-brain`, {
|
|
168
|
+
cwd: installDir,
|
|
169
|
+
encoding: 'utf8',
|
|
170
|
+
timeout: 120000,
|
|
171
|
+
stdio: 'pipe',
|
|
172
|
+
});
|
|
173
|
+
log(`[${PACKAGE_NAME}] Updated from GitHub ✅`);
|
|
174
|
+
}
|
|
175
|
+
catch (installErr) {
|
|
176
|
+
logger.warn('GitHub auto-update failed', { error: String(installErr) });
|
|
177
|
+
log(`[${PACKAGE_NAME}] Auto-update failed. Run manually in ${installDir}: npm install aigentry-brain@github:dmsdc-ai/aigentry-brain`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
writeCache({ lastCheck: now, latestVersion: latestHash.slice(0, 7) });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// npm registry install: original behavior
|
|
186
|
+
const latestRaw = execSync(`npm view ${PACKAGE_NAME} version 2>/dev/null`, {
|
|
187
|
+
encoding: 'utf8',
|
|
188
|
+
timeout: 15000,
|
|
189
|
+
}).trim();
|
|
190
|
+
writeCache({ lastCheck: now, latestVersion: latestRaw });
|
|
191
|
+
const currentVersion = getCurrentVersion();
|
|
192
|
+
if (compareVersions(latestRaw, currentVersion) > 0) {
|
|
193
|
+
logger.info(`Updating ${PACKAGE_NAME}: ${currentVersion} → ${latestRaw}`);
|
|
194
|
+
log(`[${PACKAGE_NAME}] Updating to v${latestRaw}...`);
|
|
195
|
+
try {
|
|
196
|
+
execSync(`npm install -g ${PACKAGE_NAME}@${latestRaw}`, {
|
|
197
|
+
encoding: 'utf8',
|
|
198
|
+
timeout: 120000,
|
|
199
|
+
stdio: 'pipe',
|
|
200
|
+
});
|
|
201
|
+
log(`[${PACKAGE_NAME}] Updated to v${latestRaw} ✅`);
|
|
202
|
+
}
|
|
203
|
+
catch (installErr) {
|
|
204
|
+
logger.warn('Auto-update install failed', { error: String(installErr) });
|
|
205
|
+
log(`[${PACKAGE_NAME}] Auto-update failed. Run manually: npm install -g ${PACKAGE_NAME}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
// Network error, npm down, etc. — silently skip
|
|
212
|
+
writeCache({ lastCheck: now, latestVersion: null });
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
if (options?.blocking) {
|
|
216
|
+
// For CLI usage: run synchronously
|
|
217
|
+
try {
|
|
218
|
+
const { isGithub, installDir } = isGitHubInstall();
|
|
219
|
+
if (isGithub && installDir) {
|
|
220
|
+
const latestHash = execSync('git ls-remote https://github.com/dmsdc-ai/aigentry-brain.git HEAD 2>/dev/null', { encoding: 'utf8', timeout: 15000 }).split('\t')[0]?.trim();
|
|
221
|
+
if (latestHash) {
|
|
222
|
+
const installedPkgPath = join(installDir, 'node_modules', PACKAGE_NAME, 'package.json');
|
|
223
|
+
let needsUpdate = true;
|
|
224
|
+
try {
|
|
225
|
+
const installed = JSON.parse(readFileSync(installedPkgPath, 'utf8'));
|
|
226
|
+
if (installed._resolved?.includes(latestHash.slice(0, 7)))
|
|
227
|
+
needsUpdate = false;
|
|
228
|
+
}
|
|
229
|
+
catch { /* assume needs update */ }
|
|
230
|
+
if (needsUpdate) {
|
|
231
|
+
log(`[${PACKAGE_NAME}] Updating from GitHub...`);
|
|
232
|
+
try {
|
|
233
|
+
execSync(`npm install --install-links ${PACKAGE_NAME}@github:dmsdc-ai/aigentry-brain`, {
|
|
234
|
+
cwd: installDir, encoding: 'utf8', timeout: 120000, stdio: 'pipe',
|
|
235
|
+
});
|
|
236
|
+
log(`[${PACKAGE_NAME}] Updated from GitHub ✅`);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
log(`[${PACKAGE_NAME}] Auto-update failed. Run manually in ${installDir}: npm install aigentry-brain@github:dmsdc-ai/aigentry-brain`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
writeCache({ lastCheck: now, latestVersion: latestHash.slice(0, 7) });
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
const latestRaw = execSync(`npm view ${PACKAGE_NAME} version 2>/dev/null`, {
|
|
247
|
+
encoding: 'utf8',
|
|
248
|
+
timeout: 15000,
|
|
249
|
+
}).trim();
|
|
250
|
+
writeCache({ lastCheck: now, latestVersion: latestRaw });
|
|
251
|
+
const currentVersion = getCurrentVersion();
|
|
252
|
+
if (compareVersions(latestRaw, currentVersion) > 0) {
|
|
253
|
+
log(`[${PACKAGE_NAME}] Updating to v${latestRaw}...`);
|
|
254
|
+
try {
|
|
255
|
+
execSync(`npm install -g ${PACKAGE_NAME}@${latestRaw}`, {
|
|
256
|
+
encoding: 'utf8', timeout: 120000, stdio: 'pipe',
|
|
257
|
+
});
|
|
258
|
+
log(`[${PACKAGE_NAME}] Updated to v${latestRaw} ✅`);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
log(`[${PACKAGE_NAME}] Auto-update failed. Run manually: npm install -g ${PACKAGE_NAME}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
writeCache({ lastCheck: now, latestVersion: null });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
// For MCP server: run in background, don't block
|
|
272
|
+
doCheck().catch(() => { });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=AutoUpdater.js.map
|