@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
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
isConfigured, cloudVendorLatest, cloudVendorChangelog,
|
|
5
|
+
cloudVendorPublish, cloudVendorDownload, cloudVendorDownloadLatest,
|
|
6
|
+
} from '../lib/api-client.mjs';
|
|
7
|
+
import { findProjectRoot, getProjectVendor } from '../lib/project-store.mjs';
|
|
8
|
+
|
|
9
|
+
export default async function vendor(args) {
|
|
10
|
+
const subcommand = args[0];
|
|
11
|
+
|
|
12
|
+
if (!subcommand || subcommand === '--help') {
|
|
13
|
+
console.log(`memory vendor — Manage vendor distribution
|
|
14
|
+
|
|
15
|
+
Subcommands:
|
|
16
|
+
publish [--version=X.Y.Z] Publish current project vendor to Cloud Brain (admin only)
|
|
17
|
+
latest Check latest cloud vendor version
|
|
18
|
+
changelog Show vendor changelog
|
|
19
|
+
download [--version=X.Y.Z] Download specific vendor version to project
|
|
20
|
+
`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const handlers = {
|
|
25
|
+
publish: handlePublish,
|
|
26
|
+
latest: handleLatest,
|
|
27
|
+
changelog: handleChangelog,
|
|
28
|
+
download: handleDownload,
|
|
29
|
+
};
|
|
30
|
+
const handler = handlers[subcommand];
|
|
31
|
+
if (!handler) {
|
|
32
|
+
console.error('Unknown vendor subcommand: ' + subcommand);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
await handler(args.slice(1));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function collectFiles(dir, base) {
|
|
39
|
+
const files = {};
|
|
40
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
41
|
+
for (const entry of entries) {
|
|
42
|
+
const fullPath = join(dir, entry.name);
|
|
43
|
+
const relPath = relative(base, fullPath).replace(/\\/g, '/');
|
|
44
|
+
if (entry.isDirectory()) {
|
|
45
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
46
|
+
Object.assign(files, collectFiles(fullPath, base));
|
|
47
|
+
} else {
|
|
48
|
+
files[relPath] = readFileSync(fullPath, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return files;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function requireCloud() {
|
|
55
|
+
if (!isConfigured()) {
|
|
56
|
+
console.error('Not logged in. Run "memory login" first.');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function handlePublish(args) {
|
|
62
|
+
requireCloud();
|
|
63
|
+
|
|
64
|
+
const versionFlag = args.find((a) => a.startsWith('--version='))?.split('=')[1];
|
|
65
|
+
|
|
66
|
+
const projectRoot = findProjectRoot();
|
|
67
|
+
if (!projectRoot) {
|
|
68
|
+
console.error('Not in a memory project. Run this command from the memory system root directory.');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const sourceDir = getProjectVendor(projectRoot);
|
|
73
|
+
if (!existsSync(sourceDir)) {
|
|
74
|
+
console.error('No memory/vendor/ in current project.');
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const versionFile = join(sourceDir, 'version.json');
|
|
79
|
+
let currentVersion = 'unknown';
|
|
80
|
+
if (existsSync(versionFile)) {
|
|
81
|
+
try { currentVersion = JSON.parse(readFileSync(versionFile, 'utf8')).version || 'unknown'; } catch {}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const version = versionFlag || currentVersion;
|
|
85
|
+
if (version === 'unknown') {
|
|
86
|
+
console.error('Cannot determine version. Use --version=X.Y.Z or ensure vendor/version.json has a version field.');
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log('Publishing vendor from: ' + sourceDir);
|
|
91
|
+
const files = collectFiles(sourceDir, sourceDir);
|
|
92
|
+
console.log(' Files: ' + Object.keys(files).length);
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await cloudVendorPublish(version, files);
|
|
96
|
+
console.log('Published vendor v' + result.version + ' to Cloud Brain.');
|
|
97
|
+
console.log(' Hash: ' + result.content_hash);
|
|
98
|
+
console.log(' Files: ' + result.file_count);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error('Publish failed: ' + err.message);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function handleLatest() {
|
|
106
|
+
requireCloud();
|
|
107
|
+
try {
|
|
108
|
+
const result = await cloudVendorLatest();
|
|
109
|
+
console.log('Latest vendor on Cloud Brain:');
|
|
110
|
+
console.log(' Version: ' + result.version);
|
|
111
|
+
console.log(' Channel: ' + result.channel);
|
|
112
|
+
console.log(' Hash: ' + result.content_hash);
|
|
113
|
+
console.log(' Date: ' + result.created_at);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
if (err.status === 404) console.log('No vendor releases on Cloud Brain yet.');
|
|
116
|
+
else console.error('Failed: ' + err.message);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function handleChangelog() {
|
|
121
|
+
requireCloud();
|
|
122
|
+
try {
|
|
123
|
+
const result = await cloudVendorChangelog();
|
|
124
|
+
if (result.releases.length === 0) { console.log('No vendor releases.'); return; }
|
|
125
|
+
console.log('Vendor changelog:');
|
|
126
|
+
for (const r of result.releases) {
|
|
127
|
+
console.log(' v' + r.version + ' (' + r.channel + ') — ' + r.created_at);
|
|
128
|
+
if (r.changelog) console.log(' ' + r.changelog);
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error('Failed: ' + err.message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function handleDownload(args) {
|
|
136
|
+
requireCloud();
|
|
137
|
+
const versionFlag = args.find?.((a) => a.startsWith('--version='))?.split('=')[1];
|
|
138
|
+
|
|
139
|
+
const projectRoot = findProjectRoot();
|
|
140
|
+
if (!projectRoot) {
|
|
141
|
+
console.error('Not in a memory project. Run "memory init" first.');
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const vendorDir = getProjectVendor(projectRoot);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const result = versionFlag
|
|
149
|
+
? await cloudVendorDownload(versionFlag)
|
|
150
|
+
: await cloudVendorDownloadLatest();
|
|
151
|
+
|
|
152
|
+
writeVendorFiles(vendorDir, result.files, result.version);
|
|
153
|
+
|
|
154
|
+
console.log('Downloaded vendor v' + result.version + ' from Cloud Brain.');
|
|
155
|
+
console.log(' Files: ' + Object.keys(result.files).length);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (err.status === 404) console.log('Version not found on Cloud Brain.');
|
|
158
|
+
else console.error('Download failed: ' + err.message);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Shared helper — also used by update and init
|
|
163
|
+
export function writeVendorFiles(dir, files, version) {
|
|
164
|
+
for (const [relPath, content] of Object.entries(files)) {
|
|
165
|
+
const fullPath = join(dir, relPath);
|
|
166
|
+
mkdirSync(join(fullPath, '..'), { recursive: true });
|
|
167
|
+
writeFileSync(fullPath, content, 'utf8');
|
|
168
|
+
}
|
|
169
|
+
const versionData = { version, source: 'cloud-brain', updated: new Date().toISOString() };
|
|
170
|
+
writeFileSync(join(dir, 'version.json'), JSON.stringify(versionData, null, 2) + '\n', 'utf8');
|
|
171
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { findProjectRoot, getProjectVendor } from '../lib/project-store.mjs';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
export default async function version() {
|
|
9
|
+
const pkgPath = join(__dirname, '..', 'package.json');
|
|
10
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
11
|
+
console.log('CLI version: ' + pkg.version);
|
|
12
|
+
|
|
13
|
+
const projectRoot = findProjectRoot();
|
|
14
|
+
if (projectRoot) {
|
|
15
|
+
const versionFile = join(getProjectVendor(projectRoot), 'version.json');
|
|
16
|
+
if (existsSync(versionFile)) {
|
|
17
|
+
try {
|
|
18
|
+
const v = JSON.parse(readFileSync(versionFile, 'utf8'));
|
|
19
|
+
console.log('Vendor: ' + (v.version || 'unknown') + (v.updated ? ' (updated: ' + v.updated + ')' : ''));
|
|
20
|
+
} catch {
|
|
21
|
+
console.log('Vendor: version.json unreadable');
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
console.log('Vendor: not installed');
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
console.log('Vendor: not in a memory project');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { findProjectRoot, getProjectWillCurrent } from '../lib/project-store.mjs';
|
|
4
|
+
import {
|
|
5
|
+
isConfigured, cloudWillPush, cloudWillPull, cloudWillVersions, cloudWillSetVisibility,
|
|
6
|
+
cloudSubscribe, cloudUnsubscribe, cloudListSubscriptions, cloudListSubscribers, cloudPullSubscription,
|
|
7
|
+
} from '../lib/api-client.mjs';
|
|
8
|
+
|
|
9
|
+
const WILL_FILES = ['00-压缩意志.md', '01-身份与目标.md', '02-开发偏好.md', '03-思维决策.md', '04-沟通协作.md'];
|
|
10
|
+
|
|
11
|
+
export default async function will(args) {
|
|
12
|
+
const subcommand = args[0];
|
|
13
|
+
|
|
14
|
+
if (!subcommand || subcommand === '--help') {
|
|
15
|
+
console.log(`memory will — Manage will
|
|
16
|
+
|
|
17
|
+
Subcommands:
|
|
18
|
+
publish Publish project will to Cloud Brain
|
|
19
|
+
pull Pull will from Cloud Brain to project
|
|
20
|
+
status Show will status
|
|
21
|
+
versions List cloud will versions
|
|
22
|
+
visibility <v> Set will visibility (private/public/unlisted)
|
|
23
|
+
subscribe <id> [--dimensions 02,03] Subscribe to a will
|
|
24
|
+
unsubscribe <id> Unsubscribe from a will
|
|
25
|
+
subscriptions List my subscriptions
|
|
26
|
+
subscribers List who subscribes to my will
|
|
27
|
+
sub-pull <id> Pull a subscribed will to project
|
|
28
|
+
`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const handlers = {
|
|
33
|
+
publish: handlePublish, pull: handlePull, status: handleStatus,
|
|
34
|
+
versions: handleVersions, visibility: handleVisibility,
|
|
35
|
+
subscribe: handleSubscribe, unsubscribe: handleUnsubscribe,
|
|
36
|
+
subscriptions: handleSubscriptions, subscribers: handleSubscribers,
|
|
37
|
+
'sub-pull': handleSubPull,
|
|
38
|
+
// backward compat
|
|
39
|
+
'cloud-push': handlePublish, 'cloud-pull': handlePull,
|
|
40
|
+
};
|
|
41
|
+
const handler = handlers[subcommand];
|
|
42
|
+
if (!handler) {
|
|
43
|
+
console.error('Unknown will subcommand: ' + subcommand);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
await handler(args.slice(1));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// --- Core ---
|
|
50
|
+
|
|
51
|
+
function requireProject() {
|
|
52
|
+
const projectRoot = findProjectRoot();
|
|
53
|
+
if (!projectRoot) {
|
|
54
|
+
console.error('Not in a memory project. Run "memory init" first.');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
return projectRoot;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function requireCloud() {
|
|
61
|
+
if (!isConfigured()) {
|
|
62
|
+
console.error('Not logged in. Run "memory login" first.');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readWillFiles(dir) {
|
|
68
|
+
const files = {};
|
|
69
|
+
for (const f of WILL_FILES) {
|
|
70
|
+
const p = join(dir, f);
|
|
71
|
+
if (existsSync(p)) files[f] = readFileSync(p, 'utf8');
|
|
72
|
+
}
|
|
73
|
+
return files;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// --- Handlers ---
|
|
77
|
+
|
|
78
|
+
async function handlePublish() {
|
|
79
|
+
const projectRoot = requireProject();
|
|
80
|
+
requireCloud();
|
|
81
|
+
|
|
82
|
+
const willDir = getProjectWillCurrent(projectRoot);
|
|
83
|
+
if (!existsSync(willDir)) {
|
|
84
|
+
console.error('No will directory. Run "memory will pull" first.');
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const files = readWillFiles(willDir);
|
|
89
|
+
if (Object.keys(files).length === 0) {
|
|
90
|
+
console.error('No will files found in: ' + willDir);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await cloudWillPush(files);
|
|
96
|
+
console.log('Will published to Cloud Brain.');
|
|
97
|
+
console.log(' Version: ' + result.version);
|
|
98
|
+
console.log(' Files: ' + Object.keys(files).length);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
console.error('Publish failed: ' + err.message);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function handlePull() {
|
|
106
|
+
const projectRoot = requireProject();
|
|
107
|
+
requireCloud();
|
|
108
|
+
|
|
109
|
+
const willDir = getProjectWillCurrent(projectRoot);
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const result = await cloudWillPull();
|
|
113
|
+
mkdirSync(willDir, { recursive: true });
|
|
114
|
+
for (const [name, content] of Object.entries(result.files)) {
|
|
115
|
+
writeFileSync(join(willDir, name), content, 'utf8');
|
|
116
|
+
}
|
|
117
|
+
console.log('Will pulled from Cloud Brain.');
|
|
118
|
+
console.log(' Version: ' + result.version);
|
|
119
|
+
console.log(' Files: ' + Object.keys(result.files).length);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
console.error('Pull failed: ' + err.message);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function handleStatus() {
|
|
127
|
+
const projectRoot = findProjectRoot();
|
|
128
|
+
if (!projectRoot) {
|
|
129
|
+
console.log('Not in a memory project.');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const willDir = getProjectWillCurrent(projectRoot);
|
|
134
|
+
const willExists = existsSync(willDir);
|
|
135
|
+
const fileCount = willExists
|
|
136
|
+
? WILL_FILES.filter(f => existsSync(join(willDir, f))).length
|
|
137
|
+
: 0;
|
|
138
|
+
|
|
139
|
+
console.log('Will status:');
|
|
140
|
+
console.log(' Directory: ' + (willExists ? 'exists' : 'missing'));
|
|
141
|
+
console.log(' Files: ' + fileCount + '/' + WILL_FILES.length);
|
|
142
|
+
console.log(' Cloud: ' + (isConfigured() ? 'connected' : 'not configured'));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function handleVersions() {
|
|
146
|
+
requireCloud();
|
|
147
|
+
try {
|
|
148
|
+
const result = await cloudWillVersions();
|
|
149
|
+
if (result.versions.length === 0) { console.log('No will versions.'); return; }
|
|
150
|
+
console.log('Will versions:');
|
|
151
|
+
for (const v of result.versions) {
|
|
152
|
+
console.log(' v' + v.version + ' — ' + v.created_at);
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error('Failed: ' + err.message);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function handleVisibility(args) {
|
|
160
|
+
requireCloud();
|
|
161
|
+
const visibility = args[0];
|
|
162
|
+
if (!visibility || !['private', 'public', 'unlisted'].includes(visibility)) {
|
|
163
|
+
console.error('Usage: memory will visibility <private|public|unlisted>');
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
await cloudWillSetVisibility(visibility);
|
|
168
|
+
console.log('Will visibility set to: ' + visibility);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error('Failed: ' + err.message);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function handleSubscribe(args) {
|
|
175
|
+
requireCloud();
|
|
176
|
+
const willId = args[0];
|
|
177
|
+
if (!willId) {
|
|
178
|
+
console.error('Usage: memory will subscribe <will_id> [--dimensions 02,03]');
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
const dimFlag = args.find(a => a.startsWith('--dimensions='))?.split('=')[1]
|
|
182
|
+
|| (args.indexOf('--dimensions') >= 0 ? args[args.indexOf('--dimensions') + 1] : null);
|
|
183
|
+
const dimensions = dimFlag ? dimFlag.split(',').map(d => d.trim()) : undefined;
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const result = await cloudSubscribe(willId, dimensions);
|
|
187
|
+
console.log('Subscribed to: ' + result.will_id);
|
|
188
|
+
console.log(' Owner: ' + result.owner.display_name);
|
|
189
|
+
if (result.dimensions) console.log(' Dimensions: ' + result.dimensions.join(', '));
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.error('Subscribe failed: ' + err.message);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function handleUnsubscribe(args) {
|
|
196
|
+
requireCloud();
|
|
197
|
+
const willId = args[0];
|
|
198
|
+
if (!willId) {
|
|
199
|
+
console.error('Usage: memory will unsubscribe <will_id>');
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
await cloudUnsubscribe(willId);
|
|
204
|
+
console.log('Unsubscribed from: ' + willId);
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error('Unsubscribe failed: ' + err.message);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function handleSubscriptions() {
|
|
211
|
+
requireCloud();
|
|
212
|
+
try {
|
|
213
|
+
const result = await cloudListSubscriptions();
|
|
214
|
+
if (result.subscriptions.length === 0) { console.log('No subscriptions.'); return; }
|
|
215
|
+
console.log('Subscriptions:');
|
|
216
|
+
for (const s of result.subscriptions) {
|
|
217
|
+
console.log(' ' + s.will_id + ' — ' + s.owner.display_name + (s.dimensions ? ' [' + s.dimensions.join(',') + ']' : ''));
|
|
218
|
+
}
|
|
219
|
+
} catch (err) {
|
|
220
|
+
console.error('Failed: ' + err.message);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function handleSubscribers() {
|
|
225
|
+
requireCloud();
|
|
226
|
+
try {
|
|
227
|
+
const result = await cloudListSubscribers();
|
|
228
|
+
if (result.subscribers.length === 0) { console.log('No subscribers.'); return; }
|
|
229
|
+
console.log('Subscribers:');
|
|
230
|
+
for (const s of result.subscribers) {
|
|
231
|
+
console.log(' ' + s.username + ' (' + s.display_name + ')' + (s.dimensions ? ' [' + s.dimensions.join(',') + ']' : ''));
|
|
232
|
+
}
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error('Failed: ' + err.message);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function handleSubPull(args) {
|
|
239
|
+
requireCloud();
|
|
240
|
+
const projectRoot = requireProject();
|
|
241
|
+
const willId = args[0];
|
|
242
|
+
if (!willId) {
|
|
243
|
+
console.error('Usage: memory will sub-pull <will_id>');
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const result = await cloudPullSubscription(willId);
|
|
249
|
+
// Save to project .memory/.project/subscriptions/<will_id>/
|
|
250
|
+
const subDir = join(projectRoot, '.memory', '.project', 'subscriptions', result.will_id);
|
|
251
|
+
mkdirSync(subDir, { recursive: true });
|
|
252
|
+
for (const [name, content] of Object.entries(result.files)) {
|
|
253
|
+
writeFileSync(join(subDir, name), content, 'utf8');
|
|
254
|
+
}
|
|
255
|
+
writeFileSync(join(subDir, 'manifest.json'), JSON.stringify({
|
|
256
|
+
will_id: result.will_id,
|
|
257
|
+
owner: result.owner,
|
|
258
|
+
version: result.version,
|
|
259
|
+
dimensions: result.dimensions,
|
|
260
|
+
synced_at: new Date().toISOString(),
|
|
261
|
+
}, null, 2) + '\n', 'utf8');
|
|
262
|
+
|
|
263
|
+
console.log('Pulled subscription: ' + result.will_id);
|
|
264
|
+
console.log(' Owner: ' + result.owner.display_name + ' (' + result.owner.username + ')');
|
|
265
|
+
console.log(' Version: ' + result.version);
|
|
266
|
+
console.log(' Saved to: ' + subDir);
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.error('Pull failed: ' + err.message);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloud Brain API client.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readCredentials } from './credentials.mjs';
|
|
6
|
+
import { request } from 'node:http';
|
|
7
|
+
import { request as httpsRequest } from 'node:https';
|
|
8
|
+
|
|
9
|
+
function getEndpoint() {
|
|
10
|
+
return readCredentials().api_endpoint || 'http://localhost:3900';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getApiKey() {
|
|
14
|
+
return readCredentials().api_key;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isConfigured() {
|
|
18
|
+
const creds = readCredentials();
|
|
19
|
+
return !!(creds.api_key && creds.api_endpoint);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function httpFetch(method, path, body = null) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const endpoint = getEndpoint();
|
|
25
|
+
const url = new URL(path, endpoint);
|
|
26
|
+
const isHttps = url.protocol === 'https:';
|
|
27
|
+
const reqFn = isHttps ? httpsRequest : request;
|
|
28
|
+
|
|
29
|
+
const options = {
|
|
30
|
+
hostname: url.hostname,
|
|
31
|
+
port: url.port || (isHttps ? 443 : 80),
|
|
32
|
+
path: url.pathname + url.search,
|
|
33
|
+
method,
|
|
34
|
+
headers: {
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const apiKey = getApiKey();
|
|
40
|
+
if (apiKey) options.headers['Authorization'] = 'Bearer ' + apiKey;
|
|
41
|
+
|
|
42
|
+
const req = reqFn(options, (res) => {
|
|
43
|
+
const chunks = [];
|
|
44
|
+
res.on('data', (c) => chunks.push(c));
|
|
45
|
+
res.on('end', () => {
|
|
46
|
+
const raw = Buffer.concat(chunks).toString('utf8');
|
|
47
|
+
try {
|
|
48
|
+
const data = JSON.parse(raw);
|
|
49
|
+
if (res.statusCode >= 400) {
|
|
50
|
+
const err = new Error(data.error || 'API error');
|
|
51
|
+
err.status = res.statusCode;
|
|
52
|
+
err.data = data;
|
|
53
|
+
reject(err);
|
|
54
|
+
} else {
|
|
55
|
+
resolve(data);
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
reject(new Error('Invalid JSON response: ' + raw.slice(0, 200)));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
req.on('error', reject);
|
|
64
|
+
if (body) req.write(JSON.stringify(body));
|
|
65
|
+
req.end();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// --- Auth ---
|
|
70
|
+
|
|
71
|
+
export async function cloudRegister(username, password, displayName) {
|
|
72
|
+
return httpFetch('POST', '/auth/register', { username, password, display_name: displayName });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function cloudLogin(username, password) {
|
|
76
|
+
return httpFetch('POST', '/auth/login', { username, password });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function cloudMe() {
|
|
80
|
+
return httpFetch('GET', '/auth/me');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// --- Will ---
|
|
84
|
+
|
|
85
|
+
export async function cloudWillPush(files) {
|
|
86
|
+
return httpFetch('PUT', '/will/me', { files });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function cloudWillPull() {
|
|
90
|
+
return httpFetch('GET', '/will/me/pull');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function cloudWillVersions() {
|
|
94
|
+
return httpFetch('GET', '/will/me/versions');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function cloudWillSetVisibility(visibility) {
|
|
98
|
+
return httpFetch('PUT', '/will/me/visibility', { visibility });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function cloudGetWill(willId) {
|
|
102
|
+
return httpFetch('GET', '/will/' + willId);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function cloudGetWillDimension(willId, dimension) {
|
|
106
|
+
return httpFetch('GET', '/will/' + willId + '/dimensions/' + dimension);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- Subscriptions ---
|
|
110
|
+
|
|
111
|
+
export async function cloudSubscribe(willId, dimensions) {
|
|
112
|
+
return httpFetch('POST', '/will/subscribe', { will_id: willId, dimensions });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function cloudUnsubscribe(willId) {
|
|
116
|
+
return httpFetch('DELETE', '/will/unsubscribe/' + willId);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function cloudListSubscriptions() {
|
|
120
|
+
return httpFetch('GET', '/will/subscriptions');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function cloudListSubscribers() {
|
|
124
|
+
return httpFetch('GET', '/will/subscribers');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function cloudPullSubscription(willId) {
|
|
128
|
+
return httpFetch('GET', '/will/subscribe/' + willId + '/pull');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- Vendor ---
|
|
132
|
+
|
|
133
|
+
export async function cloudVendorLatest() {
|
|
134
|
+
return httpFetch('GET', '/vendor/latest');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function cloudVendorChangelog() {
|
|
138
|
+
return httpFetch('GET', '/vendor/changelog');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function cloudVendorPublish(version, files, channel = 'stable') {
|
|
142
|
+
return httpFetch('POST', '/vendor/publish', { version, files, channel });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function cloudVendorDownload(version) {
|
|
146
|
+
return httpFetch('GET', '/vendor/download/' + version);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function cloudVendorDownloadLatest() {
|
|
150
|
+
return httpFetch('GET', '/vendor/download/latest');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// --- Helpers ---
|
|
154
|
+
|
|
155
|
+
export { isConfigured, getEndpoint, getApiKey };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal credentials store — ~/.memory-cli/credentials.json
|
|
3
|
+
* Only stores API endpoint, key, user_id, will_id.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
|
|
10
|
+
const CREDENTIALS_DIR = process.env.MEMORY_CREDENTIALS_DIR || join(homedir(), '.memory-cli');
|
|
11
|
+
const CREDENTIALS_PATH = join(CREDENTIALS_DIR, 'credentials.json');
|
|
12
|
+
|
|
13
|
+
const DEFAULT_CREDENTIALS = {
|
|
14
|
+
api_endpoint: null,
|
|
15
|
+
api_key: null,
|
|
16
|
+
user_id: null,
|
|
17
|
+
will_id: null,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function readCredentials() {
|
|
21
|
+
if (!existsSync(CREDENTIALS_PATH)) return { ...DEFAULT_CREDENTIALS };
|
|
22
|
+
try {
|
|
23
|
+
return { ...DEFAULT_CREDENTIALS, ...JSON.parse(readFileSync(CREDENTIALS_PATH, 'utf8')) };
|
|
24
|
+
} catch {
|
|
25
|
+
return { ...DEFAULT_CREDENTIALS };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function writeCredentials(data) {
|
|
30
|
+
mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
31
|
+
writeFileSync(CREDENTIALS_PATH, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isConfigured() {
|
|
35
|
+
const creds = readCredentials();
|
|
36
|
+
return !!(creds.api_key && creds.api_endpoint);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function saveCredentials(apiKey, userId, willId, endpoint) {
|
|
40
|
+
const creds = readCredentials();
|
|
41
|
+
creds.api_key = apiKey;
|
|
42
|
+
creds.user_id = userId;
|
|
43
|
+
creds.will_id = willId;
|
|
44
|
+
if (endpoint) creds.api_endpoint = endpoint;
|
|
45
|
+
writeCredentials(creds);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { CREDENTIALS_DIR, CREDENTIALS_PATH };
|