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.
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LitService = exports.LIT_ACTION_CID = exports.LIT_ACTION_CODE = exports.LIT_NETWORK = void 0;
4
+ const lit_node_client_1 = require("@lit-protocol/lit-node-client");
5
+ // Use 'manhattan' as a default network for now, or 'datil-dev' / 'datil-test'
6
+ // For this MVP we will target 'datil-test'
7
+ exports.LIT_NETWORK = 'datil-test';
8
+ exports.LIT_ACTION_CODE = `
9
+ (async () => {
10
+ // --- Configuration ---
11
+ const RPC_URL = "https://rpc.mantle.xyz";
12
+
13
+ // 1. Get params from accessControlConditions
14
+ const _cond = accessControlConditions.find(c => c.standardContractType === "LitAction");
15
+ const params = _cond.parameters;
16
+ const repoId = params[0];
17
+ const contractAddress = params[1];
18
+ const userAddress = params[2];
19
+
20
+ if (!repoId || !contractAddress || !userAddress) {
21
+ console.log("Missing required params");
22
+ LitActions.setResponse({ response: "false" });
23
+ return;
24
+ }
25
+
26
+ const abi = [
27
+ "function hasAccess(uint256 repoId, address user) view returns (bool)"
28
+ ];
29
+
30
+ const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
31
+ const contract = new ethers.Contract(contractAddress, abi, provider);
32
+
33
+ try {
34
+ console.log("Checking access for Repo " + repoId + " User " + userAddress);
35
+ const hasAccess = await contract.hasAccess(repoId, userAddress);
36
+ console.log("Result: " + hasAccess);
37
+ LitActions.setResponse({ response: hasAccess.toString() });
38
+ } catch (error) {
39
+ console.error("Error checking access:", error);
40
+ LitActions.setResponse({ response: "false" });
41
+ }
42
+ })();
43
+ `;
44
+ exports.LIT_ACTION_CID = 'bafkreibbq3fltufjarcmk45426mhhssky5izkjuxio7im26q6lxlf2qbda';
45
+ class LitService {
46
+ constructor(config = {}) {
47
+ this.connected = false;
48
+ this.litNodeClient = new lit_node_client_1.LitNodeClient({
49
+ litNetwork: exports.LIT_NETWORK,
50
+ debug: config.debug ?? false,
51
+ });
52
+ }
53
+ /**
54
+ * Generates the Access Control Conditions (ACC) for a specific repo.
55
+ * Use unifiedAccessControlConditions for evmContract type support.
56
+ *
57
+ * @param repoId The repository ID.
58
+ * @param contractAddress The Wit Repo Contract address.
59
+ * @returns The Unified ACC array compatible with Lit SDK.
60
+ */
61
+ getAccessControlConditions(repoId, contractAddress) {
62
+ return [
63
+ {
64
+ conditionType: 'evmContract',
65
+ contractAddress: contractAddress,
66
+ functionName: 'hasAccess',
67
+ functionParams: [repoId, ':userAddress'],
68
+ functionAbi: {
69
+ name: "hasAccess",
70
+ type: "function",
71
+ stateMutability: "view",
72
+ inputs: [
73
+ { name: "repoId", type: "uint256" },
74
+ { name: "user", type: "address" }
75
+ ],
76
+ outputs: [
77
+ { name: "", type: "bool" }
78
+ ]
79
+ },
80
+ chain: 'mantle',
81
+ returnValueTest: {
82
+ key: '',
83
+ comparator: '=',
84
+ value: 'true',
85
+ },
86
+ },
87
+ ];
88
+ }
89
+ async connect() {
90
+ if (this.connected)
91
+ return;
92
+ await this.litNodeClient.connect();
93
+ this.connected = true;
94
+ }
95
+ async disconnect() {
96
+ if (!this.connected)
97
+ return;
98
+ if (typeof this.litNodeClient.disconnect === 'function') {
99
+ await this.litNodeClient.disconnect();
100
+ }
101
+ this.connected = false;
102
+ }
103
+ /**
104
+ * Encrypts a session key using Lit Protocol.
105
+ *
106
+ * @param sessionKey The 32-byte session key to encrypt.
107
+ * @param unifiedAccessControlConditions The Lit Unified Access Control Conditions.
108
+ * @returns The { ciphertext, dataToEncryptHash } from Lit encryption.
109
+ */
110
+ async encryptSessionKey(sessionKey, unifiedAccessControlConditions) {
111
+ await this.connect();
112
+ const { ciphertext, dataToEncryptHash } = await this.litNodeClient.encrypt({
113
+ dataToEncrypt: sessionKey,
114
+ unifiedAccessControlConditions,
115
+ });
116
+ return { ciphertext, dataToEncryptHash };
117
+ }
118
+ /**
119
+ * Decrypts the session key using Lit Protocol.
120
+ *
121
+ * @param ciphertext The encrypted session key from Lit.
122
+ * @param dataToEncryptHash The hash of the session key.
123
+ * @param unifiedAccessControlConditions The conditions used to encrypt.
124
+ * @param authSig The user's auth signature (e.g. from wallet).
125
+ * @returns The raw session key (Buffer).
126
+ */
127
+ async decryptSessionKey(ciphertext, dataToEncryptHash, unifiedAccessControlConditions, authSig) {
128
+ await this.connect();
129
+ const decryptedString = await this.litNodeClient.decrypt({
130
+ ciphertext,
131
+ dataToEncryptHash,
132
+ unifiedAccessControlConditions,
133
+ authSig,
134
+ chain: 'mantle',
135
+ });
136
+ return Buffer.from(decryptedString.decryptedData);
137
+ }
138
+ /**
139
+ * Generates a SIWE AuthSig using a local ethers Signer.
140
+ * This is required for Lit Protocol to authenticate the user and check access.
141
+ */
142
+ async getAuthSig(signer) {
143
+ const address = await signer.getAddress();
144
+ const domain = 'localhost';
145
+ const origin = 'https://localhost/login';
146
+ const statement = 'Sign in to Wit CLI to decrypt repository data.';
147
+ const now = new Date().toISOString();
148
+ const expiration = new Date(Date.now() + 1000 * 60 * 60 * 24).toISOString(); // 24 hours
149
+ const chainId = 5000; // Mantle Mainnet
150
+ // Construct SIWE Message (EIP-4361)
151
+ // Note: Newlines (\n) are critical.
152
+ const message = `${domain} wants you to sign in with your Ethereum account:\n${address}\n\n${statement}\n\nURI: ${origin}\nVersion: 1\nChain ID: ${chainId}\nNonce: ${this.randomNonce()}\nIssued At: ${now}\nExpiration Time: ${expiration}`;
153
+ const signature = await signer.signMessage(message);
154
+ return {
155
+ sig: signature,
156
+ derivedVia: 'web3.eth.personal.sign',
157
+ signedMessage: message,
158
+ address: address,
159
+ };
160
+ }
161
+ randomNonce() {
162
+ return Math.random().toString(36).substring(2, 12);
163
+ }
164
+ }
165
+ exports.LitService = LitService;
@@ -1,12 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildRootEntries = buildRootEntries;
3
4
  exports.computeRootHash = computeRootHash;
