molly-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,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerAddress(program: Command): void;
@@ -0,0 +1,24 @@
1
+ import { createAccount } from "genlayer-js";
2
+ import { resolveConfig } from "../lib/config-manager.js";
3
+ import { output, outputError } from "../lib/output.js";
4
+ import { EXIT_AUTH_ERROR } from "../lib/errors.js";
5
+ export function registerAddress(program) {
6
+ program
7
+ .command("address")
8
+ .description("Print wallet address")
9
+ .action((_opts, cmd) => {
10
+ const globals = cmd.optsWithGlobals();
11
+ const pretty = globals.pretty;
12
+ try {
13
+ const config = resolveConfig({ privateKey: globals.privateKey });
14
+ if (!config.privateKey) {
15
+ outputError("No wallet configured. Run 'molly init' first or set PRIVATE_KEY env var.", EXIT_AUTH_ERROR, pretty);
16
+ }
17
+ const account = createAccount(config.privateKey);
18
+ output({ address: account.address }, pretty);
19
+ }
20
+ catch (err) {
21
+ outputError(err, EXIT_AUTH_ERROR, pretty);
22
+ }
23
+ });
24
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerCampaign(program: Command): void;
@@ -0,0 +1,253 @@
1
+ import { resolveConfig } from "../lib/config-manager.js";
2
+ import { buildClient, buildReadClient } from "../lib/client.js";
3
+ import { writeAndWait, readContract } from "../lib/tx.js";
4
+ import { output, outputError } from "../lib/output.js";
5
+ import { EXIT_TX_ERROR, EXIT_NETWORK_ERROR, EXIT_INPUT_ERROR } from "../lib/errors.js";
6
+ function getAddress(cmd) {
7
+ const globals = cmd.optsWithGlobals();
8
+ const address = globals.address;
9
+ if (!address) {
10
+ outputError("Campaign address is required. Use: molly campaign --address <addr> <command>", EXIT_INPUT_ERROR, globals.pretty);
11
+ }
12
+ return address;
13
+ }
14
+ export function registerCampaign(program) {
15
+ const campaign = program
16
+ .command("campaign")
17
+ .description("Interact with a deployed campaign")
18
+ .requiredOption("--address <addr>", "Campaign contract address");
19
+ // ── Read commands ──────────────────────────────────────────────────
20
+ campaign
21
+ .command("metadata")
22
+ .description("Get campaign metadata")
23
+ .action(async (_opts, cmd) => {
24
+ const globals = cmd.optsWithGlobals();
25
+ const pretty = globals.pretty;
26
+ const address = getAddress(cmd);
27
+ try {
28
+ const config = resolveConfig({ network: globals.network });
29
+ const { client } = buildReadClient(config);
30
+ const data = await readContract(client, address, "get_campaign_metadata", []);
31
+ output({ ok: true, data }, pretty);
32
+ }
33
+ catch (err) {
34
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
35
+ }
36
+ });
37
+ campaign
38
+ .command("info")
39
+ .description("Get full campaign info including submissions")
40
+ .action(async (_opts, cmd) => {
41
+ const globals = cmd.optsWithGlobals();
42
+ const pretty = globals.pretty;
43
+ const address = getAddress(cmd);
44
+ try {
45
+ const config = resolveConfig({ network: globals.network });
46
+ const { client } = buildReadClient(config);
47
+ const data = await readContract(client, address, "get_campaign_info", []);
48
+ output({ ok: true, data }, pretty);
49
+ }
50
+ catch (err) {
51
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
52
+ }
53
+ });
54
+ campaign
55
+ .command("scoreboard")
56
+ .description("Get scores per period")
57
+ .action(async (_opts, cmd) => {
58
+ const globals = cmd.optsWithGlobals();
59
+ const pretty = globals.pretty;
60
+ const address = getAddress(cmd);
61
+ try {
62
+ const config = resolveConfig({ network: globals.network });
63
+ const { client } = buildReadClient(config);
64
+ const data = await readContract(client, address, "get_scoreboard", []);
65
+ output({ ok: true, data }, pretty);
66
+ }
67
+ catch (err) {
68
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
69
+ }
70
+ });
71
+ campaign
72
+ .command("submissions <mission-id>")
73
+ .description("List submissions for a mission")
74
+ .action(async (missionId, _opts, cmd) => {
75
+ const globals = cmd.optsWithGlobals();
76
+ const pretty = globals.pretty;
77
+ const address = getAddress(cmd);
78
+ try {
79
+ const config = resolveConfig({ network: globals.network });
80
+ const { client } = buildReadClient(config);
81
+ const data = await readContract(client, address, "get_submissions", [missionId]);
82
+ output({ ok: true, data }, pretty);
83
+ }
84
+ catch (err) {
85
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
86
+ }
87
+ });
88
+ campaign
89
+ .command("distribution <period>")
90
+ .description("Get period distribution")
91
+ .action(async (period, _opts, cmd) => {
92
+ const globals = cmd.optsWithGlobals();
93
+ const pretty = globals.pretty;
94
+ const address = getAddress(cmd);
95
+ try {
96
+ const config = resolveConfig({ network: globals.network });
97
+ const { client } = buildReadClient(config);
98
+ const data = await readContract(client, address, "get_period_distribution", [parseInt(period, 10)]);
99
+ output({ ok: true, data }, pretty);
100
+ }
101
+ catch (err) {
102
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
103
+ }
104
+ });
105
+ // ── Write commands ─────────────────────────────────────────────────
106
+ campaign
107
+ .command("submit <mission-id> <post-url>")
108
+ .description("Submit a post to a campaign mission")
109
+ .option("--referrer <code>", "Referrer code", "")
110
+ .action(async (missionId, postUrl, opts, cmd) => {
111
+ const globals = cmd.optsWithGlobals();
112
+ const pretty = globals.pretty;
113
+ const address = getAddress(cmd);
114
+ try {
115
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
116
+ const { client } = buildClient(config);
117
+ const { hash } = await writeAndWait(client, address, "submit_post", [missionId, postUrl, opts.referrer || ""], { timeout: globals.timeout });
118
+ output({ ok: true, hash, missionId, postUrl }, pretty);
119
+ }
120
+ catch (err) {
121
+ outputError(err, EXIT_TX_ERROR, pretty);
122
+ }
123
+ });
124
+ campaign
125
+ .command("resubmit <mission-id> <post-url>")
126
+ .description("Resubmit a post for re-evaluation")
127
+ .action(async (missionId, postUrl, _opts, cmd) => {
128
+ const globals = cmd.optsWithGlobals();
129
+ const pretty = globals.pretty;
130
+ const address = getAddress(cmd);
131
+ try {
132
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
133
+ const { client } = buildClient(config);
134
+ const { hash } = await writeAndWait(client, address, "resubmit_post", [missionId, postUrl], { timeout: globals.timeout });
135
+ output({ ok: true, hash, missionId, postUrl }, pretty);
136
+ }
137
+ catch (err) {
138
+ outputError(err, EXIT_TX_ERROR, pretty);
139
+ }
140
+ });
141
+ campaign
142
+ .command("retry <mission-id> <post-url>")
143
+ .description("Retry a failed submission")
144
+ .action(async (missionId, postUrl, _opts, cmd) => {
145
+ const globals = cmd.optsWithGlobals();
146
+ const pretty = globals.pretty;
147
+ const address = getAddress(cmd);
148
+ try {
149
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
150
+ const { client } = buildClient(config);
151
+ const { hash } = await writeAndWait(client, address, "retry_submission", [missionId, postUrl], { timeout: globals.timeout });
152
+ output({ ok: true, hash, missionId, postUrl }, pretty);
153
+ }
154
+ catch (err) {
155
+ outputError(err, EXIT_TX_ERROR, pretty);
156
+ }
157
+ });
158
+ campaign
159
+ .command("bridge-distribution <period>")
160
+ .description("Bridge a period distribution to EVM")
161
+ .action(async (period, _opts, cmd) => {
162
+ const globals = cmd.optsWithGlobals();
163
+ const pretty = globals.pretty;
164
+ const address = getAddress(cmd);
165
+ try {
166
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
167
+ const { client } = buildClient(config);
168
+ const { hash } = await writeAndWait(client, address, "bridge_period_distribution", [parseInt(period, 10)], { timeout: globals.timeout });
169
+ output({ ok: true, hash, period: parseInt(period, 10) }, pretty);
170
+ }
171
+ catch (err) {
172
+ outputError(err, EXIT_TX_ERROR, pretty);
173
+ }
174
+ });
175
+ campaign
176
+ .command("challenge <post-id>")
177
+ .description("Challenge a deleted post")
178
+ .action(async (postId, _opts, cmd) => {
179
+ const globals = cmd.optsWithGlobals();
180
+ const pretty = globals.pretty;
181
+ const address = getAddress(cmd);
182
+ try {
183
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
184
+ const { client } = buildClient(config);
185
+ const { hash } = await writeAndWait(client, address, "challenge_deleted_post", [postId], { timeout: globals.timeout });
186
+ output({ ok: true, hash, postId }, pretty);
187
+ }
188
+ catch (err) {
189
+ outputError(err, EXIT_TX_ERROR, pretty);
190
+ }
191
+ });
192
+ campaign
193
+ .command("disqualify <post-id>")
194
+ .description("Disqualify a submission (creator only)")
195
+ .action(async (postId, _opts, cmd) => {
196
+ const globals = cmd.optsWithGlobals();
197
+ const pretty = globals.pretty;
198
+ const address = getAddress(cmd);
199
+ try {
200
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
201
+ const { client } = buildClient(config);
202
+ const { hash } = await writeAndWait(client, address, "disqualify_submission", [postId], { timeout: globals.timeout });
203
+ output({ ok: true, hash, postId }, pretty);
204
+ }
205
+ catch (err) {
206
+ outputError(err, EXIT_TX_ERROR, pretty);
207
+ }
208
+ });
209
+ campaign
210
+ .command("update-targeting")
211
+ .description("Update campaign targeting (creator only)")
212
+ .option("--whitelisted <addrs...>", "Whitelisted submitter addresses")
213
+ .option("--verified-only <bool>", "Require verified users")
214
+ .option("--min-followers <n>", "Minimum followers", parseInt)
215
+ .option("--max-followers <n>", "Maximum followers", parseInt)
216
+ .action(async (opts, cmd) => {
217
+ const globals = cmd.optsWithGlobals();
218
+ const pretty = globals.pretty;
219
+ const address = getAddress(cmd);
220
+ try {
221
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
222
+ const { client } = buildClient(config);
223
+ const args = [
224
+ opts.whitelisted || null,
225
+ opts.verifiedOnly !== undefined ? opts.verifiedOnly === "true" : null,
226
+ opts.minFollowers ?? null,
227
+ opts.maxFollowers ?? null,
228
+ ];
229
+ const { hash } = await writeAndWait(client, address, "update_campaign_targeting", args, { timeout: globals.timeout });
230
+ output({ ok: true, hash }, pretty);
231
+ }
232
+ catch (err) {
233
+ outputError(err, EXIT_TX_ERROR, pretty);
234
+ }
235
+ });
236
+ campaign
237
+ .command("update-duration <periods>")
238
+ .description("Update campaign duration in periods (creator only)")
239
+ .action(async (periods, _opts, cmd) => {
240
+ const globals = cmd.optsWithGlobals();
241
+ const pretty = globals.pretty;
242
+ const address = getAddress(cmd);
243
+ try {
244
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
245
+ const { client } = buildClient(config);
246
+ const { hash } = await writeAndWait(client, address, "update_campaign_duration_periods", [parseInt(periods, 10)], { timeout: globals.timeout });
247
+ output({ ok: true, hash, periods: parseInt(periods, 10) }, pretty);
248
+ }
249
+ catch (err) {
250
+ outputError(err, EXIT_TX_ERROR, pretty);
251
+ }
252
+ });
253
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerConfig(program: Command): void;
@@ -0,0 +1,36 @@
1
+ import { updateConfig, getConfigPath, resolveConfig } from "../lib/config-manager.js";
2
+ import { output, outputError } from "../lib/output.js";
3
+ import { EXIT_INPUT_ERROR } from "../lib/errors.js";
4
+ export function registerConfig(program) {
5
+ const configCmd = program
6
+ .command("config")
7
+ .description("Show or update configuration");
8
+ configCmd
9
+ .command("show", { isDefault: true })
10
+ .description("Show current resolved configuration")
11
+ .action((_opts, cmd) => {
12
+ const pretty = cmd.optsWithGlobals().pretty;
13
+ const resolved = resolveConfig({});
14
+ // Mask private key for display
15
+ const display = {
16
+ ...resolved,
17
+ privateKey: resolved.privateKey
18
+ ? resolved.privateKey.slice(0, 6) + "..." + resolved.privateKey.slice(-4)
19
+ : "(not set)",
20
+ configPath: getConfigPath(),
21
+ };
22
+ output(display, pretty);
23
+ });
24
+ configCmd
25
+ .command("set <key> <value>")
26
+ .description("Update a config value (network, factoryAddress, identityAddress, privateKey)")
27
+ .action((key, value, _opts, cmd) => {
28
+ const pretty = cmd.optsWithGlobals().pretty;
29
+ const validKeys = ["network", "factoryAddress", "identityAddress", "privateKey"];
30
+ if (!validKeys.includes(key)) {
31
+ outputError(`Invalid config key: ${key}. Valid keys: ${validKeys.join(", ")}`, EXIT_INPUT_ERROR, pretty);
32
+ }
33
+ updateConfig(key, value);
34
+ output({ ok: true, key, value: key === "privateKey" ? "***" : value }, pretty);
35
+ });
36
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerFactory(program: Command): void;
@@ -0,0 +1,273 @@
1
+ import { resolveConfig } from "../lib/config-manager.js";
2
+ import { buildClient, buildReadClient } from "../lib/client.js";
3
+ import { writeAndWait, readContract } from "../lib/tx.js";
4
+ import { output, outputError } from "../lib/output.js";
5
+ import { EXIT_TX_ERROR, EXIT_NETWORK_ERROR, EXIT_INPUT_ERROR } from "../lib/errors.js";
6
+ // Default campaign parameter values (from scripts/create-campaign.ts:50-76)
7
+ const CAMPAIGN_DEFAULTS = {
8
+ knowledge_base: "",
9
+ rules: "1. Posts must be original content\n2. No spam or duplicate submissions\n3. Content must be relevant to the campaign goal",
10
+ style: "",
11
+ campaign_duration_periods: 4,
12
+ period_length_days: 7,
13
+ missions: {
14
+ main: {
15
+ title: "Main Mission",
16
+ description: "Create engaging social media content that promotes the campaign goal",
17
+ rules: "Posts must be original and relevant to the campaign theme",
18
+ active: true,
19
+ },
20
+ },
21
+ distribution_contract_chain_id: 0,
22
+ distribution_contract_address: "0x0000000000000000000000000000000000000000",
23
+ only_verified_users: false,
24
+ minimum_followers: 0,
25
+ maximum_followers: 0,
26
+ whitelisted_submitters: [],
27
+ alpha: 50,
28
+ beta: 50,
29
+ gate_weights: [1, 1, 1],
30
+ metric_weights: [1, 1, 1, 1],
31
+ allow_old_posts: true,
32
+ max_submissions_per_participant: 0,
33
+ target_chain_rpc_url: "",
34
+ disqualification_enabled: false,
35
+ num_shards: 3,
36
+ };
37
+ function generateId(title) {
38
+ const slug = title
39
+ .toLowerCase()
40
+ .replace(/[^a-z0-9]+/g, "-")
41
+ .replace(/(^-|-$)/g, "");
42
+ const timestamp = Date.now().toString(36);
43
+ return `${slug}-${timestamp}`;
44
+ }
45
+ function getStartTime() {
46
+ const now = new Date();
47
+ now.setHours(now.getHours() + 1);
48
+ now.setMinutes(0, 0, 0);
49
+ return now.toISOString();
50
+ }
51
+ export function registerFactory(program) {
52
+ const factory = program
53
+ .command("factory")
54
+ .description("Manage campaigns via CampaignFactory");
55
+ factory
56
+ .command("create")
57
+ .description("Create a new campaign")
58
+ .option("--params-json <json>", "Full campaign params as JSON")
59
+ .option("--id <id>", "Campaign ID")
60
+ .option("--title <title>", "Campaign title")
61
+ .option("--goal <goal>", "Campaign goal")
62
+ .option("--knowledge-base <text>", "Knowledge base")
63
+ .option("--rules <text>", "Campaign rules")
64
+ .option("--style <text>", "Campaign style")
65
+ .option("--start <iso>", "Start datetime ISO")
66
+ .option("--duration-periods <n>", "Number of periods", parseInt)
67
+ .option("--period-days <n>", "Days per period", parseInt)
68
+ .option("--missions-json <json>", "Missions as JSON object")
69
+ .option("--dist-chain-id <n>", "Distribution contract chain ID", parseInt)
70
+ .option("--dist-address <addr>", "Distribution contract address")
71
+ .option("--verified-only", "Require verified users")
72
+ .option("--min-followers <n>", "Minimum followers", parseInt)
73
+ .option("--max-followers <n>", "Maximum followers", parseInt)
74
+ .option("--whitelisted <addrs...>", "Whitelisted submitter addresses")
75
+ .option("--alpha <n>", "Alpha parameter", parseInt)
76
+ .option("--beta <n>", "Beta parameter", parseInt)
77
+ .option("--gate-weights <weights...>", "Gate weights (space-separated)")
78
+ .option("--metric-weights <weights...>", "Metric weights (space-separated)")
79
+ .option("--no-old-posts", "Disallow old posts")
80
+ .option("--max-submissions <n>", "Max submissions per participant", parseInt)
81
+ .option("--target-rpc <url>", "Target chain RPC URL")
82
+ .option("--enable-disqualification", "Enable disqualification")
83
+ .option("--num-shards <n>", "Number of shards", parseInt)
84
+ .action(async (opts, cmd) => {
85
+ const globals = cmd.optsWithGlobals();
86
+ const pretty = globals.pretty;
87
+ try {
88
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
89
+ let params;
90
+ if (opts.paramsJson) {
91
+ try {
92
+ params = JSON.parse(opts.paramsJson);
93
+ }
94
+ catch {
95
+ outputError("Invalid --params-json", EXIT_INPUT_ERROR, pretty);
96
+ return; // unreachable but helps TS
97
+ }
98
+ }
99
+ else {
100
+ if (!opts.title || !opts.goal) {
101
+ outputError("--title and --goal are required (or use --params-json)", EXIT_INPUT_ERROR, pretty);
102
+ }
103
+ params = {};
104
+ if (opts.title)
105
+ params.title = opts.title;
106
+ if (opts.goal)
107
+ params.goal = opts.goal;
108
+ if (opts.id)
109
+ params.id = opts.id;
110
+ if (opts.knowledgeBase)
111
+ params.knowledge_base = opts.knowledgeBase;
112
+ if (opts.rules)
113
+ params.rules = opts.rules;
114
+ if (opts.style)
115
+ params.style = opts.style;
116
+ if (opts.start)
117
+ params.start_datetime_iso = opts.start;
118
+ if (opts.durationPeriods)
119
+ params.campaign_duration_periods = opts.durationPeriods;
120
+ if (opts.periodDays)
121
+ params.period_length_days = opts.periodDays;
122
+ if (opts.missionsJson) {
123
+ try {
124
+ params.missions = JSON.parse(opts.missionsJson);
125
+ }
126
+ catch {
127
+ outputError("Invalid --missions-json", EXIT_INPUT_ERROR, pretty);
128
+ }
129
+ }
130
+ if (opts.distChainId)
131
+ params.distribution_contract_chain_id = opts.distChainId;
132
+ if (opts.distAddress)
133
+ params.distribution_contract_address = opts.distAddress;
134
+ if (opts.verifiedOnly)
135
+ params.only_verified_users = true;
136
+ if (opts.minFollowers)
137
+ params.minimum_followers = opts.minFollowers;
138
+ if (opts.maxFollowers)
139
+ params.maximum_followers = opts.maxFollowers;
140
+ if (opts.whitelisted)
141
+ params.whitelisted_submitters = opts.whitelisted;
142
+ if (opts.alpha)
143
+ params.alpha = opts.alpha;
144
+ if (opts.beta)
145
+ params.beta = opts.beta;
146
+ if (opts.gateWeights)
147
+ params.gate_weights = opts.gateWeights.map(Number);
148
+ if (opts.metricWeights)
149
+ params.metric_weights = opts.metricWeights.map(Number);
150
+ if (opts.oldPosts === false)
151
+ params.allow_old_posts = false;
152
+ if (opts.maxSubmissions)
153
+ params.max_submissions_per_participant = opts.maxSubmissions;
154
+ if (opts.targetRpc)
155
+ params.target_chain_rpc_url = opts.targetRpc;
156
+ if (opts.enableDisqualification)
157
+ params.disqualification_enabled = true;
158
+ if (opts.numShards)
159
+ params.num_shards = opts.numShards;
160
+ }
161
+ // Merge with defaults
162
+ const p = params;
163
+ const campaign = {
164
+ id: p.id || generateId(p.title),
165
+ title: p.title,
166
+ goal: p.goal,
167
+ knowledge_base: p.knowledge_base ?? CAMPAIGN_DEFAULTS.knowledge_base,
168
+ rules: p.rules ?? CAMPAIGN_DEFAULTS.rules,
169
+ style: p.style ?? CAMPAIGN_DEFAULTS.style,
170
+ start_datetime_iso: p.start_datetime_iso ?? getStartTime(),
171
+ campaign_duration_periods: p.campaign_duration_periods ?? CAMPAIGN_DEFAULTS.campaign_duration_periods,
172
+ period_length_days: p.period_length_days ?? CAMPAIGN_DEFAULTS.period_length_days,
173
+ missions: p.missions ?? CAMPAIGN_DEFAULTS.missions,
174
+ distribution_contract_chain_id: p.distribution_contract_chain_id ?? CAMPAIGN_DEFAULTS.distribution_contract_chain_id,
175
+ distribution_contract_address: p.distribution_contract_address ?? CAMPAIGN_DEFAULTS.distribution_contract_address,
176
+ only_verified_users: p.only_verified_users ?? CAMPAIGN_DEFAULTS.only_verified_users,
177
+ minimum_followers: p.minimum_followers ?? CAMPAIGN_DEFAULTS.minimum_followers,
178
+ maximum_followers: p.maximum_followers ?? CAMPAIGN_DEFAULTS.maximum_followers,
179
+ whitelisted_submitters: p.whitelisted_submitters ?? CAMPAIGN_DEFAULTS.whitelisted_submitters,
180
+ alpha: p.alpha ?? CAMPAIGN_DEFAULTS.alpha,
181
+ beta: p.beta ?? CAMPAIGN_DEFAULTS.beta,
182
+ gate_weights: p.gate_weights ?? CAMPAIGN_DEFAULTS.gate_weights,
183
+ metric_weights: p.metric_weights ?? CAMPAIGN_DEFAULTS.metric_weights,
184
+ allow_old_posts: p.allow_old_posts ?? CAMPAIGN_DEFAULTS.allow_old_posts,
185
+ max_submissions_per_participant: p.max_submissions_per_participant ?? CAMPAIGN_DEFAULTS.max_submissions_per_participant,
186
+ target_chain_rpc_url: p.target_chain_rpc_url ?? CAMPAIGN_DEFAULTS.target_chain_rpc_url,
187
+ disqualification_enabled: p.disqualification_enabled ?? CAMPAIGN_DEFAULTS.disqualification_enabled,
188
+ num_shards: p.num_shards ?? CAMPAIGN_DEFAULTS.num_shards,
189
+ };
190
+ const { client } = buildClient(config);
191
+ // create_campaign args match CampaignFactory.__init__.py:73-99
192
+ const { hash } = await writeAndWait(client, config.factoryAddress, "create_campaign", [
193
+ campaign.id,
194
+ campaign.title,
195
+ campaign.goal,
196
+ campaign.knowledge_base,
197
+ campaign.rules,
198
+ campaign.style,
199
+ campaign.start_datetime_iso,
200
+ campaign.campaign_duration_periods,
201
+ campaign.period_length_days,
202
+ campaign.missions,
203
+ campaign.distribution_contract_chain_id,
204
+ campaign.distribution_contract_address,
205
+ campaign.only_verified_users,
206
+ campaign.minimum_followers,
207
+ campaign.maximum_followers,
208
+ campaign.whitelisted_submitters,
209
+ campaign.alpha,
210
+ campaign.beta,
211
+ campaign.gate_weights,
212
+ campaign.metric_weights,
213
+ campaign.allow_old_posts,
214
+ campaign.max_submissions_per_participant,
215
+ campaign.target_chain_rpc_url,
216
+ campaign.disqualification_enabled,
217
+ campaign.num_shards,
218
+ ], { timeout: globals.timeout });
219
+ output({ ok: true, hash, campaignId: campaign.id }, pretty);
220
+ }
221
+ catch (err) {
222
+ outputError(err, EXIT_TX_ERROR, pretty);
223
+ }
224
+ });
225
+ factory
226
+ .command("get <campaign-id>")
227
+ .description("Get campaign address by ID")
228
+ .action(async (campaignId, _opts, cmd) => {
229
+ const globals = cmd.optsWithGlobals();
230
+ const pretty = globals.pretty;
231
+ try {
232
+ const config = resolveConfig({ network: globals.network });
233
+ const { client } = buildReadClient(config);
234
+ const address = await readContract(client, config.factoryAddress, "get_contract_by_id", [campaignId]);
235
+ output({ ok: true, campaignId, address }, pretty);
236
+ }
237
+ catch (err) {
238
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
239
+ }
240
+ });
241
+ factory
242
+ .command("list-all")
243
+ .description("List all campaigns (IDs and addresses)")
244
+ .action(async (_opts, cmd) => {
245
+ const globals = cmd.optsWithGlobals();
246
+ const pretty = globals.pretty;
247
+ try {
248
+ const config = resolveConfig({ network: globals.network });
249
+ const { client } = buildReadClient(config);
250
+ const campaigns = await readContract(client, config.factoryAddress, "get_all_campaigns", []);
251
+ output({ ok: true, campaigns }, pretty);
252
+ }
253
+ catch (err) {
254
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
255
+ }
256
+ });
257
+ factory
258
+ .command("list <ids...>")
259
+ .description("Get multiple campaign addresses by IDs")
260
+ .action(async (ids, _opts, cmd) => {
261
+ const globals = cmd.optsWithGlobals();
262
+ const pretty = globals.pretty;
263
+ try {
264
+ const config = resolveConfig({ network: globals.network });
265
+ const { client } = buildReadClient(config);
266
+ const addresses = await readContract(client, config.factoryAddress, "get_contracts_by_ids", [ids]);
267
+ output({ ok: true, ids, addresses }, pretty);
268
+ }
269
+ catch (err) {
270
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
271
+ }
272
+ });
273
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerIdentity(program: Command): void;
@@ -0,0 +1,114 @@
1
+ import { resolveConfig } from "../lib/config-manager.js";
2
+ import { buildClient, buildReadClient } from "../lib/client.js";
3
+ import { writeAndWait, readContract } from "../lib/tx.js";
4
+ import { output, outputError } from "../lib/output.js";
5
+ import { EXIT_TX_ERROR, EXIT_NETWORK_ERROR } from "../lib/errors.js";
6
+ export function registerIdentity(program) {
7
+ const identity = program
8
+ .command("identity")
9
+ .description("Manage MoltBook identity");
10
+ identity
11
+ .command("link-start <username>")
12
+ .description("Initiate address link — returns a verification token")
13
+ .action(async (username, _opts, cmd) => {
14
+ const globals = cmd.optsWithGlobals();
15
+ const pretty = globals.pretty;
16
+ try {
17
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
18
+ const { client, account } = buildClient(config);
19
+ const { hash } = await writeAndWait(client, config.identityAddress, "initiate_address_link", [username], { timeout: globals.timeout });
20
+ // Read the token back
21
+ const tokenInfo = await readContract(client, config.identityAddress, "get_token", [account.address]);
22
+ output({ ok: true, hash, address: account.address, token: tokenInfo }, pretty);
23
+ }
24
+ catch (err) {
25
+ outputError(err, EXIT_TX_ERROR, pretty);
26
+ }
27
+ });
28
+ identity
29
+ .command("link-complete <username>")
30
+ .description("Complete address link (token must be in your MoltBook profile)")
31
+ .action(async (username, _opts, cmd) => {
32
+ const globals = cmd.optsWithGlobals();
33
+ const pretty = globals.pretty;
34
+ try {
35
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
36
+ const { client } = buildClient(config);
37
+ const { hash } = await writeAndWait(client, config.identityAddress, "complete_address_link", [username], { timeout: globals.timeout });
38
+ output({ ok: true, hash }, pretty);
39
+ }
40
+ catch (err) {
41
+ outputError(err, EXIT_TX_ERROR, pretty);
42
+ }
43
+ });
44
+ identity
45
+ .command("get-wallet <username>")
46
+ .description("Look up wallet address by MoltBook username")
47
+ .action(async (username, _opts, cmd) => {
48
+ const globals = cmd.optsWithGlobals();
49
+ const pretty = globals.pretty;
50
+ try {
51
+ const config = resolveConfig({ network: globals.network });
52
+ const { client } = buildReadClient(config);
53
+ const wallet = await readContract(client, config.identityAddress, "get_wallet", [username]);
54
+ output({ ok: true, username, wallet }, pretty);
55
+ }
56
+ catch (err) {
57
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
58
+ }
59
+ });
60
+ identity
61
+ .command("get-username <address>")
62
+ .description("Look up MoltBook username by wallet address")
63
+ .action(async (address, _opts, cmd) => {
64
+ const globals = cmd.optsWithGlobals();
65
+ const pretty = globals.pretty;
66
+ try {
67
+ const config = resolveConfig({ network: globals.network });
68
+ const { client } = buildReadClient(config);
69
+ const username = await readContract(client, config.identityAddress, "get_username", [address]);
70
+ output({ ok: true, address, username }, pretty);
71
+ }
72
+ catch (err) {
73
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
74
+ }
75
+ });
76
+ identity
77
+ .command("get-token [address]")
78
+ .description("Check pending verification token for an address")
79
+ .action(async (address, _opts, cmd) => {
80
+ const globals = cmd.optsWithGlobals();
81
+ const pretty = globals.pretty;
82
+ try {
83
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
84
+ let targetAddress = address;
85
+ if (!targetAddress) {
86
+ // Need a wallet to derive own address
87
+ const { account } = buildClient(config);
88
+ targetAddress = account.address;
89
+ }
90
+ const { client } = buildReadClient(config);
91
+ const token = await readContract(client, config.identityAddress, "get_token", [targetAddress]);
92
+ output({ ok: true, address: targetAddress, token }, pretty);
93
+ }
94
+ catch (err) {
95
+ outputError(err, EXIT_NETWORK_ERROR, pretty);
96
+ }
97
+ });
98
+ identity
99
+ .command("bridge <username> <chain-id>")
100
+ .description("Bridge identity to an EVM chain")
101
+ .action(async (username, chainId, _opts, cmd) => {
102
+ const globals = cmd.optsWithGlobals();
103
+ const pretty = globals.pretty;
104
+ try {
105
+ const config = resolveConfig({ privateKey: globals.privateKey, network: globals.network });
106
+ const { client } = buildClient(config);
107
+ const { hash } = await writeAndWait(client, config.identityAddress, "bridge_user_to_chain", [username, parseInt(chainId, 10)], { timeout: globals.timeout });
108
+ output({ ok: true, hash, username, chainId: parseInt(chainId, 10) }, pretty);
109
+ }
110
+ catch (err) {
111
+ outputError(err, EXIT_TX_ERROR, pretty);
112
+ }
113
+ });
114
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerInit(program: Command): void;
@@ -0,0 +1,37 @@
1
+ import { createAccount } from "genlayer-js";
2
+ import { randomBytes } from "crypto";
3
+ import { readConfig, writeConfig, getConfigPath } from "../lib/config-manager.js";
4
+ import { output, outputError } from "../lib/output.js";
5
+ import { EXIT_AUTH_ERROR } from "../lib/errors.js";
6
+ export function registerInit(program) {
7
+ program
8
+ .command("init")
9
+ .description("Generate a new wallet and save config")
10
+ .action(async (_opts, cmd) => {
11
+ const pretty = cmd.optsWithGlobals().pretty;
12
+ try {
13
+ const existing = readConfig();
14
+ if (existing.privateKey) {
15
+ const account = createAccount(existing.privateKey);
16
+ output({
17
+ ok: true,
18
+ message: "Wallet already exists",
19
+ address: account.address,
20
+ configPath: getConfigPath(),
21
+ }, pretty);
22
+ return;
23
+ }
24
+ const privateKey = `0x${randomBytes(32).toString("hex")}`;
25
+ const account = createAccount(privateKey);
26
+ writeConfig({ ...existing, privateKey });
27
+ output({
28
+ ok: true,
29
+ address: account.address,
30
+ configPath: getConfigPath(),
31
+ }, pretty);
32
+ }
33
+ catch (err) {
34
+ outputError(err, EXIT_AUTH_ERROR, pretty);
35
+ }
36
+ });
37
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { registerInit } from "./commands/init.js";
4
+ import { registerConfig } from "./commands/config.js";
5
+ import { registerAddress } from "./commands/address.js";
6
+ import { registerIdentity } from "./commands/identity.js";
7
+ import { registerFactory } from "./commands/factory.js";
8
+ import { registerCampaign } from "./commands/campaign.js";
9
+ const program = new Command();
10
+ program
11
+ .name("molly")
12
+ .description("CLI for AI bots to interact with GenLayer via Moltrally")
13
+ .version("0.1.0")
14
+ .option("--network <url>", "GenLayer RPC URL")
15
+ .option("--private-key <key>", "Private key (overrides config)")
16
+ .option("--pretty", "Pretty-print JSON output")
17
+ .option("--timeout <ms>", "Transaction timeout in milliseconds", parseInt);
18
+ registerInit(program);
19
+ registerConfig(program);
20
+ registerAddress(program);
21
+ registerIdentity(program);
22
+ registerFactory(program);
23
+ registerCampaign(program);
24
+ program.parseAsync(process.argv);
@@ -0,0 +1,18 @@
1
+ import { createAccount } from "genlayer-js";
2
+ import type { GenLayerClient, GenLayerChain } from "genlayer-js/types";
3
+ import type { ResolvedConfig } from "./types.js";
4
+ /**
5
+ * Build a read-only client (no account needed).
6
+ * Use this for readContract calls that don't require a wallet.
7
+ */
8
+ export declare function buildReadClient(config: ResolvedConfig): {
9
+ client: GenLayerClient<GenLayerChain>;
10
+ };
11
+ /**
12
+ * Build a full client with account for write operations.
13
+ * Throws a clear error if no private key is configured.
14
+ */
15
+ export declare function buildClient(config: ResolvedConfig): {
16
+ client: GenLayerClient<GenLayerChain>;
17
+ account: ReturnType<typeof createAccount>;
18
+ };
@@ -0,0 +1,60 @@
1
+ import { createAccount, createClient } from "genlayer-js";
2
+ import { localnet, studionet } from "genlayer-js/chains";
3
+ /**
4
+ * Select the base chain config based on the network URL.
5
+ * localnet (127.0.0.1 / localhost) uses chain ID 61127.
6
+ * studionet (studio.genlayer.com) uses chain ID 61999.
7
+ */
8
+ function resolveBaseChain(networkUrl) {
9
+ try {
10
+ const url = new URL(networkUrl);
11
+ if (url.hostname === "127.0.0.1" || url.hostname === "localhost") {
12
+ return localnet;
13
+ }
14
+ }
15
+ catch {
16
+ // fall through to default
17
+ }
18
+ return studionet;
19
+ }
20
+ /**
21
+ * Build a read-only client (no account needed).
22
+ * Use this for readContract calls that don't require a wallet.
23
+ */
24
+ export function buildReadClient(config) {
25
+ const baseChain = resolveBaseChain(config.network);
26
+ const chain = {
27
+ ...baseChain,
28
+ rpcUrls: {
29
+ default: { http: [config.network] },
30
+ },
31
+ };
32
+ const client = createClient({
33
+ chain,
34
+ endpoint: config.network,
35
+ });
36
+ return { client };
37
+ }
38
+ /**
39
+ * Build a full client with account for write operations.
40
+ * Throws a clear error if no private key is configured.
41
+ */
42
+ export function buildClient(config) {
43
+ if (!config.privateKey) {
44
+ throw new Error("No wallet configured. Run 'molly init' or set PRIVATE_KEY env var.");
45
+ }
46
+ const account = createAccount(config.privateKey);
47
+ const baseChain = resolveBaseChain(config.network);
48
+ const chain = {
49
+ ...baseChain,
50
+ rpcUrls: {
51
+ default: { http: [config.network] },
52
+ },
53
+ };
54
+ const client = createClient({
55
+ chain,
56
+ account,
57
+ endpoint: config.network,
58
+ });
59
+ return { client, account };
60
+ }
@@ -0,0 +1,12 @@
1
+ import type { MollyConfig, ResolvedConfig } from "./types.js";
2
+ export declare function getConfigPath(): string;
3
+ export declare function readConfig(): MollyConfig;
4
+ export declare function writeConfig(config: MollyConfig): void;
5
+ export declare function updateConfig(key: string, value: string): void;
6
+ /**
7
+ * Resolution order: CLI flag > env var > config file > default
8
+ */
9
+ export declare function resolveConfig(overrides: {
10
+ privateKey?: string;
11
+ network?: string;
12
+ }): ResolvedConfig;
@@ -0,0 +1,55 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
2
+ import path from "path";
3
+ import os from "os";
4
+ import { studionet } from "genlayer-js/chains";
5
+ import { DEFAULT_FACTORY_ADDRESS, DEFAULT_IDENTITY_ADDRESS, } from "./contracts.js";
6
+ const CONFIG_DIR = path.join(os.homedir(), ".molly");
7
+ const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
8
+ /** Default network URL from the studionet chain config */
9
+ const DEFAULT_NETWORK = studionet.rpcUrls.default.http[0];
10
+ export function getConfigPath() {
11
+ return CONFIG_PATH;
12
+ }
13
+ export function readConfig() {
14
+ if (!existsSync(CONFIG_PATH)) {
15
+ return {};
16
+ }
17
+ try {
18
+ return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
19
+ }
20
+ catch {
21
+ return {};
22
+ }
23
+ }
24
+ export function writeConfig(config) {
25
+ if (!existsSync(CONFIG_DIR)) {
26
+ mkdirSync(CONFIG_DIR, { recursive: true });
27
+ }
28
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
29
+ }
30
+ export function updateConfig(key, value) {
31
+ const config = readConfig();
32
+ config[key] = value;
33
+ writeConfig(config);
34
+ }
35
+ /**
36
+ * Resolution order: CLI flag > env var > config file > default
37
+ */
38
+ export function resolveConfig(overrides) {
39
+ const file = readConfig();
40
+ const privateKey = overrides.privateKey ||
41
+ process.env.PRIVATE_KEY ||
42
+ file.privateKey ||
43
+ "";
44
+ const network = overrides.network ||
45
+ process.env.MOLLY_NETWORK ||
46
+ file.network ||
47
+ DEFAULT_NETWORK;
48
+ const factoryAddress = process.env.MOLLY_FACTORY_ADDRESS ||
49
+ file.factoryAddress ||
50
+ DEFAULT_FACTORY_ADDRESS;
51
+ const identityAddress = process.env.MOLLY_IDENTITY_ADDRESS ||
52
+ file.identityAddress ||
53
+ DEFAULT_IDENTITY_ADDRESS;
54
+ return { privateKey, network, factoryAddress, identityAddress };
55
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default contract addresses for studionet (studio.genlayer.com).
3
+ * Override via ~/.molly/config.json or CLI flags.
4
+ */
5
+ export declare const DEFAULT_FACTORY_ADDRESS = "0x0000000000000000000000000000000000000000";
6
+ export declare const DEFAULT_IDENTITY_ADDRESS = "0x0000000000000000000000000000000000000000";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default contract addresses for studionet (studio.genlayer.com).
3
+ * Override via ~/.molly/config.json or CLI flags.
4
+ */
5
+ export const DEFAULT_FACTORY_ADDRESS = "0x0000000000000000000000000000000000000000";
6
+ export const DEFAULT_IDENTITY_ADDRESS = "0x0000000000000000000000000000000000000000";
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Exit codes:
3
+ * 0 = success
4
+ * 1 = transaction / contract error
5
+ * 2 = auth / wallet error
6
+ * 3 = network error
7
+ * 4 = invalid input
8
+ */
9
+ export declare const EXIT_SUCCESS = 0;
10
+ export declare const EXIT_TX_ERROR = 1;
11
+ export declare const EXIT_AUTH_ERROR = 2;
12
+ export declare const EXIT_NETWORK_ERROR = 3;
13
+ export declare const EXIT_INPUT_ERROR = 4;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Exit codes:
3
+ * 0 = success
4
+ * 1 = transaction / contract error
5
+ * 2 = auth / wallet error
6
+ * 3 = network error
7
+ * 4 = invalid input
8
+ */
9
+ export const EXIT_SUCCESS = 0;
10
+ export const EXIT_TX_ERROR = 1;
11
+ export const EXIT_AUTH_ERROR = 2;
12
+ export const EXIT_NETWORK_ERROR = 3;
13
+ export const EXIT_INPUT_ERROR = 4;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Structured output for bot consumption.
3
+ * All data goes to stdout as JSON. Errors go to stderr as JSON.
4
+ */
5
+ export declare function output(data: unknown, pretty?: boolean): void;
6
+ export declare function outputError(error: unknown, exitCode: number, pretty?: boolean): never;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Structured output for bot consumption.
3
+ * All data goes to stdout as JSON. Errors go to stderr as JSON.
4
+ */
5
+ export function output(data, pretty) {
6
+ if (pretty) {
7
+ console.log(JSON.stringify(data, replacer, 2));
8
+ }
9
+ else {
10
+ console.log(JSON.stringify(data, replacer));
11
+ }
12
+ }
13
+ export function outputError(error, exitCode, pretty) {
14
+ const payload = { ok: false };
15
+ if (error instanceof Error) {
16
+ payload.error = error.message;
17
+ }
18
+ else if (typeof error === "string") {
19
+ payload.error = error;
20
+ }
21
+ else {
22
+ payload.error = String(error);
23
+ }
24
+ if (pretty) {
25
+ process.stderr.write(JSON.stringify(payload, null, 2) + "\n");
26
+ }
27
+ else {
28
+ process.stderr.write(JSON.stringify(payload) + "\n");
29
+ }
30
+ process.exit(exitCode);
31
+ }
32
+ /**
33
+ * Detect CalldataAddress byte objects: { bytes: { 0: n, 1: n, ... } }
34
+ * and convert to hex string "0x..."
35
+ */
36
+ function isCalldataAddress(value) {
37
+ if (typeof value !== "object" || value === null)
38
+ return false;
39
+ const obj = value;
40
+ if (!("bytes" in obj) || typeof obj.bytes !== "object" || obj.bytes === null)
41
+ return false;
42
+ const bytes = obj.bytes;
43
+ return "0" in bytes && typeof bytes["0"] === "number";
44
+ }
45
+ function bytesObjectToHex(bytes) {
46
+ const keys = Object.keys(bytes).map(Number).sort((a, b) => a - b);
47
+ const hex = keys.map((k) => bytes[String(k)].toString(16).padStart(2, "0")).join("");
48
+ return "0x" + hex;
49
+ }
50
+ /**
51
+ * JSON replacer that converts Map instances to plain objects,
52
+ * BigInt to string, and CalldataAddress byte objects to hex strings.
53
+ */
54
+ function replacer(_key, value) {
55
+ if (value instanceof Map) {
56
+ const obj = {};
57
+ for (const [k, v] of value) {
58
+ obj[String(k)] = v;
59
+ }
60
+ return obj;
61
+ }
62
+ if (typeof value === "bigint") {
63
+ return value.toString();
64
+ }
65
+ if (isCalldataAddress(value)) {
66
+ return bytesObjectToHex(value.bytes);
67
+ }
68
+ return value;
69
+ }
@@ -0,0 +1,16 @@
1
+ import type { GenLayerClient, GenLayerChain, GenLayerTransaction, CalldataEncodable } from "genlayer-js/types";
2
+ /**
3
+ * Write to a contract and wait for the tx to be accepted.
4
+ * Pattern from scripts/create-campaign.ts:175-215
5
+ */
6
+ export declare function writeAndWait(client: GenLayerClient<GenLayerChain>, address: string, functionName: string, args: CalldataEncodable[], opts?: {
7
+ leaderOnly?: boolean;
8
+ timeout?: number;
9
+ }): Promise<{
10
+ hash: string;
11
+ receipt: GenLayerTransaction;
12
+ }>;
13
+ /**
14
+ * Read from a contract (view call).
15
+ */
16
+ export declare function readContract(client: GenLayerClient<GenLayerChain>, address: string, functionName: string, args: CalldataEncodable[]): Promise<CalldataEncodable>;
package/dist/lib/tx.js ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Write to a contract and wait for the tx to be accepted.
3
+ * Pattern from scripts/create-campaign.ts:175-215
4
+ */
5
+ export async function writeAndWait(client, address, functionName, args, opts) {
6
+ const hash = await client.writeContract({
7
+ address: address,
8
+ functionName,
9
+ args,
10
+ value: 0n,
11
+ leaderOnly: opts?.leaderOnly ?? true,
12
+ });
13
+ const receipt = await client.waitForTransactionReceipt({
14
+ hash,
15
+ status: "ACCEPTED",
16
+ retries: opts?.timeout ? Math.ceil(opts.timeout / 2000) : 60,
17
+ });
18
+ return { hash, receipt };
19
+ }
20
+ /**
21
+ * Read from a contract (view call).
22
+ */
23
+ export async function readContract(client, address, functionName, args) {
24
+ return client.readContract({
25
+ address: address,
26
+ functionName,
27
+ args,
28
+ });
29
+ }
@@ -0,0 +1,15 @@
1
+ export interface MollyConfig {
2
+ privateKey?: string;
3
+ network?: string;
4
+ factoryAddress?: string;
5
+ identityAddress?: string;
6
+ }
7
+ export interface ResolvedConfig {
8
+ privateKey: string;
9
+ network: string;
10
+ factoryAddress: string;
11
+ identityAddress: string;
12
+ }
13
+ export interface OutputOptions {
14
+ pretty?: boolean;
15
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "molly-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for AI bots to interact with the GenLayer blockchain via Moltrally",
5
+ "type": "module",
6
+ "bin": {
7
+ "molly": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsx src/index.ts",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/genlayerlabs/moltrally.git",
17
+ "directory": "cli"
18
+ },
19
+ "homepage": "https://github.com/genlayerlabs/moltrally/tree/main/cli",
20
+ "bugs": {
21
+ "url": "https://github.com/genlayerlabs/moltrally/issues"
22
+ },
23
+ "author": "GenLayer Labs",
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "dependencies": {
28
+ "commander": "^13.1.0",
29
+ "genlayer-js": "^0.18.9"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.10.2",
33
+ "tsx": "^4.19.2",
34
+ "typescript": "^5.7.2"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "keywords": [
40
+ "genlayer",
41
+ "moltrally",
42
+ "moltbook",
43
+ "cli",
44
+ "ai",
45
+ "bot"
46
+ ],
47
+ "license": "MIT"
48
+ }