@memfork/cli 0.1.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.
@@ -0,0 +1,217 @@
1
+ /**
2
+ * `memfork ui` — local HTTP server.
3
+ *
4
+ * Serves the pre-built React app from app/dist/ as static files and
5
+ * exposes two API routes so the React app can discover the current tree
6
+ * config and recall MemWal facts without exposing credentials in the
7
+ * browser bundle.
8
+ *
9
+ * GET /api/config → { treeId, packageId, network, rpcUrl, hasMemwal }
10
+ * GET /api/facts → { facts: MemWal results[] } (proxied server-side)
11
+ * GET /* → index.html (SPA fallback)
12
+ * GET /assets/* → static file
13
+ */
14
+ import http from "node:http";
15
+ import fs from "node:fs";
16
+ import path from "node:path";
17
+ import { readProjectConfig, readCredentials, MEMWAL_CONSTANTS } from "../config.js";
18
+ const MIME = {
19
+ ".html": "text/html; charset=utf-8",
20
+ ".js": "application/javascript",
21
+ ".mjs": "application/javascript",
22
+ ".css": "text/css",
23
+ ".svg": "image/svg+xml",
24
+ ".png": "image/png",
25
+ ".jpg": "image/jpeg",
26
+ ".ico": "image/x-icon",
27
+ ".json": "application/json",
28
+ ".woff": "font/woff",
29
+ ".woff2": "font/woff2",
30
+ ".map": "application/json",
31
+ };
32
+ function getMime(filePath) {
33
+ return MIME[path.extname(filePath).toLowerCase()] ?? "application/octet-stream";
34
+ }
35
+ function json(res, data, status = 200) {
36
+ const body = JSON.stringify(data);
37
+ res.writeHead(status, {
38
+ "Content-Type": "application/json",
39
+ "Access-Control-Allow-Origin": "*",
40
+ "Cache-Control": "no-store",
41
+ });
42
+ res.end(body);
43
+ }
44
+ async function handleApiConfig(res) {
45
+ const project = readProjectConfig();
46
+ const creds = readCredentials();
47
+ const treeId = project?.treeId ?? creds.default ?? null;
48
+ const network = (project?.network ?? "testnet");
49
+ const stored = treeId ? creds.trees[treeId] : undefined;
50
+ json(res, {
51
+ treeId,
52
+ packageId: project?.packageId ?? "0x7df9719d799386d34d657c49ae8cd6f5f03b39036f7c428b556095e42afd852f",
53
+ network,
54
+ rpcUrl: project?.rpcUrl ?? null,
55
+ hasMemwal: !!(stored?.memwalKey && stored?.memwalAccountId),
56
+ });
57
+ }
58
+ async function memwalSearch(relayer, key, accountId, namespace, limit = 200) {
59
+ const upstream = await fetch(`${relayer}/api/search`, {
60
+ method: "POST",
61
+ headers: {
62
+ "Content-Type": "application/json",
63
+ "Authorization": `Bearer ${key}`,
64
+ "x-memwal-account-id": accountId,
65
+ },
66
+ body: JSON.stringify({ query: "", namespace, limit }),
67
+ signal: AbortSignal.timeout(8_000),
68
+ });
69
+ if (!upstream.ok)
70
+ return [];
71
+ const data = await upstream.json();
72
+ return (data["results"] ?? data["entries"] ?? []);
73
+ }
74
+ async function handleApiFacts(res, url) {
75
+ const branch = url.searchParams.get("branch") ?? "main";
76
+ const project = readProjectConfig();
77
+ const creds = readCredentials();
78
+ const treeId = project?.treeId ?? creds.default;
79
+ const network = (project?.network ?? "testnet");
80
+ const stored = treeId ? creds.trees[treeId] : undefined;
81
+ if (!stored?.memwalKey || !stored?.memwalAccountId || !treeId) {
82
+ json(res, { facts: [] });
83
+ return;
84
+ }
85
+ const relayer = stored.memwalRelayer ?? MEMWAL_CONSTANTS[network].relayer;
86
+ const treeHex = treeId.startsWith("0x") ? treeId.slice(2) : treeId;
87
+ const namespace = `memforks/${treeHex}/${branch}`;
88
+ try {
89
+ const facts = await memwalSearch(relayer, stored.memwalKey, stored.memwalAccountId, namespace);
90
+ json(res, { facts });
91
+ }
92
+ catch (e) {
93
+ json(res, { facts: [], error: String(e) });
94
+ }
95
+ }
96
+ /**
97
+ * GET /api/history?branch=<name>&limit=<n>
98
+ *
99
+ * Returns all off-chain CommitPayload objects stored in MemWal for this branch,
100
+ * sorted oldest-first. Each entry includes the MemWal blob_id plus the parsed
101
+ * payload fields that the UI needs (branch, author, ts_ms, delta, parent_blob_ids).
102
+ *
103
+ * The browser cannot call MemWal directly (SEAL-encrypted, key lives server-side),
104
+ * so this endpoint acts as the commit-history proxy.
105
+ */
106
+ async function handleApiHistory(res, url) {
107
+ const branch = url.searchParams.get("branch") ?? "main";
108
+ const limit = Math.min(Number(url.searchParams.get("limit") ?? "500"), 1000);
109
+ const project = readProjectConfig();
110
+ const creds = readCredentials();
111
+ const treeId = project?.treeId ?? creds.default;
112
+ const network = (project?.network ?? "testnet");
113
+ const stored = treeId ? creds.trees[treeId] : undefined;
114
+ if (!stored?.memwalKey || !stored?.memwalAccountId || !treeId) {
115
+ json(res, { commits: [] });
116
+ return;
117
+ }
118
+ const relayer = stored.memwalRelayer ?? MEMWAL_CONSTANTS[network].relayer;
119
+ const treeHexH = treeId.startsWith("0x") ? treeId.slice(2) : treeId;
120
+ const namespace = `memforks/${treeHexH}/${branch}`;
121
+ try {
122
+ const results = await memwalSearch(relayer, stored.memwalKey, stored.memwalAccountId, namespace, limit);
123
+ const commits = results.flatMap((entry) => {
124
+ const e = entry;
125
+ const blobId = String(e["blob_id"] ?? "");
126
+ const text = String(e["text"] ?? "");
127
+ // Try to parse the stored text as a CommitPayload JSON.
128
+ let payload = null;
129
+ try {
130
+ payload = JSON.parse(text);
131
+ }
132
+ catch {
133
+ return [];
134
+ }
135
+ if (payload["type"] !== "commit")
136
+ return [];
137
+ return [{
138
+ blob_id: blobId,
139
+ branch: String(payload["branch"] ?? branch),
140
+ ts_ms: Number(payload["ts_ms"] ?? 0),
141
+ parent_blob_ids: payload["parent_blob_ids"] ?? [],
142
+ parent_blob_hashes: payload["parent_blob_hashes"] ?? [],
143
+ // Extract readable facts from the delta.
144
+ message: (() => {
145
+ const delta = payload["delta"];
146
+ const facts = delta?.["facts"];
147
+ return facts?.length ? facts[0] : `commit ${blobId.slice(0, 8)}`;
148
+ })(),
149
+ delta: payload["delta"] ?? {},
150
+ }];
151
+ });
152
+ // Sort oldest-first by ts_ms.
153
+ commits.sort((a, b) => a.ts_ms - b.ts_ms);
154
+ json(res, { commits, branch });
155
+ }
156
+ catch (e) {
157
+ json(res, { commits: [], error: String(e) });
158
+ }
159
+ }
160
+ function serveStatic(res, distDir, urlPath) {
161
+ // Resolve the requested file path.
162
+ let filePath = path.join(distDir, urlPath);
163
+ // SPA fallback: no extension or file not found → serve index.html.
164
+ if (!path.extname(filePath) || !fs.existsSync(filePath)) {
165
+ filePath = path.join(distDir, "index.html");
166
+ }
167
+ if (!fs.existsSync(filePath)) {
168
+ res.writeHead(404, { "Content-Type": "text/plain" });
169
+ res.end("Not found");
170
+ return;
171
+ }
172
+ const mimeType = getMime(filePath);
173
+ const isImmutable = urlPath.startsWith("/assets/");
174
+ res.writeHead(200, {
175
+ "Content-Type": mimeType,
176
+ "Cache-Control": isImmutable
177
+ ? "public, max-age=31536000, immutable"
178
+ : "no-cache",
179
+ "Access-Control-Allow-Origin": "*",
180
+ });
181
+ fs.createReadStream(filePath).pipe(res);
182
+ }
183
+ export function startUiServer(distDir, port = 4242) {
184
+ const server = http.createServer((req, res) => {
185
+ const url = new URL(req.url ?? "/", `http://localhost:${port}`);
186
+ // CORS pre-flight.
187
+ if (req.method === "OPTIONS") {
188
+ res.writeHead(204, { "Access-Control-Allow-Origin": "*" });
189
+ res.end();
190
+ return;
191
+ }
192
+ if (url.pathname === "/api/config") {
193
+ handleApiConfig(res).catch((e) => json(res, { error: String(e) }, 500));
194
+ return;
195
+ }
196
+ if (url.pathname === "/api/facts") {
197
+ handleApiFacts(res, url).catch((e) => json(res, { facts: [], error: String(e) }, 500));
198
+ return;
199
+ }
200
+ if (url.pathname === "/api/history") {
201
+ handleApiHistory(res, url).catch((e) => json(res, { commits: [], error: String(e) }, 500));
202
+ return;
203
+ }
204
+ serveStatic(res, distDir, url.pathname);
205
+ });
206
+ server.listen(port, "127.0.0.1", () => {
207
+ console.log(` http://localhost:${port}`);
208
+ });
209
+ server.on("error", (e) => {
210
+ if (e.code === "EADDRINUSE") {
211
+ console.error(` Port ${port} is already in use. Is memfork ui already running?`);
212
+ process.exit(1);
213
+ }
214
+ throw e;
215
+ });
216
+ return server;
217
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Layered config resolver.
3
+ *
4
+ * Layer 1 (lowest priority) — project-local, committable:
5
+ * .memfork/config.json { treeId, network, defaultBranch, rpcUrl, packageId }
6
+ *
7
+ * Layer 2 — user-global, secrets, never committed (chmod 600):
8
+ * ~/.memfork/credentials.json
9
+ * {
10
+ * "default": "<treeId>", // which tree to use when none specified
11
+ * "trees": {
12
+ * "<treeId>": {
13
+ * "privateKey": "suiprivkey1...",
14
+ * "memwalAccountId": "0x...",
15
+ * "memwalKey": "<64-char-hex>"
16
+ * }
17
+ * }
18
+ * }
19
+ *
20
+ * Layer 3 (highest priority) — env var overrides for CI / headless use:
21
+ * MEMFORK_TREE_ID, MEMFORK_PRIVATE_KEY, MEMFORK_MEMWAL_ACCOUNT, MEMFORK_MEMWAL_KEY
22
+ *
23
+ * Plugins and hooks call the CLI binary and never read credentials themselves.
24
+ */
25
+ export declare const MEMWAL_CONSTANTS: {
26
+ readonly testnet: {
27
+ readonly packageId: "0xcf6ad755a1cdff7217865c796778fabe5aa399cb0cf2eba986f4b582047229c6";
28
+ readonly registryId: "0xe80f2feec1c139616a86c9f71210152e2a7ca552b20841f2e192f99f75864437";
29
+ readonly relayer: "https://relayer.staging.memwal.ai";
30
+ };
31
+ readonly mainnet: {
32
+ readonly packageId: "0xcee7a6fd8de52ce645c38332bde23d4a30fd9426bc4681409733dd50958a24c6";
33
+ readonly registryId: "0x0da982cefa26864ae834a8a0504b904233d49e20fcc17c373c8bed99c75a7edd";
34
+ readonly relayer: "https://relayer.memwal.ai";
35
+ };
36
+ };
37
+ export interface ProjectConfig {
38
+ /** Sui MemoryTree object ID. */
39
+ treeId?: string;
40
+ /** Sui network. Default: "testnet". */
41
+ network?: "testnet" | "mainnet" | "devnet" | "localnet";
42
+ /** Default branch name. Default: "main". */
43
+ defaultBranch?: string;
44
+ /** Override Sui RPC URL. */
45
+ rpcUrl?: string;
46
+ /** Override package ID (post-upgrade). */
47
+ packageId?: string;
48
+ }
49
+ export interface TreeCredential {
50
+ /** Ed25519 private key in bech32 suiprivkey1… format. */
51
+ privateKey: string;
52
+ /** MemWal account object ID. */
53
+ memwalAccountId: string;
54
+ /** MemWal delegate key (64-char hex). */
55
+ memwalKey: string;
56
+ /** Optional MemWal relayer URL override. */
57
+ memwalRelayer?: string;
58
+ }
59
+ export interface CredentialsFile {
60
+ /** treeId of the tree to use when no project config is present. */
61
+ default?: string;
62
+ trees: Record<string, TreeCredential>;
63
+ }
64
+ /** Fully resolved, ready-to-use config for a single tree. */
65
+ export interface ResolvedConfig {
66
+ treeId: string;
67
+ privateKey: string;
68
+ memwalAccountId: string;
69
+ memwalKey: string;
70
+ memwalRelayer: string;
71
+ network: "testnet" | "mainnet" | "devnet" | "localnet";
72
+ defaultBranch: string;
73
+ rpcUrl?: string;
74
+ packageId?: string;
75
+ }
76
+ export declare function projectConfigPath(cwd?: string): string;
77
+ export declare function credentialsPath(): string;
78
+ export declare function readProjectConfig(cwd?: string): ProjectConfig | null;
79
+ export declare function readCredentials(): CredentialsFile;
80
+ export declare function writeProjectConfig(cfg: ProjectConfig, cwd?: string): void;
81
+ export declare function writeCredentials(creds: CredentialsFile): void;
82
+ export declare function upsertCredential(treeId: string, cred: TreeCredential): void;
83
+ export declare function setDefaultTree(treeId: string): void;
84
+ export declare class ConfigError extends Error {
85
+ constructor(message: string);
86
+ }
87
+ /**
88
+ * Resolve the full config for a single tree, merging all three layers.
89
+ * Throws `ConfigError` with a human-readable message if anything is missing.
90
+ */
91
+ export declare function resolveConfig(opts?: {
92
+ treeId?: string;
93
+ cwd?: string;
94
+ }): ResolvedConfig;
95
+ /**
96
+ * Build a `MemForksClientConfig` from resolved config (for SDK calls).
97
+ * Imported by commands that need to create a MemForksClient.
98
+ */
99
+ export declare function toClientConfig(r: ResolvedConfig): {
100
+ treeId: string;
101
+ signer: string;
102
+ network: "testnet" | "mainnet" | "devnet" | "localnet";
103
+ rpcUrl: string | undefined;
104
+ packageId: string | undefined;
105
+ memwal: {
106
+ accountId: string;
107
+ delegateKey: string;
108
+ serverUrl: string;
109
+ };
110
+ };
package/dist/config.js ADDED
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Layered config resolver.
3
+ *
4
+ * Layer 1 (lowest priority) — project-local, committable:
5
+ * .memfork/config.json { treeId, network, defaultBranch, rpcUrl, packageId }
6
+ *
7
+ * Layer 2 — user-global, secrets, never committed (chmod 600):
8
+ * ~/.memfork/credentials.json
9
+ * {
10
+ * "default": "<treeId>", // which tree to use when none specified
11
+ * "trees": {
12
+ * "<treeId>": {
13
+ * "privateKey": "suiprivkey1...",
14
+ * "memwalAccountId": "0x...",
15
+ * "memwalKey": "<64-char-hex>"
16
+ * }
17
+ * }
18
+ * }
19
+ *
20
+ * Layer 3 (highest priority) — env var overrides for CI / headless use:
21
+ * MEMFORK_TREE_ID, MEMFORK_PRIVATE_KEY, MEMFORK_MEMWAL_ACCOUNT, MEMFORK_MEMWAL_KEY
22
+ *
23
+ * Plugins and hooks call the CLI binary and never read credentials themselves.
24
+ */
25
+ import fs from "node:fs";
26
+ import path from "node:path";
27
+ import os from "node:os";
28
+ // ─── Public network constants ─────────────────────────────────────────────────
29
+ // Sources: https://docs.memwal.ai/contract/overview
30
+ export const MEMWAL_CONSTANTS = {
31
+ testnet: {
32
+ packageId: "0xcf6ad755a1cdff7217865c796778fabe5aa399cb0cf2eba986f4b582047229c6",
33
+ registryId: "0xe80f2feec1c139616a86c9f71210152e2a7ca552b20841f2e192f99f75864437",
34
+ relayer: "https://relayer.staging.memwal.ai",
35
+ },
36
+ mainnet: {
37
+ packageId: "0xcee7a6fd8de52ce645c38332bde23d4a30fd9426bc4681409733dd50958a24c6",
38
+ registryId: "0x0da982cefa26864ae834a8a0504b904233d49e20fcc17c373c8bed99c75a7edd",
39
+ relayer: "https://relayer.memwal.ai",
40
+ },
41
+ };
42
+ // ─── Paths ────────────────────────────────────────────────────────────────────
43
+ const DEFAULT_RELAYER = "https://relayer.staging.memwal.ai";
44
+ export function projectConfigPath(cwd = process.cwd()) {
45
+ return path.join(cwd, ".memfork", "config.json");
46
+ }
47
+ /**
48
+ * Walk up the directory tree from `cwd` looking for a `.memfork/config.json`,
49
+ * just like git looks for `.git`. Returns the first one found, or null.
50
+ */
51
+ function findProjectConfigPath(cwd = process.cwd()) {
52
+ let dir = cwd;
53
+ while (true) {
54
+ const candidate = path.join(dir, ".memfork", "config.json");
55
+ if (fs.existsSync(candidate))
56
+ return candidate;
57
+ const parent = path.dirname(dir);
58
+ if (parent === dir)
59
+ return null; // reached filesystem root
60
+ dir = parent;
61
+ }
62
+ }
63
+ export function credentialsPath() {
64
+ return path.join(os.homedir(), ".memfork", "credentials.json");
65
+ }
66
+ // ─── Read helpers ─────────────────────────────────────────────────────────────
67
+ export function readProjectConfig(cwd = process.cwd()) {
68
+ const p = findProjectConfigPath(cwd);
69
+ if (!p)
70
+ return null;
71
+ try {
72
+ return JSON.parse(fs.readFileSync(p, "utf8"));
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ export function readCredentials() {
79
+ const p = credentialsPath();
80
+ if (!fs.existsSync(p))
81
+ return { trees: {} };
82
+ try {
83
+ return JSON.parse(fs.readFileSync(p, "utf8"));
84
+ }
85
+ catch {
86
+ return { trees: {} };
87
+ }
88
+ }
89
+ // ─── Write helpers ────────────────────────────────────────────────────────────
90
+ export function writeProjectConfig(cfg, cwd = process.cwd()) {
91
+ const root = findGitRoot(cwd) ?? cwd;
92
+ const dir = path.join(root, ".memfork");
93
+ fs.mkdirSync(dir, { recursive: true });
94
+ fs.writeFileSync(path.join(root, ".memfork", "config.json"), JSON.stringify(cfg, null, 2) + "\n", "utf8");
95
+ }
96
+ function findGitRoot(cwd) {
97
+ let dir = cwd;
98
+ while (true) {
99
+ if (fs.existsSync(path.join(dir, ".git")))
100
+ return dir;
101
+ const parent = path.dirname(dir);
102
+ if (parent === dir)
103
+ return null;
104
+ dir = parent;
105
+ }
106
+ }
107
+ export function writeCredentials(creds) {
108
+ const dir = path.join(os.homedir(), ".memfork");
109
+ fs.mkdirSync(dir, { recursive: true });
110
+ const p = credentialsPath();
111
+ fs.writeFileSync(p, JSON.stringify(creds, null, 2) + "\n", "utf8");
112
+ // 600: owner read+write only — no other users can read private keys.
113
+ fs.chmodSync(p, 0o600);
114
+ }
115
+ export function upsertCredential(treeId, cred) {
116
+ const creds = readCredentials();
117
+ creds.trees[treeId] = cred;
118
+ if (!creds.default)
119
+ creds.default = treeId;
120
+ writeCredentials(creds);
121
+ }
122
+ export function setDefaultTree(treeId) {
123
+ const creds = readCredentials();
124
+ creds.default = treeId;
125
+ writeCredentials(creds);
126
+ }
127
+ // ─── Resolution ───────────────────────────────────────────────────────────────
128
+ export class ConfigError extends Error {
129
+ constructor(message) {
130
+ super(message);
131
+ this.name = "ConfigError";
132
+ }
133
+ }
134
+ /**
135
+ * Resolve the full config for a single tree, merging all three layers.
136
+ * Throws `ConfigError` with a human-readable message if anything is missing.
137
+ */
138
+ export function resolveConfig(opts = {}) {
139
+ const project = readProjectConfig(opts.cwd);
140
+ const creds = readCredentials();
141
+ const env = process.env;
142
+ // ── Resolve treeId ──────────────────────────────────────────────────────────
143
+ const treeId = env["MEMFORK_TREE_ID"] ??
144
+ opts.treeId ??
145
+ project?.treeId ??
146
+ creds.default;
147
+ if (!treeId) {
148
+ throw new ConfigError("No MemoryTree found. Run `memfork init` to create or link one.");
149
+ }
150
+ // ── Resolve credentials ────────────────────────────────────────────────────
151
+ const stored = creds.trees[treeId];
152
+ const privateKey = env["MEMFORK_PRIVATE_KEY"] ??
153
+ stored?.privateKey;
154
+ const memwalAccountId = env["MEMFORK_MEMWAL_ACCOUNT"] ??
155
+ stored?.memwalAccountId;
156
+ const memwalKey = env["MEMFORK_MEMWAL_KEY"] ??
157
+ stored?.memwalKey;
158
+ if (!privateKey) {
159
+ throw new ConfigError(`No private key for tree ${treeId}. Run \`memfork init\` or set MEMFORK_PRIVATE_KEY.`);
160
+ }
161
+ if (!memwalAccountId) {
162
+ throw new ConfigError(`No MemWal account for tree ${treeId}. Run \`memfork init\` or set MEMFORK_MEMWAL_ACCOUNT.`);
163
+ }
164
+ if (!memwalKey) {
165
+ throw new ConfigError(`No MemWal delegate key for tree ${treeId}. Run \`memfork init\` or set MEMFORK_MEMWAL_KEY.`);
166
+ }
167
+ // ── Merge non-secret config ────────────────────────────────────────────────
168
+ const network = (env["MEMFORK_NETWORK"] ??
169
+ project?.network ??
170
+ "mainnet");
171
+ return {
172
+ treeId,
173
+ privateKey,
174
+ memwalAccountId,
175
+ memwalKey,
176
+ memwalRelayer: stored?.memwalRelayer ?? DEFAULT_RELAYER,
177
+ network,
178
+ defaultBranch: project?.defaultBranch ?? "main",
179
+ rpcUrl: env["MEMFORK_RPC_URL"] ?? project?.rpcUrl,
180
+ packageId: env["MEMFORK_PACKAGE_ID"] ?? project?.packageId,
181
+ };
182
+ }
183
+ /**
184
+ * Build a `MemForksClientConfig` from resolved config (for SDK calls).
185
+ * Imported by commands that need to create a MemForksClient.
186
+ */
187
+ export function toClientConfig(r) {
188
+ return {
189
+ treeId: r.treeId,
190
+ signer: r.privateKey,
191
+ network: r.network,
192
+ rpcUrl: r.rpcUrl,
193
+ packageId: r.packageId,
194
+ memwal: {
195
+ accountId: r.memwalAccountId,
196
+ delegateKey: r.memwalKey,
197
+ serverUrl: r.memwalRelayer,
198
+ },
199
+ };
200
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @memfork/cli — library exports
3
+ *
4
+ * Import the config API from here:
5
+ * import { resolveConfig, toClientConfig } from "@memfork/cli"
6
+ *
7
+ * The CLI binary entry point is src/cli.ts → dist/cli.js
8
+ */
9
+ export { resolveConfig, toClientConfig, readProjectConfig, writeProjectConfig, readCredentials, writeCredentials, upsertCredential, setDefaultTree, projectConfigPath, credentialsPath, ConfigError, } from "./config.js";
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @memfork/cli — library exports
3
+ *
4
+ * Import the config API from here:
5
+ * import { resolveConfig, toClientConfig } from "@memfork/cli"
6
+ *
7
+ * The CLI binary entry point is src/cli.ts → dist/cli.js
8
+ */
9
+ export { resolveConfig, toClientConfig, readProjectConfig, writeProjectConfig, readCredentials, writeCredentials, upsertCredential, setDefaultTree, projectConfigPath, credentialsPath, ConfigError, } from "./config.js";
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@memfork/cli",
3
+ "version": "0.1.0",
4
+ "description": "MemForks CLI — init, commit, recall, merge, install plugins",
5
+ "type": "module",
6
+ "bin": {
7
+ "memfork": "dist/cli.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "exports": {
11
+ ".": "./dist/index.js",
12
+ "./config": "./dist/config.js"
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "dev": "tsc --watch",
17
+ "start": "node dist/index.js"
18
+ },
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "files": [
23
+ "dist"
24
+ ],
25
+ "dependencies": {
26
+ "@inquirer/prompts": "^8.5.2",
27
+ "@memfork/core": "^0.1.0",
28
+ "chalk": "^5.6.2",
29
+ "commander": "^15.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^20.0.0",
33
+ "typescript": "^5.4.0"
34
+ },
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
38
+ "license": "Apache-2.0"
39
+ }