4
5
  exports.buildManifest = buildManifest;
6
+ exports.buildSnapshotManifest = buildSnapshotManifest;
5
7
  const fs_1 = require("./fs");
6
8
  const serialize_1 = require("./serialize");
7
9
  const schema_1 = require("./schema");
8
- function computeRootHash(index) {
9
- const entries = Object.keys(index)
10
+ function buildRootEntries(index) {
11
+ return Object.keys(index)
10
12
  .sort()
11
13
  .map((rel) => {
12
14
  const meta = index[rel];
@@ -18,8 +20,10 @@ function computeRootHash(index) {
18
20
  mtime: meta.mtime,
19
21
  };
20
22
  });
21
- const serialized = (0, serialize_1.canonicalStringify)(entries);
22
- return (0, serialize_1.sha256Base64)(serialized);
23
+ }
24
+ function computeRootHash(index) {
25
+ const entries = buildRootEntries(index);
26
+ return (0, serialize_1.sha256Base64)((0, serialize_1.canonicalStringify)(entries));
23
27
  }
24
28
  function buildManifest(index, quiltId) {
25
29
  const files = {};
@@ -35,3 +39,17 @@ function buildManifest(index, quiltId) {
35
39
  };
36
40
  return schema_1.ManifestSchema.parse(manifest);
37
41
  }
42
+ function buildSnapshotManifest(index, snapshotCid) {
43
+ const files = {};
44
+ for (const rel of Object.keys(index).sort()) {
45
+ const posix = (0, fs_1.pathToPosix)(rel);
46
+ files[posix] = index[rel];
47
+ }
48
+ const manifest = {
49
+ version: 1,
50
+ snapshot_cid: snapshotCid,
51
+ root_hash: computeRootHash(index),
52
+ files,
53
+ };
54
+ return schema_1.ManifestSchema.parse(manifest);
55
+ }
package/dist/lib/repo.js CHANGED
@@ -6,12 +6,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.requireWitDir = requireWitDir;
7
7
  exports.readRepoConfig = readRepoConfig;
8
8
  exports.writeRepoConfig = writeRepoConfig;
9
+ exports.getRepoChainMismatch = getRepoChainMismatch;
10
+ exports.resolveChainConfig = resolveChainConfig;
11
+ exports.resolveChainAuthor = resolveChainAuthor;
12
+ exports.resolveSuiConfig = resolveSuiConfig;
13
+ exports.resolveSuiSealPolicyId = resolveSuiSealPolicyId;
14
+ exports.setSuiSealPolicyId = setSuiSealPolicyId;
15
+ exports.setChainAuthor = setChainAuthor;
9
16
  exports.readRemoteState = readRemoteState;
10
17
  exports.writeRemoteState = writeRemoteState;
11
18
  exports.readRemoteRef = readRemoteRef;
12
19
  exports.writeRemoteRef = writeRemoteRef;
13
20
  const promises_1 = __importDefault(require("fs/promises"));
14
21
  const path_1 = __importDefault(require("path"));
22
+ const chain_1 = require("./chain");
15
23
  const DEFAULT_REMOTE_REF = path_1.default.join('refs', 'remotes', 'main');
16
24
  async function requireWitDir(cwd = process.cwd()) {
17
25
  const dir = path_1.default.join(cwd, '.wit');
@@ -32,6 +40,92 @@ async function writeRepoConfig(witPath, cfg) {
32
40
  const file = path_1.default.join(witPath, 'config.json');
33
41
  await promises_1.default.writeFile(file, JSON.stringify(cfg, null, 2) + '\n', 'utf8');
34
42
  }
43
+ async function getRepoChainMismatch(cfg) {
44
+ let repoChain;
45
+ try {
46
+ repoChain = (0, chain_1.normalizeChain)(cfg.chain ?? 'sui');
47
+ }
48
+ catch (err) {
49
+ return { error: err?.message || 'Unknown chain in repository config.' };
50
+ }
51
+ const activeChain = await (0, chain_1.readActiveChain)();
52
+ if (repoChain !== activeChain) {
53
+ return { repoChain, activeChain };
54
+ }
55
+ return null;
56
+ }
57
+ function resolveChainConfig(cfg, chain) {
58
+ if (!chain)
59
+ return null;
60
+ const entry = cfg.chains?.[chain];
61
+ if (!entry || typeof entry !== 'object')
62
+ return null;
63
+ return entry;
64
+ }
65
+ function resolveChainAuthor(cfg) {
66
+ const chainAuthor = resolveChainConfig(cfg, cfg.chain)?.author;
67
+ return chainAuthor || cfg.author || 'unknown';
68
+ }
69
+ function resolveSuiConfig(cfg) {
70
+ const chainCfg = resolveChainConfig(cfg, 'sui');
71
+ if (chainCfg)
72
+ return chainCfg;
73
+ const hasLegacy = cfg.network !== undefined ||
74
+ (Array.isArray(cfg.relays) && cfg.relays.length > 0) ||
75
+ Object.prototype.hasOwnProperty.call(cfg, 'seal_policy_id');
76
+ if (!hasLegacy)
77
+ return null;
78
+ return {
79
+ author: cfg.author || 'unknown',
80
+ key_alias: cfg.key_alias,
81
+ network: cfg.network,
82
+ relays: cfg.relays,
83
+ seal_policy_id: cfg.seal_policy_id ?? null,
84
+ storage_backend: 'walrus',
85
+ };
86
+ }
87
+ function resolveSuiSealPolicyId(cfg) {
88
+ const chainCfg = resolveSuiConfig(cfg);
89
+ if (chainCfg?.seal_policy_id !== undefined) {
90
+ return chainCfg.seal_policy_id ?? null;
91
+ }
92
+ if (Object.prototype.hasOwnProperty.call(cfg, 'seal_policy_id')) {
93
+ return cfg.seal_policy_id ?? null;
94
+ }
95
+ return null;
96
+ }
97
+ function setSuiSealPolicyId(cfg, value) {
98
+ if (cfg.chain && cfg.chain !== 'sui')
99
+ return;
100
+ if (!cfg.chains)
101
+ cfg.chains = {};
102
+ if (!cfg.chains.sui) {
103
+ cfg.chains.sui = {
104
+ author: cfg.author || 'unknown',
105
+ key_alias: cfg.key_alias,
106
+ storage_backend: 'walrus',
107
+ };
108
+ }
109
+ cfg.chains.sui.seal_policy_id = value;
110
+ if (Object.prototype.hasOwnProperty.call(cfg, 'seal_policy_id')) {
111
+ cfg.seal_policy_id = value ?? null;
112
+ }
113
+ }
114
+ function setChainAuthor(cfg, chain, author) {
115
+ if (chain) {
116
+ if (!cfg.chains)
117
+ cfg.chains = {};
118
+ if (!cfg.chains[chain]) {
119
+ cfg.chains[chain] = { author };
120
+ }
121
+ else {
122
+ cfg.chains[chain].author = author;
123
+ }
124
+ }
125
+ if (Object.prototype.hasOwnProperty.call(cfg, 'author')) {
126
+ cfg.author = author;
127
+ }
128
+ }
35
129
  async function readRemoteState(witPath) {
36
130
  const file = path_1.default.join(witPath, 'state', 'remote.json');
37
131
  try {
@@ -8,9 +8,14 @@ const fileMeta = zod_1.z.object({
8
8
  mode: zod_1.z.string().regex(/^\d{6}$/),
9
9
  mtime: zod_1.z.number().int().nonnegative(),
10
10
  id: zod_1.z.string().min(1).optional(),
11
+ cid: zod_1.z.string().min(1).optional(),
11
12
  enc: zod_1.z
12
13
  .object({
13
- alg: zod_1.z.union([zod_1.z.literal('aes-256-gcm'), zod_1.z.literal('seal-aes-256-gcm')]),
14
+ alg: zod_1.z.union([
15
+ zod_1.z.literal('aes-256-gcm'),
16
+ zod_1.z.literal('seal-aes-256-gcm'),
17
+ zod_1.z.literal('lit-aes-256-gcm'),
18
+ ]),
14
19
  iv: zod_1.z.string().min(1),
15
20
  tag: zod_1.z.string().min(1),
16
21
  policy: zod_1.z.string().min(1).optional(),
@@ -18,21 +23,36 @@ const fileMeta = zod_1.z.object({
18
23
  package_id: zod_1.z.string().min(1).optional(),
19
24
  sealed_session_key: zod_1.z.string().min(1).optional(),
20
25
  cipher_size: zod_1.z.number().int().nonnegative().optional(),
26
+ // Lit Protocol specific fields
27
+ lit_encrypted_key: zod_1.z.string().min(1).optional(),
28
+ lit_hash: zod_1.z.string().min(1).optional(),
29
+ access_control_conditions: zod_1.z.array(zod_1.z.any()).optional(),
30
+ unified_access_control_conditions: zod_1.z.array(zod_1.z.any()).optional(),
31
+ lit_chain: zod_1.z.string().min(1).optional(),
32
+ lit_network: zod_1.z.string().min(1).optional(),
21
33
  })
22
34
  .optional(),
23
35
  });
24
- exports.ManifestSchema = zod_1.z.object({
36
+ exports.ManifestSchema = zod_1.z
37
+ .object({
25
38
  version: zod_1.z.literal(1),
26
- quilt_id: zod_1.z.string().min(1),
39
+ quilt_id: zod_1.z.string().min(1).optional(),
40
+ snapshot_cid: zod_1.z.string().min(1).optional(),
27
41
  root_hash: zod_1.z.string().min(1),
28
42
  files: zod_1.z.record(fileMeta),
29
43
  });
30
44
  exports.CommitSchema = zod_1.z.object({
31
- tree: zod_1.z.object({
32
- quilt_id: zod_1.z.string().min(1).nullable(),
33
- manifest_id: zod_1.z.string().min(1).nullable(),
45
+ tree: zod_1.z
46
+ .object({
47
+ quilt_id: zod_1.z.string().min(1).nullable().optional(),
48
+ manifest_id: zod_1.z.string().min(1).nullable().optional(),
49
+ manifest_cid: zod_1.z.string().min(1).nullable().optional(),
50
+ snapshot_cid: zod_1.z.string().min(1).nullable().optional(),
34
51
  root_hash: zod_1.z.string().min(1),
35
52
  files: zod_1.z.record(fileMeta).optional(),
53
+ })
54
+ .refine((tree) => Boolean(tree.files || tree.manifest_id || tree.manifest_cid), {
55
+ message: 'Commit tree must include files or manifest id',
36
56
  }),
37
57
  parent: zod_1.z.string().min(1).nullable(),
38
58
  author: zod_1.z.string().min(1),
@@ -38,6 +38,15 @@ async function readJsonIfExists(file) {
38
38
  throw err;
39
39
  }
40
40
  }
41
+ function resolveRepoWalrusConfig(repoCfg) {
42
+ if (!repoCfg)
43
+ return null;
44
+ const chainCfg = repoCfg.chains?.sui;
45
+ if (chainCfg && typeof chainCfg === 'object') {
46
+ return { ...repoCfg, ...chainCfg };
47
+ }
48
+ return repoCfg;
49
+ }
41
50
  function normalizeNetwork(network) {
42
51
  if (network === 'mainnet')
43
52
  return 'mainnet';
@@ -81,7 +90,8 @@ function pickAggregatorHost(network, repoCfg, globalCfg) {
81
90
  }
82
91
  async function resolveWalrusConfig(cwd = process.cwd()) {
83
92
  const witDir = path_1.default.join(cwd, '.wit');
84
- const repoCfg = await readJsonIfExists(path_1.default.join(witDir, 'config.json'));
93
+ const repoCfgRaw = await readJsonIfExists(path_1.default.join(witDir, 'config.json'));
94
+ const repoCfg = resolveRepoWalrusConfig(repoCfgRaw);
85
95
  if (!repoCfg) {
86
96
  throw new Error('Not a wit repository (missing .wit/config.json). Run `wit init` first.');
87
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "withub-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "wit CLI (Commander + Ink) skeleton",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,14 +22,26 @@
22
22
  "prepublishOnly": "npm run build && npm run test:smoke"
23
23
  },
24
24
  "dependencies": {
25
+ "@lighthouse-web3/sdk": "^0.4.3",
26
+ "@lit-protocol/auth-helpers": "^8.2.0",
27
+ "@lit-protocol/constants": "^9.0.0",
28
+ "@lit-protocol/contracts-sdk": "^7.4.0",
29
+ "@lit-protocol/encryption": "^7.4.0",
30
+ "@lit-protocol/lit-node-client": "^7.4.0",
31
+ "@lit-protocol/types": "^8.0.2",
25
32
  "@mysten/seal": "^0.9.4",
26
33
  "@mysten/sui": "^1.45.0",
27
34
  "@mysten/walrus": "^0.8.4",
35
+ "@web3-storage/car-block-validator": "^1.2.2",
28
36
  "chalk": "^4.1.2",
29
37
  "commander": "^12.1.0",
30
38
  "diff": "^5.2.0",
39
+ "dotenv": "^17.2.3",
40
+ "ethers": "^6.16.0",
31
41
  "ignore": "^5.3.1",
32
42
  "ink": "^4.4.1",
43
+ "ipfs-car": "3.1.0",
44
+ "ipfs-only-hash": "^4.0.0",
33
45
  "isbinaryfile": "^5.0.2",
34
46
  "react": "^18.2.0",
35
47
  "zod": "^3.23.8"
@@ -44,6 +56,10 @@
44
56
  "engines": {
45
57
  "node": ">=18"
46
58
  },
59
+ "overrides": {
60
+ "@lit-protocol/constants": "^9.0.0",
61
+ "@lit-protocol/contracts": "^0.9.0"
62
+ },
47
63
  "license": "Apache-2.0",
48
64
  "files": [
49
65
  "dist/**",
@@ -54,4 +70,4 @@
54
70
  "publishConfig": {
55
71
  "access": "public"
56
72
  }
57
- }
73
+ }