@superpms/memory-cli 0.1.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/README.md +54 -0
- package/bin/memory.mjs +72 -0
- package/commands/cache.mjs +99 -0
- package/commands/init.mjs +152 -0
- package/commands/login.mjs +84 -0
- package/commands/skill.mjs +112 -0
- package/commands/status.mjs +28 -0
- package/commands/update.mjs +32 -0
- package/commands/vendor.mjs +171 -0
- package/commands/version.mjs +29 -0
- package/commands/will.mjs +270 -0
- package/lib/api-client.mjs +155 -0
- package/lib/credentials.mjs +48 -0
- package/lib/project-store.mjs +44 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# @superpms/memory-cli
|
|
2
|
+
|
|
3
|
+
Memory system CLI — manage global layer, will sync, vendor distribution, and Cloud Brain integration.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @superpms/memory-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Initialize global layer
|
|
15
|
+
memory init --global
|
|
16
|
+
|
|
17
|
+
# Check status
|
|
18
|
+
memory status
|
|
19
|
+
|
|
20
|
+
# Sync will
|
|
21
|
+
memory will push # project → global
|
|
22
|
+
memory will pull # global → project
|
|
23
|
+
|
|
24
|
+
# Update vendor
|
|
25
|
+
memory update # global → current project
|
|
26
|
+
memory update --cloud # Cloud Brain → global → project
|
|
27
|
+
memory update --cloud --all # → all registered projects
|
|
28
|
+
|
|
29
|
+
# Cloud Brain
|
|
30
|
+
memory login
|
|
31
|
+
memory vendor publish --version=1.0.0
|
|
32
|
+
memory vendor download
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Commands
|
|
36
|
+
|
|
37
|
+
| Command | Description |
|
|
38
|
+
|---------|-------------|
|
|
39
|
+
| `init --global` | Initialize global memory layer |
|
|
40
|
+
| `login` | Authenticate with Cloud Brain |
|
|
41
|
+
| `status` | Show current project memory status |
|
|
42
|
+
| `will <push\|pull\|sync\|broadcast>` | Will synchronization |
|
|
43
|
+
| `will <cloud-push\|cloud-pull>` | Cloud will sync |
|
|
44
|
+
| `update [--cloud] [--all]` | Vendor distribution |
|
|
45
|
+
| `vendor <publish\|latest\|changelog\|download>` | Cloud vendor management |
|
|
46
|
+
| `skill <list\|promote\|demote>` | Skill layer management |
|
|
47
|
+
| `cache <status\|clean>` | Cache management |
|
|
48
|
+
| `projects` | List registered projects |
|
|
49
|
+
| `version` | Show version info |
|
|
50
|
+
|
|
51
|
+
## Requirements
|
|
52
|
+
|
|
53
|
+
- Node.js >= 18
|
|
54
|
+
- No external dependencies
|
package/bin/memory.mjs
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { parseArgs } from 'node:util';
|
|
3
|
+
import { resolve, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
const COMMANDS = {
|
|
9
|
+
init: () => import('../commands/init.mjs'),
|
|
10
|
+
login: () => import('../commands/login.mjs'),
|
|
11
|
+
update: () => import('../commands/update.mjs'),
|
|
12
|
+
will: () => import('../commands/will.mjs'),
|
|
13
|
+
vendor: () => import('../commands/vendor.mjs'),
|
|
14
|
+
skill: () => import('../commands/skill.mjs'),
|
|
15
|
+
cache: () => import('../commands/cache.mjs'),
|
|
16
|
+
status: () => import('../commands/status.mjs'),
|
|
17
|
+
version: () => import('../commands/version.mjs'),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const command = process.argv[2];
|
|
21
|
+
const args = process.argv.slice(3);
|
|
22
|
+
|
|
23
|
+
if (!command || command === '--help' || command === '-h') {
|
|
24
|
+
console.log(`memory — CLI for memory system
|
|
25
|
+
|
|
26
|
+
Commands:
|
|
27
|
+
init Initialize memory project (downloads vendor + will from Cloud)
|
|
28
|
+
login Login to Cloud Brain
|
|
29
|
+
login register Register a new account
|
|
30
|
+
login status Check login status
|
|
31
|
+
login logout Remove saved credentials
|
|
32
|
+
update Update vendor from Cloud Brain
|
|
33
|
+
will publish Publish will to Cloud Brain
|
|
34
|
+
will pull Pull will from Cloud Brain
|
|
35
|
+
will status Show will status
|
|
36
|
+
will versions List cloud will versions
|
|
37
|
+
will visibility <v> Set will visibility (private/public/unlisted)
|
|
38
|
+
will subscribe <id> Subscribe to a will
|
|
39
|
+
will unsubscribe <id> Unsubscribe from a will
|
|
40
|
+
will subscriptions List my subscriptions
|
|
41
|
+
will subscribers List who subscribes to my will
|
|
42
|
+
will sub-pull <id> Pull a subscribed will
|
|
43
|
+
vendor publish Publish vendor to Cloud Brain (admin only)
|
|
44
|
+
vendor latest Check latest cloud vendor version
|
|
45
|
+
vendor changelog Show vendor changelog
|
|
46
|
+
vendor download Download specific vendor version
|
|
47
|
+
skill list List all skills
|
|
48
|
+
skill uninstall <name> Remove skill from project
|
|
49
|
+
cache status Show cache size
|
|
50
|
+
cache clean Remove old cache entries
|
|
51
|
+
status Current project status
|
|
52
|
+
version Show CLI version
|
|
53
|
+
|
|
54
|
+
Options:
|
|
55
|
+
--help, -h Show this help message
|
|
56
|
+
`);
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!COMMANDS[command]) {
|
|
61
|
+
console.error(`Unknown command: ${command}`);
|
|
62
|
+
console.error('Run "memory --help" for available commands.');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const mod = await COMMANDS[command]();
|
|
68
|
+
await mod.default(args);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error(`Error: ${err.message}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { existsSync, readdirSync, statSync, rmSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { findProjectRoot } from '../lib/project-store.mjs';
|
|
4
|
+
|
|
5
|
+
const CACHE_DIR = '.memory/.project/cache';
|
|
6
|
+
|
|
7
|
+
export default async function cache(args) {
|
|
8
|
+
const subcommand = args[0];
|
|
9
|
+
|
|
10
|
+
if (!subcommand || subcommand === '--help') {
|
|
11
|
+
console.log(`memory cache — Manage project cache
|
|
12
|
+
|
|
13
|
+
Subcommands:
|
|
14
|
+
status Show cache size and contents
|
|
15
|
+
clean [--older-than=30d] Remove old cache entries
|
|
16
|
+
`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const handlers = { status: handleStatus, clean: handleClean };
|
|
21
|
+
const handler = handlers[subcommand];
|
|
22
|
+
if (!handler) {
|
|
23
|
+
console.error('Unknown cache subcommand: ' + subcommand);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
await handler(args.slice(1));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getCacheDir() {
|
|
30
|
+
const root = findProjectRoot();
|
|
31
|
+
if (!root) { console.error('Not in a memory project.'); process.exit(1); }
|
|
32
|
+
return join(root, CACHE_DIR);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function dirSize(dir) {
|
|
36
|
+
let total = 0;
|
|
37
|
+
let count = 0;
|
|
38
|
+
if (!existsSync(dir)) return { total, count };
|
|
39
|
+
const walk = (d) => {
|
|
40
|
+
for (const entry of readdirSync(d, { withFileTypes: true })) {
|
|
41
|
+
const full = join(d, entry.name);
|
|
42
|
+
if (entry.isDirectory()) walk(full);
|
|
43
|
+
else { total += statSync(full).size; count++; }
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
walk(dir);
|
|
47
|
+
return { total, count };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function formatSize(bytes) {
|
|
51
|
+
if (bytes < 1024) return bytes + ' B';
|
|
52
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
53
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function handleStatus() {
|
|
57
|
+
const cacheDir = getCacheDir();
|
|
58
|
+
if (!existsSync(cacheDir)) { console.log('Cache directory does not exist.'); return; }
|
|
59
|
+
|
|
60
|
+
const subdirs = readdirSync(cacheDir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
61
|
+
let totalSize = 0;
|
|
62
|
+
let totalFiles = 0;
|
|
63
|
+
|
|
64
|
+
console.log('=== Cache Status ===\n');
|
|
65
|
+
for (const sub of subdirs) {
|
|
66
|
+
const { total, count } = dirSize(join(cacheDir, sub.name));
|
|
67
|
+
totalSize += total;
|
|
68
|
+
totalFiles += count;
|
|
69
|
+
console.log(' ' + sub.name + ': ' + count + ' files, ' + formatSize(total));
|
|
70
|
+
}
|
|
71
|
+
console.log('\n Total: ' + totalFiles + ' files, ' + formatSize(totalSize));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function handleClean(args) {
|
|
75
|
+
const olderThanFlag = args.find((a) => a.startsWith('--older-than='))?.split('=')[1];
|
|
76
|
+
const days = olderThanFlag ? parseInt(olderThanFlag) : 30;
|
|
77
|
+
const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
78
|
+
|
|
79
|
+
const cacheDir = getCacheDir();
|
|
80
|
+
if (!existsSync(cacheDir)) { console.log('Cache directory does not exist.'); return; }
|
|
81
|
+
|
|
82
|
+
const sessionsDir = join(cacheDir, 'workflow-sessions');
|
|
83
|
+
const evidenceDir = join(cacheDir, 'skill-evidence');
|
|
84
|
+
|
|
85
|
+
let removed = 0;
|
|
86
|
+
for (const dir of [sessionsDir, evidenceDir]) {
|
|
87
|
+
if (!existsSync(dir)) continue;
|
|
88
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
89
|
+
const full = join(dir, entry.name);
|
|
90
|
+
const stat = statSync(full);
|
|
91
|
+
if (stat.mtimeMs < cutoff) {
|
|
92
|
+
rmSync(full, { recursive: true, force: true });
|
|
93
|
+
removed++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log('Cleaned ' + removed + ' entries older than ' + days + ' days.');
|
|
99
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { existsSync, cpSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join, resolve, basename, dirname } from 'node:path';
|
|
3
|
+
import { isConfigured } from '../lib/api-client.mjs';
|
|
4
|
+
import { findProjectRoot, getProjectWillCurrent, getProjectVendor, readProjectName } from '../lib/project-store.mjs';
|
|
5
|
+
|
|
6
|
+
export default async function init(args) {
|
|
7
|
+
await initProject();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function initProject() {
|
|
11
|
+
if (!isConfigured()) {
|
|
12
|
+
console.error('Not logged in. Run "memory login" first.');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const projectRoot = findProjectRoot();
|
|
17
|
+
if (projectRoot) {
|
|
18
|
+
console.log('Memory project already exists at: ' + projectRoot);
|
|
19
|
+
console.log('Use "memory status" to check current state.');
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const root = process.cwd();
|
|
24
|
+
console.log('Initializing memory project in: ' + root);
|
|
25
|
+
|
|
26
|
+
// 1. Download vendor from Cloud Brain
|
|
27
|
+
console.log('Downloading vendor from Cloud Brain...');
|
|
28
|
+
const { cloudVendorDownloadLatest } = await import('../lib/api-client.mjs');
|
|
29
|
+
const { writeVendorFiles } = await import('./vendor.mjs');
|
|
30
|
+
|
|
31
|
+
let vendorDir = join(root, 'memory', 'vendor');
|
|
32
|
+
try {
|
|
33
|
+
const result = await cloudVendorDownloadLatest();
|
|
34
|
+
mkdirSync(vendorDir, { recursive: true });
|
|
35
|
+
writeVendorFiles(vendorDir, result.files, result.version);
|
|
36
|
+
console.log(' Vendor v' + result.version + ' (' + Object.keys(result.files).length + ' files)');
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (err.status === 404) {
|
|
39
|
+
console.log(' No vendor releases on Cloud Brain. Creating empty vendor.');
|
|
40
|
+
mkdirSync(vendorDir, { recursive: true });
|
|
41
|
+
} else {
|
|
42
|
+
console.error('Failed to download vendor: ' + err.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 2. Bootstrap project from templates
|
|
48
|
+
const templatesDir = join(vendorDir, 'install', 'templates');
|
|
49
|
+
if (existsSync(templatesDir)) {
|
|
50
|
+
bootstrapFromTemplates(root, templatesDir);
|
|
51
|
+
} else {
|
|
52
|
+
console.log(' No templates in vendor, creating minimal structure.');
|
|
53
|
+
bootstrapMinimal(root);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 3. Pull will from Cloud Brain
|
|
57
|
+
console.log('Pulling will from Cloud Brain...');
|
|
58
|
+
const { cloudWillPull } = await import('../lib/api-client.mjs');
|
|
59
|
+
const willDir = getProjectWillCurrent(root);
|
|
60
|
+
try {
|
|
61
|
+
const result = await cloudWillPull();
|
|
62
|
+
mkdirSync(willDir, { recursive: true });
|
|
63
|
+
for (const [name, content] of Object.entries(result.files)) {
|
|
64
|
+
writeFileSync(join(willDir, name), content, 'utf8');
|
|
65
|
+
}
|
|
66
|
+
console.log(' Will v' + result.version + ' (' + Object.keys(result.files).length + ' files)');
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.log(' No will on Cloud Brain yet. Will directory created empty.');
|
|
69
|
+
mkdirSync(willDir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log('Project initialized. Files created:');
|
|
74
|
+
console.log(' memory/start.md — agent entry point');
|
|
75
|
+
console.log(' memory/vendor/ — system specs (read-only)');
|
|
76
|
+
console.log(' memory/will/current/ — your will');
|
|
77
|
+
console.log(' .memory/ — project content');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readTemplate(templatesDir, relativePath) {
|
|
81
|
+
const filePath = join(templatesDir, relativePath);
|
|
82
|
+
if (!existsSync(filePath)) return null;
|
|
83
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
84
|
+
return readFileSync(filePath, 'utf8').replace(/\{\{today\}\}/g, today);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function writeIfMissing(filePath, content) {
|
|
88
|
+
if (!content || existsSync(filePath)) return false;
|
|
89
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
90
|
+
writeFileSync(filePath, content, 'utf8');
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function bootstrapFromTemplates(root, templatesDir) {
|
|
95
|
+
const MEMORY = join(root, 'memory');
|
|
96
|
+
const DOT_MEMORY = join(root, '.memory');
|
|
97
|
+
const DOT_PROJECT = join(DOT_MEMORY, '.project');
|
|
98
|
+
|
|
99
|
+
// Skeleton directories
|
|
100
|
+
for (const sub of ['config', 'cache', 'global', 'skills']) {
|
|
101
|
+
mkdirSync(join(DOT_PROJECT, sub), { recursive: true });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// memory/start.md, memory/setup.md
|
|
105
|
+
writeIfMissing(join(MEMORY, 'start.md'), readTemplate(templatesDir, 'start.md'));
|
|
106
|
+
writeIfMissing(join(MEMORY, 'setup.md'), readTemplate(templatesDir, 'setup.md'));
|
|
107
|
+
|
|
108
|
+
// .memory/ template files
|
|
109
|
+
const dotMemoryMap = {
|
|
110
|
+
'README.md': join(DOT_MEMORY, 'README.md'),
|
|
111
|
+
'00-索引.md': join(DOT_MEMORY, '00-索引.md'),
|
|
112
|
+
'.gitignore': join(DOT_MEMORY, '.gitignore'),
|
|
113
|
+
'project/README.md': join(DOT_PROJECT, 'README.md'),
|
|
114
|
+
'config/README.md': join(DOT_PROJECT, 'config', 'README.md'),
|
|
115
|
+
'config/runtime.md': join(DOT_PROJECT, 'config', 'runtime.md'),
|
|
116
|
+
'config/install-lock.md': join(DOT_PROJECT, 'config', 'install-lock.md'),
|
|
117
|
+
'cache/README.md': join(DOT_PROJECT, 'cache', 'README.md'),
|
|
118
|
+
'global/00-压缩背景.md': join(DOT_PROJECT, 'global', '00-压缩背景.md'),
|
|
119
|
+
'global/01-全局记忆.md': join(DOT_PROJECT, 'global', '01-全局记忆.md'),
|
|
120
|
+
'global/03-全局开发规范.md': join(DOT_PROJECT, 'global', '03-全局开发规范.md'),
|
|
121
|
+
'skills/README.md': join(DOT_PROJECT, 'skills', 'README.md'),
|
|
122
|
+
};
|
|
123
|
+
for (const [tplPath, destPath] of Object.entries(dotMemoryMap)) {
|
|
124
|
+
writeIfMissing(destPath, readTemplate(templatesDir, join('dot-memory', tplPath)));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Agent entry files
|
|
128
|
+
const agentEntryMap = {
|
|
129
|
+
'CLAUDE.md': join(root, 'CLAUDE.md'),
|
|
130
|
+
'AGENTS.md': join(root, 'AGENTS.md'),
|
|
131
|
+
'Agent.md': join(root, 'Agent.md'),
|
|
132
|
+
'.cursorrules': join(root, '.cursorrules'),
|
|
133
|
+
};
|
|
134
|
+
for (const [tplName, destPath] of Object.entries(agentEntryMap)) {
|
|
135
|
+
writeIfMissing(destPath, readTemplate(templatesDir, join('agent-entries', tplName)));
|
|
136
|
+
}
|
|
137
|
+
writeIfMissing(
|
|
138
|
+
join(root, '.github', 'copilot-instructions.md'),
|
|
139
|
+
readTemplate(templatesDir, join('agent-entries', 'copilot-instructions.md'))
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function bootstrapMinimal(root) {
|
|
144
|
+
const MEMORY = join(root, 'memory');
|
|
145
|
+
const DOT_MEMORY = join(root, '.memory');
|
|
146
|
+
const DOT_PROJECT = join(DOT_MEMORY, '.project');
|
|
147
|
+
|
|
148
|
+
for (const sub of ['config', 'cache', 'global', 'skills']) {
|
|
149
|
+
mkdirSync(join(DOT_PROJECT, sub), { recursive: true });
|
|
150
|
+
}
|
|
151
|
+
writeIfMissing(join(MEMORY, 'start.md'), '# Start\n\nRead this file first.\n');
|
|
152
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readCredentials } from '../lib/credentials.mjs';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { cloudRegister, cloudLogin, cloudMe, isConfigured } from '../lib/api-client.mjs';
|
|
4
|
+
import { saveCredentials } from '../lib/credentials.mjs';
|
|
5
|
+
|
|
6
|
+
function prompt(question) {
|
|
7
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
rl.question(question, (answer) => {
|
|
10
|
+
rl.close();
|
|
11
|
+
resolve(answer.trim());
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default async function login(args) {
|
|
17
|
+
const subcommand = args[0];
|
|
18
|
+
|
|
19
|
+
if (subcommand === 'register') return handleRegister(args.slice(1));
|
|
20
|
+
if (subcommand === 'status') return handleStatus();
|
|
21
|
+
if (subcommand === 'logout') return handleLogout();
|
|
22
|
+
|
|
23
|
+
await handleLogin(args);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function handleLogin(args) {
|
|
27
|
+
const endpoint = args.find((a) => a.startsWith('--endpoint='))?.split('=')[1];
|
|
28
|
+
|
|
29
|
+
const username = await prompt('Username: ');
|
|
30
|
+
const password = await prompt('Password: ');
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const result = await cloudLogin(username, password);
|
|
34
|
+
saveCredentials(result.api_key, result.user_id, result.will_id, endpoint);
|
|
35
|
+
console.log('Logged in as: ' + result.display_name + ' (' + result.username + ')');
|
|
36
|
+
console.log('Will ID: ' + result.will_id);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error('Login failed: ' + err.message);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function handleRegister(args) {
|
|
44
|
+
const endpoint = args.find((a) => a.startsWith('--endpoint='))?.split('=')[1];
|
|
45
|
+
|
|
46
|
+
const username = await prompt('Username: ');
|
|
47
|
+
const password = await prompt('Password: ');
|
|
48
|
+
const displayName = await prompt('Display name (optional): ');
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const result = await cloudRegister(username, password, displayName || undefined);
|
|
52
|
+
saveCredentials(result.api_key, result.user_id, result.will_id, endpoint);
|
|
53
|
+
console.log('Registered as: ' + result.display_name + ' (' + result.username + ')');
|
|
54
|
+
console.log('Will ID: ' + result.will_id);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.error('Registration failed: ' + err.message);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function handleStatus() {
|
|
62
|
+
if (!isConfigured()) {
|
|
63
|
+
console.log('Not logged in. Run "memory login" to authenticate.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const me = await cloudMe();
|
|
69
|
+
console.log('Logged in as: ' + me.display_name + ' (' + me.username + ')');
|
|
70
|
+
console.log('Will ID: ' + me.will_id);
|
|
71
|
+
console.log('Subscribers: ' + me.subscribers);
|
|
72
|
+
const creds = readCredentials();
|
|
73
|
+
console.log('Endpoint: ' + creds.api_endpoint);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error('Failed to fetch profile: ' + err.message);
|
|
76
|
+
console.log('Your API key may be invalid. Run "memory login" to re-authenticate.');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function handleLogout() {
|
|
81
|
+
const { writeCredentials } = await import('../lib/credentials.mjs');
|
|
82
|
+
writeCredentials({ api_endpoint: readCredentials().api_endpoint, api_key: null, user_id: null, will_id: null });
|
|
83
|
+
console.log('Logged out.');
|
|
84
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { findProjectRoot } from '../lib/project-store.mjs';
|
|
4
|
+
|
|
5
|
+
const PROJECT_SKILLS_DIR = '.memory/.project/skills';
|
|
6
|
+
|
|
7
|
+
export default async function skill(args) {
|
|
8
|
+
const subcommand = args[0];
|
|
9
|
+
|
|
10
|
+
if (!subcommand || subcommand === '--help') {
|
|
11
|
+
console.log(`memory skill — Manage project skills
|
|
12
|
+
|
|
13
|
+
Subcommands:
|
|
14
|
+
list List all skills (vendor + project)
|
|
15
|
+
uninstall <name> Remove skill from current project
|
|
16
|
+
`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const handlers = {
|
|
21
|
+
list: handleList,
|
|
22
|
+
uninstall: handleUninstall,
|
|
23
|
+
};
|
|
24
|
+
const handler = handlers[subcommand];
|
|
25
|
+
if (!handler) {
|
|
26
|
+
console.error('Unknown skill subcommand: ' + subcommand);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
await handler(args.slice(1));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getProjectSkillsDir() {
|
|
33
|
+
const root = findProjectRoot();
|
|
34
|
+
return root ? join(root, PROJECT_SKILLS_DIR) : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function listSkillsIn(dir) {
|
|
38
|
+
if (!existsSync(dir)) return [];
|
|
39
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
40
|
+
.filter((e) => e.isDirectory())
|
|
41
|
+
.map((e) => e.name);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getSkillMeta(dir, name) {
|
|
45
|
+
const skillDir = join(dir, name);
|
|
46
|
+
const skillFile = join(skillDir, 'SKILL.md');
|
|
47
|
+
let description = '';
|
|
48
|
+
if (existsSync(skillFile)) {
|
|
49
|
+
const content = readFileSync(skillFile, 'utf8');
|
|
50
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
51
|
+
if (fmMatch) {
|
|
52
|
+
const descMatch = fmMatch[1].match(/description:\s*(.+)/);
|
|
53
|
+
if (descMatch) description = descMatch[1].trim();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { name, path: skillDir, description };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function handleList() {
|
|
60
|
+
const vendorRoot = findProjectRoot();
|
|
61
|
+
const vendorSkillsDir = vendorRoot ? join(vendorRoot, 'memory/vendor/skills') : null;
|
|
62
|
+
const projectDir = getProjectSkillsDir();
|
|
63
|
+
|
|
64
|
+
console.log('=== Skills ===\n');
|
|
65
|
+
|
|
66
|
+
if (vendorSkillsDir && existsSync(vendorSkillsDir)) {
|
|
67
|
+
const vendorSkills = listSkillsIn(vendorSkillsDir);
|
|
68
|
+
if (vendorSkills.length > 0) {
|
|
69
|
+
console.log('System (vendor):');
|
|
70
|
+
for (const s of vendorSkills) {
|
|
71
|
+
const meta = getSkillMeta(vendorSkillsDir, s);
|
|
72
|
+
console.log(' ' + s + (meta.description ? ' — ' + meta.description : ''));
|
|
73
|
+
}
|
|
74
|
+
console.log('');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (projectDir && existsSync(projectDir)) {
|
|
79
|
+
const projectSkills = listSkillsIn(projectDir);
|
|
80
|
+
if (projectSkills.length > 0) {
|
|
81
|
+
console.log('Project:');
|
|
82
|
+
for (const s of projectSkills) {
|
|
83
|
+
const meta = getSkillMeta(projectDir, s);
|
|
84
|
+
console.log(' ' + s + (meta.description ? ' — ' + meta.description : ''));
|
|
85
|
+
}
|
|
86
|
+
console.log('');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function handleUninstall(args) {
|
|
92
|
+
const name = args[0];
|
|
93
|
+
if (!name) {
|
|
94
|
+
console.error('Usage: memory skill uninstall <skill-name>');
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const projectDir = getProjectSkillsDir();
|
|
99
|
+
if (!projectDir) {
|
|
100
|
+
console.error('Not in a memory project.');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const targetDir = join(projectDir, name);
|
|
105
|
+
if (!existsSync(targetDir)) {
|
|
106
|
+
console.error('Project skill not found: ' + name);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
rmSync(targetDir, { recursive: true, force: true });
|
|
111
|
+
console.log('Removed skill "' + name + '" from project.');
|
|
112
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { isConfigured } from '../lib/api-client.mjs';
|
|
3
|
+
import { readCredentials } from '../lib/credentials.mjs';
|
|
4
|
+
import { findProjectRoot, getProjectWillCurrent, getProjectVendor } from '../lib/project-store.mjs';
|
|
5
|
+
|
|
6
|
+
export default async function status() {
|
|
7
|
+
console.log('=== Memory Status ===\n');
|
|
8
|
+
|
|
9
|
+
// Credentials
|
|
10
|
+
const creds = readCredentials();
|
|
11
|
+
console.log('Cloud Brain: ' + (isConfigured() ? creds.api_endpoint : 'not configured'));
|
|
12
|
+
if (isConfigured()) {
|
|
13
|
+
console.log(' User: ' + (creds.user_id || 'unknown'));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log('');
|
|
17
|
+
|
|
18
|
+
// Project
|
|
19
|
+
const projectRoot = findProjectRoot();
|
|
20
|
+
if (projectRoot) {
|
|
21
|
+
console.log('Project: ' + projectRoot);
|
|
22
|
+
console.log(' Vendor: ' + (existsSync(getProjectVendor(projectRoot)) ? 'exists' : 'missing'));
|
|
23
|
+
console.log(' Will: ' + (existsSync(getProjectWillCurrent(projectRoot)) ? 'exists' : 'missing'));
|
|
24
|
+
} else {
|
|
25
|
+
console.log('Project: not in a memory project');
|
|
26
|
+
console.log(' Run "memory init" to create one.');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isConfigured } from '../lib/api-client.mjs';
|
|
4
|
+
import { findProjectRoot, getProjectVendor } from '../lib/project-store.mjs';
|
|
5
|
+
|
|
6
|
+
export default async function update(args) {
|
|
7
|
+
if (!isConfigured()) {
|
|
8
|
+
console.error('Not logged in. Run "memory login" first.');
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const projectRoot = findProjectRoot();
|
|
13
|
+
if (!projectRoot) {
|
|
14
|
+
console.error('Not in a memory project. Run "memory init" first.');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { cloudVendorDownloadLatest } = await import('../lib/api-client.mjs');
|
|
19
|
+
const { writeVendorFiles } = await import('./vendor.mjs');
|
|
20
|
+
|
|
21
|
+
const vendorDir = getProjectVendor(projectRoot);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const result = await cloudVendorDownloadLatest();
|
|
25
|
+
writeVendorFiles(vendorDir, result.files, result.version);
|
|
26
|
+
console.log('Vendor updated to v' + result.version + ' (' + Object.keys(result.files).length + ' files).');
|
|
27
|
+
} catch (err) {
|
|
28
|
+
if (err.status === 404) { console.log('No vendor releases on Cloud Brain.'); return; }
|
|
29
|
+
console.error('Download failed: ' + err.message);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|