withub-cli 0.1.0 → 0.2.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.
@@ -17,12 +17,17 @@ const serialize_1 = require("../lib/serialize");
17
17
  const repo_1 = require("../lib/repo");
18
18
  const seal_1 = require("../lib/seal");
19
19
  const keys_1 = require("../lib/keys");
20
+ const lit_1 = require("../lib/lit");
21
+ const crypto_1 = require("../lib/crypto");
22
+ const lighthouse_1 = require("../lib/lighthouse");
23
+ const evmProvider_1 = require("../lib/evmProvider");
20
24
  const WIT_DIR = '.wit';
21
25
  async function checkoutAction(commitId) {
22
26
  const witPath = await requireWitDir();
23
27
  const repoCfg = await (0, repo_1.readRepoConfig)(witPath);
24
28
  const commit = await (0, state_1.readCommitById)(witPath, commitId);
25
- const files = await resolveFilesForCommit(witPath, commit, repoCfg.seal_policy_id || null);
29
+ const sealPolicyId = (0, repo_1.resolveSuiSealPolicyId)(repoCfg);
30
+ const files = await resolveFilesForCommit(witPath, commit, sealPolicyId, repoCfg);
26
31
  if (!files) {
27
32
  // Friendly message already printed inside resolveFilesForCommit/ensureBlobsFromManifest
28
33
  // eslint-disable-next-line no-console
@@ -84,11 +89,11 @@ function safeJoin(base, rel) {
84
89
  }
85
90
  return path_1.default.join(base, norm);
86
91
  }
87
- async function resolveFilesForCommit(witPath, commit, sealPolicyId) {
92
+ async function resolveFilesForCommit(witPath, commit, sealPolicyId, repoCfg) {
88
93
  if (commit.tree?.files && Object.keys(commit.tree.files).length) {
89
94
  return commit.tree.files;
90
95
  }
91
- const manifestId = commit.tree?.manifest_id;
96
+ const manifestId = commit.tree?.manifest_id || commit.tree?.manifest_cid;
92
97
  if (!manifestId) {
93
98
  throw new Error('Commit has no file list or manifest_id; cannot checkout.');
94
99
  }
@@ -100,7 +105,7 @@ async function resolveFilesForCommit(witPath, commit, sealPolicyId) {
100
105
  if (computedRoot !== commit.tree.root_hash) {
101
106
  throw new Error('Commit root_hash does not match manifest.');
102
107
  }
103
- const ok = await ensureBlobsFromManifest(witPath, manifest, sealPolicyId);
108
+ const ok = await ensureBlobsFromManifest(witPath, manifest, sealPolicyId, repoCfg);
104
109
  if (!ok) {
105
110
  return null;
106
111
  }
@@ -117,16 +122,35 @@ async function loadManifest(witPath, manifestId) {
117
122
  throw err;
118
123
  }
119
124
  // fetch from walrus
120
- const walrusSvc = await walrus_1.WalrusService.fromRepo();
121
- // eslint-disable-next-line no-console
122
- console.log(ui_1.colors.cyan(`Fetching manifest ${manifestId} from Walrus...`));
123
- const buf = Buffer.from(await walrusSvc.readBlob(manifestId));
125
+ // fetch from walrus or lighthouse
126
+ let buf;
127
+ try {
128
+ const walrusSvc = await walrus_1.WalrusService.fromRepo();
129
+ // eslint-disable-next-line no-console
130
+ console.log(ui_1.colors.cyan(`Fetching manifest ${manifestId} from Walrus...`));
131
+ buf = Buffer.from(await walrusSvc.readBlob(manifestId));
132
+ }
133
+ catch (err) {
134
+ // If Walrus fails or not configured, try Lighthouse (Mantle fallback)
135
+ // eslint-disable-next-line no-console
136
+ console.log(ui_1.colors.cyan(`Fetching manifest ${manifestId} from Lighthouse...`));
137
+ const res = await (0, lighthouse_1.downloadFromLighthouseGateway)(manifestId, { verify: false });
138
+ buf = Buffer.from(res.bytes);
139
+ }
124
140
  const manifest = schema_1.ManifestSchema.parse(JSON.parse(buf.toString('utf8')));
125
141
  await promises_1.default.mkdir(path_1.default.dirname(file), { recursive: true });
126
142
  await promises_1.default.writeFile(file, buf.toString('utf8'), 'utf8');
127
143
  return manifest;
128
144
  }
129
- async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId) {
145
+ async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId, repoCfg) {
146
+ if (repoCfg.chain === 'mantle') {
147
+ return ensureBlobsMantle(witPath, manifest, repoCfg);
148
+ }
149
+ else {
150
+ return ensureBlobsSui(witPath, manifest, sealPolicyId);
151
+ }
152
+ }
153
+ async function ensureBlobsSui(witPath, manifest, sealPolicyId) {
130
154
  const entries = Object.entries(manifest.files);
131
155
  const missing = [];
132
156
  for (const [rel, meta] of entries) {
@@ -137,10 +161,14 @@ async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId) {
137
161
  }
138
162
  if (!missing.length)
139
163
  return true;
140
- const walrusSvc = await walrus_1.WalrusService.fromRepo();
141
164
  // eslint-disable-next-line no-console
142
165
  console.log(ui_1.colors.cyan(`Fetching ${missing.length} missing blobs from Walrus...`));
143
- // Setup for decryption if needed
166
+ let walrusSvc = null;
167
+ try {
168
+ walrusSvc = await walrus_1.WalrusService.fromRepo();
169
+ }
170
+ catch { }
171
+ // Setup for decryption
144
172
  let signerInfo = null;
145
173
  let suiClient = null;
146
174
  const hasEncrypted = entries.some(([, meta]) => meta.enc);
@@ -150,8 +178,10 @@ async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId) {
150
178
  suiClient = new client_1.SuiClient({ url: resolved.suiRpcUrl });
151
179
  }
152
180
  for (const { rel, meta } of missing) {
181
+ if (!walrusSvc)
182
+ throw new Error('Walrus service not initialized');
153
183
  const data = await fetchFileBytes(walrusSvc, manifest, rel, meta);
154
- let plain;
184
+ let plain = data;
155
185
  try {
156
186
  if (meta.enc) {
157
187
  if (!signerInfo || !suiClient) {
@@ -168,13 +198,10 @@ async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId) {
168
198
  };
169
199
  plain = await (0, seal_1.decryptWithSeal)(data, encMeta, signerInfo.signer, suiClient);
170
200
  }
171
- else {
172
- plain = data;
173
- }
174
201
  }
175
202
  catch (err) {
176
203
  // eslint-disable-next-line no-console
177
- console.log(ui_1.colors.red(`Seal decryption failed for ${rel}. Check if you are whitelisted.`));
204
+ console.log(ui_1.colors.red(`❌ Access Denied or Decryption Failed for ${rel}: ${err.message}`));
178
205
  return false;
179
206
  }
180
207
  const hash = (0, serialize_1.sha256Base64)(plain);
@@ -187,6 +214,95 @@ async function ensureBlobsFromManifest(witPath, manifest, sealPolicyId) {
187
214
  }
188
215
  return true;
189
216
  }
217
+ async function ensureBlobsMantle(witPath, manifest, repoCfg) {
218
+ const entries = Object.entries(manifest.files);
219
+ const missing = [];
220
+ for (const [rel, meta] of entries) {
221
+ const buf = await (0, fs_1.readBlob)(witPath, meta.hash);
222
+ if (!buf) {
223
+ missing.push({ rel, meta });
224
+ }
225
+ }
226
+ if (!missing.length)
227
+ return true;
228
+ // eslint-disable-next-line no-console
229
+ console.log(ui_1.colors.cyan(`Fetching ${missing.length} missing blobs (Lighthouse/Mantle)...`));
230
+ let litService = null;
231
+ let authSig = null;
232
+ let mantleSigner = null;
233
+ const hasEncrypted = entries.some(([, meta]) => meta.enc);
234
+ if (hasEncrypted) {
235
+ litService = new lit_1.LitService();
236
+ mantleSigner = await (0, evmProvider_1.loadMantleSigner)();
237
+ }
238
+ for (const { rel, meta } of missing) {
239
+ let data;
240
+ if (meta.cid) { // Lighthouse/IPFS
241
+ const res = await (0, lighthouse_1.downloadFromLighthouseGateway)(meta.cid, { verify: false });
242
+ data = Buffer.from(res.bytes);
243
+ }
244
+ else {
245
+ throw new Error(`Cannot download ${rel}: missing CID for Mantle repo.`);
246
+ }
247
+ let plain = data;
248
+ try {
249
+ if (meta.enc) {
250
+ const encAny = meta.enc;
251
+ if (encAny.alg === 'lit-aes-256-gcm') {
252
+ // Lit Decryption
253
+ if (!litService || !mantleSigner)
254
+ throw new Error('Lit/Mantle env not initialized');
255
+ if (!authSig) {
256
+ // eslint-disable-next-line no-console
257
+ console.log(ui_1.colors.gray(' Generating SIWE AuthSig...'));
258
+ authSig = await litService.getAuthSig(mantleSigner.signer);
259
+ }
260
+ const encMeta = {
261
+ alg: encAny.alg,
262
+ lit_encrypted_key: encAny.lit_encrypted_key,
263
+ unified_access_control_conditions: encAny.unified_access_control_conditions || encAny.access_control_conditions,
264
+ lit_hash: encAny.lit_hash || encAny.enc_hash, // fallback
265
+ iv: encAny.iv,
266
+ tag: encAny.tag
267
+ };
268
+ const sessionKey = await litService.decryptSessionKey(encMeta.lit_encrypted_key, encMeta.lit_hash, encMeta.unified_access_control_conditions, authSig);
269
+ plain = (0, crypto_1.decryptBuffer)({
270
+ ciphertext: data,
271
+ iv: Buffer.from(encMeta.iv, 'hex'),
272
+ authTag: Buffer.from(encMeta.tag, 'hex'),
273
+ }, sessionKey);
274
+ }
275
+ else {
276
+ throw new Error(`Unsupported encryption alg on Mantle: ${encAny.alg}`);
277
+ }
278
+ }
279
+ }
280
+ catch (err) {
281
+ if (err.message && (err.message.includes('NotAuthorized') || err.message.includes('not authorized'))) {
282
+ // eslint-disable-next-line no-console
283
+ console.log(ui_1.colors.red(`❌ Access Denied: You do not have permission to decrypt ${rel}.`));
284
+ }
285
+ else {
286
+ // eslint-disable-next-line no-console
287
+ console.log(ui_1.colors.red(`❌ Failed to decrypt ${rel}: ${err.message}`));
288
+ }
289
+ if (litService)
290
+ await litService.disconnect();
291
+ return false;
292
+ }
293
+ const hash = (0, serialize_1.sha256Base64)(plain);
294
+ if (hash !== meta.hash || plain.length !== meta.size) {
295
+ throw new Error(`Downloaded blob mismatch for ${rel} (hash: ${hash}, expected: ${meta.hash})`);
296
+ }
297
+ const blobPath = path_1.default.join(witPath, 'objects', 'blobs', (0, fs_1.blobFileName)(meta.hash));
298
+ await (0, fs_1.ensureDirForFile)(blobPath);
299
+ await promises_1.default.writeFile(blobPath, plain);
300
+ }
301
+ if (litService) {
302
+ await litService.disconnect();
303
+ }
304
+ return true;
305
+ }
190
306
  function manifestIdToFile(id) {
191
307
  return id.replace(/\//g, '_').replace(/\+/g, '-');
192
308
  }
@@ -209,5 +325,10 @@ async function fetchFileBytes(walrusSvc, manifest, rel, meta) {
209
325
  }
210
326
  return data;
211
327
  }
328
+ // Fallback for types that might pass meta with just cid (cached manually handled above usually)
329
+ // But if ensureBlobsFromManifest calls this for a non-walrus file, safeguard:
330
+ if (meta.enc || meta.cid) {
331
+ throw new Error(`Should have been handled by Lighthouse downloader.`);
332
+ }
212
333
  throw new Error(`Manifest entry missing Walrus file id for ${rel}`);
213
334
  }
@@ -18,12 +18,34 @@ const state_1 = require("../lib/state");
18
18
  const repo_1 = require("../lib/repo");
19
19
  const seal_1 = require("../lib/seal");
20
20
  const keys_1 = require("../lib/keys");
21
+ const chain_1 = require("../lib/chain");
22
+ const evmClone_1 = require("../lib/evmClone");
21
23
  const DEFAULT_RELAYS = ['https://upload-relay.testnet.walrus.space'];
22
24
  const DEFAULT_NETWORK = 'testnet';
23
25
  async function cloneAction(repoId) {
24
26
  if (!repoId) {
25
27
  throw new Error('Usage: wit clone <repo_id>');
26
28
  }
29
+ const activeChain = await (0, chain_1.readActiveChain)();
30
+ const repoChain = inferRepoChain(repoId, activeChain);
31
+ if (repoId.startsWith('mantle:') || repoChain === 'mantle') {
32
+ try {
33
+ await (0, evmClone_1.cloneFromMantle)(repoId);
34
+ return;
35
+ }
36
+ catch (err) {
37
+ // eslint-disable-next-line no-console
38
+ console.error(ui_1.colors.red(`Clone failed: ${err.message}`));
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ }
43
+ if (repoChain !== activeChain) {
44
+ // eslint-disable-next-line no-console
45
+ console.error(ui_1.colors.red((0, chain_1.formatChainMismatchMessage)(repoChain, activeChain)));
46
+ process.exitCode = 1;
47
+ return;
48
+ }
27
49
  // eslint-disable-next-line no-console
28
50
  console.log(ui_1.colors.header('Starting clone...'));
29
51
  // Prepare .wit layout and config
@@ -57,7 +79,7 @@ async function cloneAction(repoId) {
57
79
  try {
58
80
  const cfgRaw = await promises_1.default.readFile(path_1.default.join(witPath, 'config.json'), 'utf8');
59
81
  const cfg = JSON.parse(cfgRaw);
60
- cfg.seal_policy_id = onchain.sealPolicyId;
82
+ (0, repo_1.setSuiSealPolicyId)(cfg, onchain.sealPolicyId);
61
83
  await (0, repo_1.writeRepoConfig)(witPath, cfg);
62
84
  }
63
85
  catch {
@@ -205,14 +227,15 @@ async function ensureLayout(cwd, repoId) {
205
227
  }
206
228
  catch (err) {
207
229
  if (err?.code === 'ENOENT') {
230
+ const activeChain = await (0, chain_1.readActiveChain)();
231
+ const repoChain = inferRepoChain(repoId, activeChain);
232
+ const chainConfig = buildChainConfig(repoChain);
208
233
  const cfg = {
209
234
  repo_name: repoId,
210
235
  repo_id: repoId,
211
- network: DEFAULT_NETWORK,
212
- relays: DEFAULT_RELAYS,
213
- author: 'unknown',
214
- key_alias: 'default',
215
- seal_policy_id: null,
236
+ chain: repoChain,
237
+ chains: { [repoChain]: chainConfig },
238
+ network: inferNetwork(repoChain),
216
239
  created_at: new Date().toISOString(),
217
240
  };
218
241
  await (0, repo_1.writeRepoConfig)(witPath, cfg);
@@ -224,13 +247,47 @@ async function ensureLayout(cwd, repoId) {
224
247
  await promises_1.default.writeFile(path_1.default.join(witPath, 'HEAD'), 'refs/heads/main\n', 'utf8');
225
248
  return witPath;
226
249
  }
250
+ function inferRepoChain(repoId, fallback) {
251
+ if (repoId.startsWith('mantle:'))
252
+ return 'mantle';
253
+ if (repoId.startsWith('sui:'))
254
+ return 'sui';
255
+ return fallback;
256
+ }
257
+ function inferNetwork(repoChain) {
258
+ if (repoChain === 'mantle')
259
+ return 'testnet';
260
+ else if (repoChain === 'sui')
261
+ return DEFAULT_NETWORK;
262
+ throw new Error(`Unsupported chain "${repoChain}".`);
263
+ }
264
+ function buildChainConfig(repoChain) {
265
+ if (repoChain === 'sui') {
266
+ return {
267
+ author: 'unknown',
268
+ key_alias: 'default',
269
+ network: DEFAULT_NETWORK,
270
+ relays: DEFAULT_RELAYS,
271
+ seal_policy_id: null,
272
+ storage_backend: 'walrus',
273
+ };
274
+ }
275
+ else if (repoChain === 'mantle') {
276
+ return {
277
+ author: 'unknown',
278
+ storage_backend: 'ipfs',
279
+ };
280
+ }
281
+ throw new Error(`Unsupported chain "${repoChain}".`);
282
+ }
227
283
  async function ensureHeadFiles(witPath, headCommit) {
228
284
  const headRefPath = path_1.default.join(witPath, 'refs', 'heads', 'main');
229
285
  await promises_1.default.writeFile(headRefPath, `${headCommit}\n`, 'utf8');
230
286
  }
231
287
  function parseRemoteCommit(buf) {
232
288
  const parsed = JSON.parse(buf.toString('utf8'));
233
- if (!parsed?.tree?.root_hash || !parsed?.tree?.manifest_id) {
289
+ const hasManifestRef = Boolean(parsed?.tree?.manifest_id || parsed?.tree?.manifest_cid);
290
+ if (!parsed?.tree?.root_hash || !hasManifestRef) {
234
291
  throw new Error('Invalid remote commit object');
235
292
  }
236
293
  return parsed;
@@ -27,8 +27,9 @@ async function commitAction(opts) {
27
27
  console.warn('Index is empty. Nothing to commit.');
28
28
  return;
29
29
  }
30
- const config = await readConfig(witPath);
31
- if (!config.author || config.author === 'unknown') {
30
+ const repoCfg = await (0, repo_1.readRepoConfig)(witPath);
31
+ const author = (0, repo_1.resolveChainAuthor)(repoCfg);
32
+ if (!author || author === 'unknown') {
32
33
  // eslint-disable-next-line no-console
33
34
  console.warn(ui_1.colors.yellow('Warning: author is unknown. Set author in .wit/config.json or ~/.witconfig.'));
34
35
  }
@@ -43,7 +44,7 @@ async function commitAction(opts) {
43
44
  files: index,
44
45
  },
45
46
  parent,
46
- author: config.author || 'unknown',
47
+ author: author || 'unknown',
47
48
  message,
48
49
  timestamp: Math.floor(Date.now() / 1000),
49
50
  extras: { patch_id: null, tags: {} },
@@ -107,19 +108,6 @@ async function requireWitDir() {
107
108
  throw new Error('Not a wit repository (missing .wit). Run `wit init` first.');
108
109
  }
109
110
  }
110
- async function readConfig(witPath) {
111
- const file = path_1.default.join(witPath, 'config.json');
112
- try {
113
- const raw = await promises_1.default.readFile(file, 'utf8');
114
- return JSON.parse(raw);
115
- }
116
- catch (err) {
117
- if (err?.code === 'ENOENT') {
118
- return {};
119
- }
120
- throw err;
121
- }
122
- }
123
111
  async function writeCommitObject(witPath, commitId, serialized) {
124
112
  const file = path_1.default.join(witPath, 'objects', 'commits', `${(0, state_1.idToFileName)(commitId)}.json`);
125
113
  await promises_1.default.writeFile(file, serialized, 'utf8');
@@ -21,6 +21,14 @@ async function fetchAction() {
21
21
  if (!repoCfg.repo_id) {
22
22
  throw new Error('Missing repo_id in .wit/config.json. Cannot fetch.');
23
23
  }
24
+ if (repoCfg.chain === 'mantle') {
25
+ return fetchActionMantle(witPath, repoCfg);
26
+ }
27
+ else {
28
+ return fetchActionSui(witPath, repoCfg);
29
+ }
30
+ }
31
+ async function fetchActionSui(witPath, repoCfg) {
24
32
  // eslint-disable-next-line no-console
25
33
  console.log(ui_1.colors.header('Fetching remote metadata...'));
26
34
  const resolved = await (0, walrus_1.resolveWalrusConfig)(process.cwd());
@@ -32,9 +40,10 @@ async function fetchAction() {
32
40
  console.log(ui_1.colors.yellow('Remote repository has no head; nothing to fetch.'));
33
41
  return;
34
42
  }
35
- if (onchain.sealPolicyId && repoCfg.seal_policy_id !== onchain.sealPolicyId) {
36
- repoCfg.seal_policy_id = onchain.sealPolicyId;
37
- await promises_1.default.writeFile(path_1.default.join(witPath, 'config.json'), JSON.stringify(repoCfg, null, 2) + '\n', 'utf8');
43
+ const currentPolicyId = (0, repo_1.resolveSuiSealPolicyId)(repoCfg);
44
+ if (onchain.sealPolicyId && onchain.sealPolicyId !== currentPolicyId) {
45
+ (0, repo_1.setSuiSealPolicyId)(repoCfg, onchain.sealPolicyId);
46
+ await (0, repo_1.writeRepoConfig)(witPath, repoCfg);
38
47
  }
39
48
  // Download manifest and commit for validation/cache
40
49
  const manifest = await loadManifestCached(walrusSvc, witPath, onchain.headManifest);
@@ -71,13 +80,76 @@ async function fetchAction() {
71
80
  // eslint-disable-next-line no-console
72
81
  console.log(`Quilt: ${ui_1.colors.hash(onchain.headQuilt)}`);
73
82
  }
83
+ // --------------------------------------------------------------------------
84
+ // MANTLE IMPLEMENTATION
85
+ // --------------------------------------------------------------------------
86
+ const evmProvider_1 = require("../lib/evmProvider");
87
+ const evmRepo_1 = require("../lib/evmRepo");
88
+ const lighthouse_1 = require("../lib/lighthouse");
89
+ const evmClone_1 = require("../lib/evmClone");
90
+ async function fetchActionMantle(witPath, repoCfg) {
91
+ // eslint-disable-next-line no-console
92
+ console.log(ui_1.colors.header('Fetching remote metadata (Mantle)...'));
93
+ // 1. Load Contract State
94
+ const signerCtx = await (0, evmProvider_1.loadMantleSigner)();
95
+ const repoService = new evmRepo_1.EvmRepoService(signerCtx);
96
+ let repoId = BigInt(0);
97
+ const repoIdStr = repoCfg.repo_id;
98
+ if (repoIdStr.startsWith('mantle:')) {
99
+ repoId = BigInt(repoIdStr.split(':').pop());
100
+ }
101
+ else {
102
+ repoId = BigInt(repoIdStr);
103
+ }
104
+ const onchain = await repoService.getRepoState(repoId);
105
+ if (!onchain || !onchain.headCommit) {
106
+ // eslint-disable-next-line no-console
107
+ console.log(ui_1.colors.yellow('Remote repository has no head; nothing to fetch.'));
108
+ return;
109
+ }
110
+ // 2. Download Head Commit & Manifest
111
+ // eslint-disable-next-line no-console
112
+ console.log(ui_1.colors.cyan(`Remote Head: ${onchain.headCommit}`));
113
+ const commitBuf = await downloadBuffer(onchain.headCommit);
114
+ const commit = parseRemoteCommit(commitBuf);
115
+ await cacheJson(path_1.default.join(witPath, 'objects', 'commits', `${(0, state_1.idToFileName)(onchain.headCommit)}.json`), commitBuf.toString('utf8'));
116
+ const manifestCid = commit.tree.manifest_cid || commit.tree.manifest_id;
117
+ if (!manifestCid) {
118
+ throw new Error('Remote commit missing manifest_cid');
119
+ }
120
+ const manifestBuf = await downloadBuffer(manifestCid);
121
+ // Cache manifest
122
+ await cacheJson(path_1.default.join(witPath, 'objects', 'manifests', `${(0, state_1.idToFileName)(manifestCid)}.json`), manifestBuf.toString('utf8'));
123
+ // 3. Sync History (Incremental)
124
+ const map = await readCommitIdMapSafe(witPath);
125
+ await (0, evmClone_1.downloadCommitChainMantle)(onchain.headCommit, witPath, map);
126
+ await (0, state_1.writeCommitIdMap)(witPath, map);
127
+ // 4. Update References
128
+ await (0, repo_1.writeRemoteRef)(witPath, onchain.headCommit);
129
+ await (0, repo_1.writeRemoteState)(witPath, {
130
+ repo_id: repoCfg.repo_id,
131
+ head_commit: onchain.headCommit,
132
+ head_manifest: manifestCid,
133
+ head_quilt: '',
134
+ version: Number(onchain.version),
135
+ });
136
+ // eslint-disable-next-line no-console
137
+ console.log(ui_1.colors.green('Fetch complete (worktree unchanged).'));
138
+ console.log(`Head: ${ui_1.colors.hash(onchain.headCommit)}`);
139
+ }
140
+ async function downloadBuffer(cid) {
141
+ const res = await (0, lighthouse_1.downloadFromLighthouseGateway)(cid, { verify: false });
142
+ return Buffer.from(res.bytes);
143
+ }
144
+ // function downloadCommitChainMantle moved to ../lib/evmClone.ts
74
145
  async function cacheJson(filePath, content) {
75
146
  await promises_1.default.mkdir(path_1.default.dirname(filePath), { recursive: true });
76
147
  await promises_1.default.writeFile(filePath, content, 'utf8');
77
148
  }
78
149
  function parseRemoteCommit(buf) {
79
150
  const parsed = JSON.parse(buf.toString('utf8'));
80
- if (!parsed?.tree?.root_hash || !parsed?.tree?.manifest_id) {
151
+ const hasManifestRef = Boolean(parsed?.tree?.manifest_id || parsed?.tree?.manifest_cid);
152
+ if (!parsed?.tree?.root_hash || !hasManifestRef) {
81
153
  throw new Error('Invalid remote commit object');
82
154
  }
83
155
  return parsed;
@@ -7,9 +7,12 @@ exports.initAction = initAction;
7
7
  const promises_1 = __importDefault(require("fs/promises"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const keys_1 = require("../lib/keys");
10
+ const chain_1 = require("../lib/chain");
11
+ const evmKeys_1 = require("../lib/evmKeys");
12
+ const ui_1 = require("../lib/ui");
10
13
  const DEFAULT_RELAYS = ['https://upload-relay.testnet.walrus.space'];
11
14
  const DEFAULT_NETWORK = 'testnet';
12
- const IGNORE_ENTRIES = ['.wit/', '~/.wit/keys', '.env.local', '*.pem', '.wit/seal'];
15
+ const IGNORE_ENTRIES = ['.wit/', '~/.wit/keys', '~/.wit/keys-sui', '~/.wit/keys-evm', '.env.local', '*.pem', '.wit/seal'];
13
16
  async function initAction(name, options) {
14
17
  const cwd = process.cwd();
15
18
  const repoName = name || path_1.default.basename(cwd);
@@ -17,14 +20,41 @@ async function initAction(name, options) {
17
20
  await promises_1.default.mkdir(witDir, { recursive: true });
18
21
  await ensureLayout(witDir);
19
22
  const globalCfg = await readGlobalConfig();
20
- const activeAddress = await (0, keys_1.readActiveAddress)();
21
- const repoCfg = buildRepoConfig(repoName, globalCfg, activeAddress);
23
+ const activeChain = await (0, chain_1.readActiveChain)();
24
+ let activeAddress = null;
25
+ if (activeChain === 'mantle') {
26
+ activeAddress = await (0, evmKeys_1.readActiveEvmAddress)();
27
+ }
28
+ else if (activeChain === 'sui') {
29
+ activeAddress = await (0, keys_1.readActiveAddress)();
30
+ }
31
+ else {
32
+ // eslint-disable-next-line no-console
33
+ console.error(ui_1.colors.red(`Unsupported chain "${activeChain}".`));
34
+ process.exitCode = 1;
35
+ return;
36
+ }
37
+ const repoCfg = buildRepoConfig(repoName, globalCfg, activeAddress, activeChain);
22
38
  const wantsPrivate = options?.private || Boolean(options?.sealPolicy || options?.sealSecret);
23
39
  if (wantsPrivate) {
24
- // We mark it as pending. The actual policy ID will be generated on-chain during 'wit push'.
25
- repoCfg.seal_policy_id = 'pending';
26
- // eslint-disable-next-line no-console
27
- console.log('Initialized as PRIVATE repository. Encryption will be enabled on first push.');
40
+ if (activeChain === 'sui') {
41
+ // We mark it as pending. The actual policy ID will be generated on-chain during 'wit push'.
42
+ const chainCfg = repoCfg.chains?.[activeChain];
43
+ if (chainCfg) {
44
+ chainCfg.seal_policy_id = 'pending';
45
+ }
46
+ // eslint-disable-next-line no-console
47
+ console.log('Initialized as PRIVATE repository. Encryption will be enabled on first push.');
48
+ }
49
+ else if (activeChain === 'mantle') {
50
+ repoCfg.isPrivate = true;
51
+ // eslint-disable-next-line no-console
52
+ console.log('Initialized as PRIVATE repository (Lit Protocol). Encryption will be enabled on first push.');
53
+ }
54
+ else {
55
+ // eslint-disable-next-line no-console
56
+ console.log('Warning: --private is only supported on Sui and Mantle for now; no seal policy was set.');
57
+ }
28
58
  }
29
59
  await writeConfigIfMissing(path_1.default.join(witDir, 'config.json'), repoCfg);
30
60
  await ensureFile(path_1.default.join(witDir, 'HEAD'), 'refs/heads/main\n');
@@ -65,18 +95,50 @@ async function readGlobalConfig() {
65
95
  return {};
66
96
  }
67
97
  }
68
- function buildRepoConfig(repoName, globalCfg, activeAddress) {
98
+ function buildRepoConfig(repoName, globalCfg, activeAddress, activeChain) {
99
+ const network = resolveNetwork(activeChain, globalCfg);
100
+ const relays = globalCfg.relays?.length ? globalCfg.relays : DEFAULT_RELAYS;
101
+ const chainConfig = buildChainConfig(activeChain, globalCfg, activeAddress, network, relays);
69
102
  return {
70
103
  repo_name: repoName,
71
104
  repo_id: null,
72
- network: globalCfg.network || DEFAULT_NETWORK,
73
- relays: globalCfg.relays?.length ? globalCfg.relays : DEFAULT_RELAYS,
74
- author: globalCfg.author || 'unknown',
75
- key_alias: globalCfg.key_alias || 'default',
76
- seal_policy_id: null,
105
+ chain: activeChain,
106
+ chains: { [activeChain]: chainConfig },
107
+ network,
77
108
  created_at: new Date().toISOString(),
78
109
  };
79
110
  }
111
+ function buildChainConfig(activeChain, globalCfg, activeAddress, network, relays) {
112
+ if (activeChain === 'sui') {
113
+ return {
114
+ author: activeAddress || globalCfg.author || 'unknown',
115
+ key_alias: globalCfg.key_alias || 'default',
116
+ relays,
117
+ seal_policy_id: null,
118
+ storage_backend: 'walrus',
119
+ };
120
+ }
121
+ else if (activeChain === 'mantle') {
122
+ return {
123
+ author: activeAddress || 'unknown',
124
+ storage_backend: 'ipfs',
125
+ };
126
+ }
127
+ // eslint-disable-next-line no-console
128
+ console.error(ui_1.colors.red(`Unsupported chain "${activeChain}".`));
129
+ process.exitCode = 1;
130
+ return {
131
+ author: activeAddress || 'unknown',
132
+ storage_backend: 'ipfs',
133
+ };
134
+ }
135
+ function resolveNetwork(activeChain, globalCfg) {
136
+ if (globalCfg.network)
137
+ return globalCfg.network;
138
+ if (activeChain === 'mantle')
139
+ return 'mainnet';
140
+ return DEFAULT_NETWORK; // Defaults to 'testnet' for Sui et al.
141
+ }
80
142
  async function writeConfigIfMissing(file, cfg) {
81
143
  try {
82
144
  await promises_1.default.access(file);