nebulon-escrow-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.
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "nebulon-escrow-cli",
3
+ "version": "0.1.0",
4
+ "description": "Nebulon CLI",
5
+ "author": "northbyt3 <northbyt3@gmail.com>",
6
+ "license": "UNLICENSED",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/northbyt3/nebulon-public.git"
10
+ },
11
+ "private": false,
12
+ "bin": {
13
+ "nebulon": "bin/nebulon.js"
14
+ },
15
+ "scripts": {
16
+ "start": "node bin/nebulon.js"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "dependencies": {
22
+ "@solana/spl-token": "^0.4.14",
23
+ "@solana/web3.js": "^1.95.5",
24
+ "@coral-xyz/anchor": "^0.30.1",
25
+ "@magicblock-labs/gum-sdk": "^3.0.10",
26
+ "@magicblock-labs/ephemeral-rollups-sdk": "^0.8.0",
27
+ "bs58": "^6.0.0",
28
+ "chalk": "^4.1.2",
29
+ "commander": "^12.1.0",
30
+ "enquirer": "^2.4.1",
31
+ "tweetnacl": "^1.0.3"
32
+ }
33
+ }
@@ -0,0 +1,154 @@
1
+ const fs = require("fs");
2
+ const {
3
+ nebulonHome,
4
+ legacyConfigPath,
5
+ legacyWalletsDir,
6
+ capsulesDir,
7
+ activeCapsulePath,
8
+ capsuleRoot,
9
+ capsuleConfigPath,
10
+ capsuleWalletsDir,
11
+ } = require("./paths");
12
+
13
+ const CAPSULE_NAME_RE = /^[a-zA-Z0-9_-]+$/;
14
+
15
+ const ensureBaseDirs = () => {
16
+ const home = nebulonHome();
17
+ if (!fs.existsSync(home)) {
18
+ fs.mkdirSync(home, { recursive: true });
19
+ }
20
+ const capsuleRootDir = capsulesDir();
21
+ if (!fs.existsSync(capsuleRootDir)) {
22
+ fs.mkdirSync(capsuleRootDir, { recursive: true });
23
+ }
24
+ };
25
+
26
+ const readActiveState = () => {
27
+ ensureBaseDirs();
28
+ const statePath = activeCapsulePath();
29
+ if (!fs.existsSync(statePath)) {
30
+ return { lastUsed: null, sessions: {} };
31
+ }
32
+ try {
33
+ const parsed = JSON.parse(fs.readFileSync(statePath, "utf8"));
34
+ return {
35
+ lastUsed: parsed.lastUsed || null,
36
+ sessions: parsed.sessions || {},
37
+ };
38
+ } catch (error) {
39
+ return { lastUsed: null, sessions: {} };
40
+ }
41
+ };
42
+
43
+ const writeActiveState = (state) => {
44
+ ensureBaseDirs();
45
+ fs.writeFileSync(activeCapsulePath(), JSON.stringify(state, null, 2));
46
+ };
47
+
48
+ const getSessionId = () => String(process.ppid || process.pid);
49
+
50
+ const isValidCapsuleName = (name) =>
51
+ typeof name === "string" && CAPSULE_NAME_RE.test(name.trim());
52
+
53
+ const ensureCapsuleDirs = (name) => {
54
+ ensureBaseDirs();
55
+ const root = capsuleRoot(name);
56
+ if (!fs.existsSync(root)) {
57
+ fs.mkdirSync(root, { recursive: true });
58
+ }
59
+ const wallets = capsuleWalletsDir(name);
60
+ if (!fs.existsSync(wallets)) {
61
+ fs.mkdirSync(wallets, { recursive: true });
62
+ }
63
+ };
64
+
65
+ const listCapsules = () => {
66
+ ensureBaseDirs();
67
+ const root = capsulesDir();
68
+ if (!fs.existsSync(root)) {
69
+ return [];
70
+ }
71
+ return fs
72
+ .readdirSync(root, { withFileTypes: true })
73
+ .filter((entry) => entry.isDirectory())
74
+ .map((entry) => entry.name)
75
+ .sort((a, b) => a.localeCompare(b));
76
+ };
77
+
78
+ const migrateLegacyIfNeeded = () => {
79
+ if (!fs.existsSync(legacyConfigPath())) {
80
+ return false;
81
+ }
82
+ if (listCapsules().length > 0) {
83
+ return false;
84
+ }
85
+ ensureCapsuleDirs("1");
86
+ try {
87
+ fs.renameSync(legacyConfigPath(), capsuleConfigPath("1"));
88
+ } catch (error) {
89
+ fs.copyFileSync(legacyConfigPath(), capsuleConfigPath("1"));
90
+ fs.rmSync(legacyConfigPath(), { force: true });
91
+ }
92
+ if (fs.existsSync(legacyWalletsDir())) {
93
+ try {
94
+ fs.renameSync(legacyWalletsDir(), capsuleWalletsDir("1"));
95
+ } catch (error) {
96
+ fs.cpSync(legacyWalletsDir(), capsuleWalletsDir("1"), {
97
+ recursive: true,
98
+ });
99
+ fs.rmSync(legacyWalletsDir(), { recursive: true, force: true });
100
+ }
101
+ }
102
+ const state = readActiveState();
103
+ state.lastUsed = "1";
104
+ state.sessions[getSessionId()] = "1";
105
+ writeActiveState(state);
106
+ return true;
107
+ };
108
+
109
+ const getActiveCapsule = (preferred) => {
110
+ migrateLegacyIfNeeded();
111
+ const state = readActiveState();
112
+ const sessionId = getSessionId();
113
+ let capsule =
114
+ (preferred && preferred.trim()) ||
115
+ state.sessions[sessionId] ||
116
+ state.lastUsed ||
117
+ "1";
118
+ if (!isValidCapsuleName(capsule)) {
119
+ capsule = "1";
120
+ }
121
+ ensureCapsuleDirs(capsule);
122
+ if (state.sessions[sessionId] !== capsule || state.lastUsed !== capsule) {
123
+ state.sessions[sessionId] = capsule;
124
+ state.lastUsed = capsule;
125
+ writeActiveState(state);
126
+ }
127
+ return capsule;
128
+ };
129
+
130
+ const setActiveCapsule = (name) => {
131
+ if (!isValidCapsuleName(name)) {
132
+ throw new Error(
133
+ "Invalid capsule name. Use letters, numbers, dashes, or underscores."
134
+ );
135
+ }
136
+ ensureCapsuleDirs(name.trim());
137
+ const state = readActiveState();
138
+ const sessionId = getSessionId();
139
+ state.sessions[sessionId] = name.trim();
140
+ state.lastUsed = name.trim();
141
+ writeActiveState(state);
142
+ return name.trim();
143
+ };
144
+
145
+ module.exports = {
146
+ ensureCapsuleDirs,
147
+ getActiveCapsule,
148
+ isValidCapsuleName,
149
+ listCapsules,
150
+ migrateLegacyIfNeeded,
151
+ readActiveState,
152
+ setActiveCapsule,
153
+ writeActiveState,
154
+ };
package/src/cli.js ADDED
@@ -0,0 +1,292 @@
1
+ const { Command } = require("commander");
2
+ const { runInit } = require("./commands/init");
3
+ const { runConfigGet, runConfigSummary, runConfigSet } = require("./commands/config");
4
+ const { runStatus } = require("./commands/status");
5
+ const { runLogin } = require("./commands/login");
6
+ const { runLogout } = require("./commands/logout");
7
+ const { runReset } = require("./commands/reset");
8
+ const { runCapsuleList, runCapsuleUse } = require("./commands/capsule");
9
+ const { runContractCommand } = require("./commands/contract");
10
+ const { runInviteList, runInviteAction } = require("./commands/invites");
11
+ const { runHostedMe, runHostedFaucet } = require("./commands/hosted");
12
+ const { runWalletShow, runWalletBalance, runWalletExport } = require("./commands/wallet");
13
+ const { runTestTee } = require("./commands/tee");
14
+ const { errorMessage } = require("./ui");
15
+
16
+ const VERSION = "0.1.0";
17
+ const program = new Command();
18
+ let hasFatalError = false;
19
+
20
+ const formatError = (error) => {
21
+ if (!error) {
22
+ return "Action failed.";
23
+ }
24
+ if (typeof error === "string") {
25
+ return error;
26
+ }
27
+ if (error.message) {
28
+ return error.message;
29
+ }
30
+ return "Action failed.";
31
+ };
32
+
33
+ process.on("uncaughtException", (error) => {
34
+ hasFatalError = true;
35
+ errorMessage(formatError(error));
36
+ process.exit(1);
37
+ });
38
+
39
+ process.on("unhandledRejection", (error) => {
40
+ hasFatalError = true;
41
+ errorMessage(formatError(error));
42
+ process.exit(1);
43
+ });
44
+
45
+ process.on("exit", (code) => {
46
+ if (!hasFatalError && code && code !== 0) {
47
+ errorMessage("Action failed.");
48
+ }
49
+ });
50
+
51
+ program
52
+ .name("nebulon")
53
+ .description("Nebulon CLI")
54
+ .version(VERSION)
55
+ .configureHelp({
56
+ formatHelp: () => {
57
+ return [
58
+ "Nebulon CLI - help",
59
+ "",
60
+ "Options:",
61
+ " -v, -V, --version output the version number",
62
+ "",
63
+ "Commands:",
64
+ " nebulon init Initializes the Nebulon setup",
65
+ " nebulon init capsule <name> Initializes a specific capsule",
66
+ " nebulon status Show account summary",
67
+ " nebulon login Login in hosted mode",
68
+ " nebulon logout Logout of hosted mode",
69
+ " nebulon reset Reset the current capsule",
70
+ " nebulon reset capsules Reset all capsules",
71
+ " nebulon capsule list List capsules",
72
+ " nebulon capsule use Switch capsules",
73
+ " nebulon contract Contract commands (create, list, show, disputed, <id> ...)",
74
+ " nebulon contract <id> sync Sync PER status to on-chain flags",
75
+ " nebulon contract <id> milestone list Show milestone status",
76
+ " nebulon contract <id> milestone <n> status Show milestone status",
77
+ " nebulon contract <id> milestone <n> ready Mark milestone as ready",
78
+ " nebulon contract <id> milestone <n> confirm Confirm milestone",
79
+ " nebulon contract <id> term list Show deadline/payment terms",
80
+ " nebulon contract <id> details Show full contract details",
81
+ " nebulon contract <id> status Show contract summary",
82
+ " nebulon contract <id> isFunded Show funding status",
83
+ " nebulon contract <id> whoami Show your role for this contract",
84
+ " nebulon contract <id> nowwhat Show the next expected action",
85
+ " nebulon contract <id> rate <1-5> Rate the other party (completed only)",
86
+ " nebulon invite list Show pending invites",
87
+ " nebulon invite list --show-all Show accepted/canceled history",
88
+ " nebulon invite list sent Show sent invites only",
89
+ " nebulon invite list received Show received invites only",
90
+ " nebulon invite <id|idx> View or respond to an invite",
91
+ " nebulon address Show connected wallet address",
92
+ " nebulon balance Show SOL + USDC balance",
93
+ " nebulon wallet export Export active wallet to C:\\Nebulon\\Wallets",
94
+ " nebulon config Show and edit configuration",
95
+ " nebulon config [key] [value] Change a configuration value directly",
96
+ " nebulon test-tee Validate TEE auth + RPC connectivity",
97
+ " nebulon whoami Show hosted profile",
98
+ " nebulon request-usdc Request test USDC from backend faucet",
99
+ " nebulon help Shows this menu",
100
+ ].join("\n");
101
+ },
102
+ });
103
+
104
+ program.helpCommand("help");
105
+
106
+ program
107
+ .command("init")
108
+ .description("Initialize Nebulon CLI")
109
+ .argument("[arg1]", "capsule or keyword")
110
+ .argument("[arg2]", "capsule name")
111
+ .option("--no-banner", "hide the banner")
112
+ .action(async (arg1, arg2, options) => {
113
+ let capsule = null;
114
+ if (arg1 === "capsule") {
115
+ if (!arg2) {
116
+ console.log("Usage: nebulon init capsule <name>");
117
+ return;
118
+ }
119
+ capsule = arg2;
120
+ } else {
121
+ if (arg2) {
122
+ console.log("Usage: nebulon init [capsule] | nebulon init capsule <name>");
123
+ return;
124
+ }
125
+ capsule = arg1 || null;
126
+ }
127
+ await runInit(options, capsule);
128
+ });
129
+
130
+ program
131
+ .command("status")
132
+ .description("Show account summary")
133
+ .action(async () => {
134
+ await runStatus();
135
+ });
136
+
137
+ program
138
+ .command("login")
139
+ .description("Login in hosted mode")
140
+ .option("--handle <handle>", "request a Nebulon handle on login")
141
+ .option("--debug", "enable login debug logging")
142
+ .action(async (options) => {
143
+ await runLogin(options);
144
+ });
145
+
146
+ program
147
+ .command("logout")
148
+ .description("Logout of hosted mode")
149
+ .action(async () => {
150
+ await runLogout();
151
+ });
152
+
153
+ program
154
+ .command("reset")
155
+ .description("Reset the current capsule")
156
+ .argument("[scope]", "capsules")
157
+ .action(async (scope) => {
158
+ await runReset(scope);
159
+ });
160
+
161
+ const capsule = program
162
+ .command("capsule")
163
+ .description("Capsule commands");
164
+
165
+ capsule
166
+ .command("list")
167
+ .description("List capsules")
168
+ .action(async () => {
169
+ runCapsuleList();
170
+ });
171
+
172
+ capsule
173
+ .command("use")
174
+ .description("Switch capsules")
175
+ .argument("<name>", "capsule name")
176
+ .action(async (name) => {
177
+ runCapsuleUse(name);
178
+ });
179
+
180
+ program
181
+ .command("contract")
182
+ .description("Contract commands")
183
+ .argument("[target]", "create | list | show | <id|index>")
184
+ .argument("[rest...]", "contract sub-commands")
185
+ .option("--confirm", "skip confirmation prompts")
186
+ .option("--full-fields", "show full list fields without truncation")
187
+ .action(async (target, rest, options) => {
188
+ const args = [];
189
+ if (target) {
190
+ args.push(target);
191
+ }
192
+ if (Array.isArray(rest) && rest.length) {
193
+ args.push(...rest);
194
+ }
195
+ await runContractCommand(args, options);
196
+ })
197
+ .alias("contracts");
198
+
199
+ const invite = program
200
+ .command("invite")
201
+ .description("Invite commands")
202
+ .argument("[target]", "list | index | contractId")
203
+ .argument("[action]", "accept | deny")
204
+ .option("--show-all", "show accepted/canceled invites too")
205
+ .action(async (target, action, options) => {
206
+ if (!target || target === "list") {
207
+ await runInviteList(action, options);
208
+ return;
209
+ }
210
+ await runInviteAction(target, action);
211
+ });
212
+
213
+ invite
214
+ .command("list")
215
+ .description("List invites")
216
+ .argument("[filter]", "sent | received")
217
+ .option("--show-all", "show accepted/canceled invites too")
218
+ .action(async (filter, options) => {
219
+ await runInviteList(filter, options);
220
+ });
221
+
222
+ program
223
+ .command("config")
224
+ .description("Manage config")
225
+ .argument("[key]", "config key")
226
+ .argument("[value]", "config value")
227
+ .action(async (key, value) => {
228
+ if (!key) {
229
+ await runConfigSummary();
230
+ return;
231
+ }
232
+ if (typeof value === "undefined") {
233
+ runConfigGet(key);
234
+ return;
235
+ }
236
+ await runConfigSet(key, value);
237
+ });
238
+
239
+ program
240
+ .command("whoami")
241
+ .description("Show hosted profile")
242
+ .action(async () => {
243
+ await runHostedMe();
244
+ });
245
+
246
+ program
247
+ .command("address")
248
+ .description("Show connected wallet address")
249
+ .action(() => {
250
+ runWalletShow();
251
+ });
252
+
253
+ program
254
+ .command("balance")
255
+ .description("Show SOL + USDC balance")
256
+ .action(async () => {
257
+ await runWalletBalance();
258
+ });
259
+
260
+ program
261
+ .command("wallet")
262
+ .description("Wallet commands")
263
+ .argument("[action]", "export")
264
+ .action(async (action) => {
265
+ if (action === "export") {
266
+ await runWalletExport();
267
+ return;
268
+ }
269
+ console.log("Usage: nebulon wallet export");
270
+ });
271
+
272
+ program
273
+ .command("request-usdc")
274
+ .description("Request test USDC from backend faucet")
275
+ .action(async () => {
276
+ await runHostedFaucet();
277
+ });
278
+
279
+ program
280
+ .command("test-tee")
281
+ .description("Validate TEE auth + RPC connectivity")
282
+ .option("--debug", "print extra diagnostic output")
283
+ .action(async (options) => {
284
+ await runTestTee(options);
285
+ });
286
+
287
+ if (process.argv.includes("-v") && !process.argv.includes("-V")) {
288
+ console.log(VERSION);
289
+ process.exit(0);
290
+ }
291
+
292
+ program.parseAsync(process.argv);
@@ -0,0 +1,46 @@
1
+ const {
2
+ listCapsules,
3
+ migrateLegacyIfNeeded,
4
+ readActiveState,
5
+ setActiveCapsule,
6
+ } = require("../capsules");
7
+
8
+ const runCapsuleList = () => {
9
+ migrateLegacyIfNeeded();
10
+ const capsules = listCapsules();
11
+ if (!capsules.length) {
12
+ console.log("No capsules found. Run `nebulon init` to create one.");
13
+ return;
14
+ }
15
+
16
+ const state = readActiveState();
17
+ const sessionId = String(process.ppid || process.pid);
18
+ const active = state.sessions && state.sessions[sessionId]
19
+ ? state.sessions[sessionId]
20
+ : state.lastUsed;
21
+
22
+ console.log("Capsules");
23
+ capsules.forEach((name) => {
24
+ const marker = name === active ? "*" : " ";
25
+ console.log(`${marker} ${name}`);
26
+ });
27
+ };
28
+
29
+ const runCapsuleUse = (name) => {
30
+ if (!name) {
31
+ console.log("Usage: nebulon capsule use <name>");
32
+ return;
33
+ }
34
+ try {
35
+ const capsule = setActiveCapsule(name);
36
+ console.log(`Capsule active: ${capsule}`);
37
+ } catch (error) {
38
+ console.error(error.message);
39
+ process.exit(1);
40
+ }
41
+ };
42
+
43
+ module.exports = {
44
+ runCapsuleList,
45
+ runCapsuleUse,
46
+ };