everything-dev 0.0.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.
- package/package.json +40 -0
- package/src/cli.ts +735 -0
- package/src/components/dev-view.tsx +243 -0
- package/src/components/status-view.tsx +173 -0
- package/src/components/streaming-view.ts +110 -0
- package/src/config.ts +214 -0
- package/src/contract.ts +364 -0
- package/src/index.ts +3 -0
- package/src/lib/env.ts +91 -0
- package/src/lib/near-cli.ts +289 -0
- package/src/lib/nova.ts +254 -0
- package/src/lib/orchestrator.ts +213 -0
- package/src/lib/process.ts +370 -0
- package/src/lib/secrets.ts +28 -0
- package/src/plugin.ts +930 -0
- package/src/utils/banner.ts +19 -0
- package/src/utils/run.ts +21 -0
- package/src/utils/theme.ts +101 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { confirm } from "@inquirer/prompts";
|
|
3
|
+
import { Effect } from "every-plugin/effect";
|
|
4
|
+
import { colors, icons } from "../utils/theme";
|
|
5
|
+
|
|
6
|
+
export interface TransactionConfig {
|
|
7
|
+
account: string;
|
|
8
|
+
contract: string;
|
|
9
|
+
method: string;
|
|
10
|
+
argsBase64: string;
|
|
11
|
+
network?: "mainnet" | "testnet";
|
|
12
|
+
privateKey?: string;
|
|
13
|
+
gas?: string;
|
|
14
|
+
deposit?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface TransactionResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
txHash?: string;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const NEAR_CLI_VERSION = "0.23.5";
|
|
24
|
+
const INSTALLER_URL = `https://github.com/near/near-cli-rs/releases/download/v${NEAR_CLI_VERSION}/near-cli-rs-installer.sh`;
|
|
25
|
+
|
|
26
|
+
export class NearCliNotFoundError extends Error {
|
|
27
|
+
readonly _tag = "NearCliNotFoundError";
|
|
28
|
+
constructor() {
|
|
29
|
+
super("NEAR CLI not found");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class NearCliInstallError extends Error {
|
|
34
|
+
readonly _tag = "NearCliInstallError";
|
|
35
|
+
constructor(message: string) {
|
|
36
|
+
super(`Failed to install NEAR CLI: ${message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class TransactionError extends Error {
|
|
41
|
+
readonly _tag = "TransactionError";
|
|
42
|
+
constructor(message: string) {
|
|
43
|
+
super(message);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const checkNearCliInstalled = Effect.gen(function* () {
|
|
48
|
+
const result = yield* Effect.tryPromise({
|
|
49
|
+
try: async () => {
|
|
50
|
+
return new Promise<boolean>((resolve) => {
|
|
51
|
+
const proc = spawn("near", ["--version"], { shell: true, stdio: "pipe" });
|
|
52
|
+
proc.on("close", (code) => resolve(code === 0));
|
|
53
|
+
proc.on("error", () => resolve(false));
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
catch: () => new Error("Failed to check NEAR CLI"),
|
|
57
|
+
});
|
|
58
|
+
return result;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const installNearCli = Effect.gen(function* () {
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(` ${icons.pkg} Installing NEAR CLI v${NEAR_CLI_VERSION}...`);
|
|
64
|
+
console.log(colors.dim(` Using official installer from GitHub`));
|
|
65
|
+
console.log();
|
|
66
|
+
|
|
67
|
+
yield* Effect.tryPromise({
|
|
68
|
+
try: async () => {
|
|
69
|
+
return new Promise<void>((resolve, reject) => {
|
|
70
|
+
const proc = spawn("sh", ["-c", `curl --proto '=https' --tlsv1.2 -LsSf ${INSTALLER_URL} | sh`], {
|
|
71
|
+
stdio: "inherit",
|
|
72
|
+
shell: true,
|
|
73
|
+
});
|
|
74
|
+
proc.on("close", (code) => {
|
|
75
|
+
if (code === 0) resolve();
|
|
76
|
+
else reject(new NearCliInstallError(`Installer exited with code ${code}`));
|
|
77
|
+
});
|
|
78
|
+
proc.on("error", (err) => reject(new NearCliInstallError(err.message)));
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
catch: (e) => e as Error,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
console.log();
|
|
85
|
+
console.log(colors.green(` ${icons.ok} NEAR CLI installed successfully`));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export const ensureNearCli = Effect.gen(function* () {
|
|
89
|
+
const isInstalled = yield* checkNearCliInstalled;
|
|
90
|
+
|
|
91
|
+
if (isInstalled) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log();
|
|
96
|
+
console.log(colors.error(` ${icons.err} NEAR CLI not found`));
|
|
97
|
+
|
|
98
|
+
const shouldInstall = yield* Effect.tryPromise({
|
|
99
|
+
try: () => confirm({
|
|
100
|
+
message: "Install NEAR CLI? (required for publishing)",
|
|
101
|
+
default: true,
|
|
102
|
+
}),
|
|
103
|
+
catch: () => new Error("Prompt cancelled"),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!shouldInstall) {
|
|
107
|
+
console.log();
|
|
108
|
+
console.log(colors.dim(" To install manually:"));
|
|
109
|
+
console.log(colors.dim(` curl --proto '=https' --tlsv1.2 -LsSf ${INSTALLER_URL} | sh`));
|
|
110
|
+
console.log();
|
|
111
|
+
yield* Effect.fail(new NearCliNotFoundError());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
yield* installNearCli;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
export interface CreateSubaccountConfig {
|
|
118
|
+
newAccount: string;
|
|
119
|
+
parentAccount: string;
|
|
120
|
+
initialBalance?: string;
|
|
121
|
+
network: "mainnet" | "testnet";
|
|
122
|
+
privateKey?: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface CreateSubaccountResult {
|
|
126
|
+
success: boolean;
|
|
127
|
+
account: string;
|
|
128
|
+
error?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const createSubaccount = (config: CreateSubaccountConfig): Effect.Effect<CreateSubaccountResult, Error> =>
|
|
132
|
+
Effect.gen(function* () {
|
|
133
|
+
const balance = config.initialBalance || "0.1NEAR";
|
|
134
|
+
const network = config.network;
|
|
135
|
+
|
|
136
|
+
const args = [
|
|
137
|
+
"account",
|
|
138
|
+
"create-account",
|
|
139
|
+
"fund-myself",
|
|
140
|
+
config.newAccount,
|
|
141
|
+
balance,
|
|
142
|
+
"autogenerate-new-keypair",
|
|
143
|
+
"save-to-keychain",
|
|
144
|
+
"sign-as",
|
|
145
|
+
config.parentAccount,
|
|
146
|
+
"network-config",
|
|
147
|
+
network,
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
if (config.privateKey) {
|
|
151
|
+
args.push("sign-with-plaintext-private-key", "--private-key", config.privateKey, "send");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log();
|
|
155
|
+
console.log(` ${icons.run} Creating subaccount...`);
|
|
156
|
+
console.log(colors.dim(` Account: ${config.newAccount}`));
|
|
157
|
+
console.log(colors.dim(` Parent: ${config.parentAccount}`));
|
|
158
|
+
console.log(colors.dim(` Balance: ${balance}`));
|
|
159
|
+
console.log();
|
|
160
|
+
|
|
161
|
+
const output = yield* Effect.tryPromise({
|
|
162
|
+
try: async () => {
|
|
163
|
+
return new Promise<string>((resolve, reject) => {
|
|
164
|
+
const proc = spawn("near", args, {
|
|
165
|
+
shell: true,
|
|
166
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
let stdout = "";
|
|
170
|
+
let stderr = "";
|
|
171
|
+
|
|
172
|
+
proc.stdout?.on("data", (data) => {
|
|
173
|
+
const text = data.toString();
|
|
174
|
+
stdout += text;
|
|
175
|
+
process.stdout.write(text);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
proc.stderr?.on("data", (data) => {
|
|
179
|
+
const text = data.toString();
|
|
180
|
+
stderr += text;
|
|
181
|
+
process.stderr.write(text);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
proc.on("close", (code) => {
|
|
185
|
+
if (code === 0) {
|
|
186
|
+
resolve(stdout);
|
|
187
|
+
} else {
|
|
188
|
+
reject(new Error(stderr || `Subaccount creation failed with code ${code}`));
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
proc.on("error", (err) => {
|
|
193
|
+
reject(new Error(err.message));
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
catch: (e) => e as Error,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
account: config.newAccount,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
export const executeTransaction = (config: TransactionConfig): Effect.Effect<TransactionResult, Error> =>
|
|
207
|
+
Effect.gen(function* () {
|
|
208
|
+
const gas = (config.gas || "300Tgas").replace(/\s+/g, "");
|
|
209
|
+
const deposit = (config.deposit || "0NEAR").replace(/\s+/g, "");
|
|
210
|
+
const network = config.network || (config.account.endsWith(".testnet") ? "testnet" : "mainnet");
|
|
211
|
+
|
|
212
|
+
const args = [
|
|
213
|
+
"contract",
|
|
214
|
+
"call-function",
|
|
215
|
+
"as-transaction",
|
|
216
|
+
config.contract,
|
|
217
|
+
config.method,
|
|
218
|
+
"base64-args",
|
|
219
|
+
config.argsBase64,
|
|
220
|
+
"prepaid-gas",
|
|
221
|
+
gas,
|
|
222
|
+
"attached-deposit",
|
|
223
|
+
deposit,
|
|
224
|
+
"sign-as",
|
|
225
|
+
config.account,
|
|
226
|
+
"network-config",
|
|
227
|
+
network,
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
if (config.privateKey) {
|
|
231
|
+
args.push("sign-with-plaintext-private-key", config.privateKey, "send");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
console.log();
|
|
235
|
+
console.log(` ${icons.run} Executing transaction...`);
|
|
236
|
+
console.log(colors.dim(` Contract: ${config.contract}`));
|
|
237
|
+
console.log(colors.dim(` Method: ${config.method}`));
|
|
238
|
+
console.log(colors.dim(` Signer: ${config.account}`));
|
|
239
|
+
console.log();
|
|
240
|
+
|
|
241
|
+
const output = yield* Effect.tryPromise({
|
|
242
|
+
try: async () => {
|
|
243
|
+
return new Promise<string>((resolve, reject) => {
|
|
244
|
+
const proc = spawn("near", args, {
|
|
245
|
+
shell: true,
|
|
246
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
let stdout = "";
|
|
250
|
+
let stderr = "";
|
|
251
|
+
|
|
252
|
+
proc.stdout?.on("data", (data) => {
|
|
253
|
+
const text = data.toString();
|
|
254
|
+
stdout += text;
|
|
255
|
+
process.stdout.write(text);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
proc.stderr?.on("data", (data) => {
|
|
259
|
+
const text = data.toString();
|
|
260
|
+
stderr += text;
|
|
261
|
+
process.stderr.write(text);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
proc.on("close", (code) => {
|
|
265
|
+
if (code === 0) {
|
|
266
|
+
resolve(stdout);
|
|
267
|
+
} else {
|
|
268
|
+
reject(new TransactionError(stderr || `Transaction failed with code ${code}`));
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
proc.on("error", (err) => {
|
|
273
|
+
reject(new TransactionError(err.message));
|
|
274
|
+
});
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
catch: (e) => e as Error,
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const txHashMatch = output.match(/Transaction ID:\s*([A-Za-z0-9]+)/i) ||
|
|
281
|
+
output.match(/([A-HJ-NP-Za-km-z1-9]{43,44})/);
|
|
282
|
+
|
|
283
|
+
const txHash = txHashMatch?.[1] || "unknown";
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
txHash,
|
|
288
|
+
};
|
|
289
|
+
});
|
package/src/lib/nova.ts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { NovaSdk } from "nova-sdk-js";
|
|
2
|
+
import { Effect } from "every-plugin/effect";
|
|
3
|
+
import { getConfigDir } from "../config";
|
|
4
|
+
|
|
5
|
+
export interface NovaConfig {
|
|
6
|
+
accountId: string;
|
|
7
|
+
sessionToken: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SecretsData {
|
|
11
|
+
secrets: Record<string, string>;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UploadResult {
|
|
16
|
+
cid: string;
|
|
17
|
+
groupId: string;
|
|
18
|
+
txHash?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const getNovaConfig = Effect.gen(function* () {
|
|
22
|
+
const accountId = process.env.NOVA_ACCOUNT_ID;
|
|
23
|
+
const sessionToken = process.env.NOVA_SESSION_TOKEN;
|
|
24
|
+
|
|
25
|
+
if (!accountId || !sessionToken) {
|
|
26
|
+
return yield* Effect.fail(
|
|
27
|
+
new Error(
|
|
28
|
+
"NOVA credentials not configured. Run 'bos login' to authenticate with NOVA."
|
|
29
|
+
)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { accountId, sessionToken } satisfies NovaConfig;
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export function createNovaClient(config: NovaConfig): NovaSdk {
|
|
37
|
+
return new NovaSdk(config.accountId, {
|
|
38
|
+
sessionToken: config.sessionToken,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function getSecretsGroupId(nearAccount: string): string {
|
|
43
|
+
return `${nearAccount}-secrets`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const registerSecretsGroup = (
|
|
47
|
+
nova: NovaSdk,
|
|
48
|
+
nearAccount: string,
|
|
49
|
+
gatewayAccount: string
|
|
50
|
+
) =>
|
|
51
|
+
Effect.gen(function* () {
|
|
52
|
+
const groupId = getSecretsGroupId(nearAccount);
|
|
53
|
+
|
|
54
|
+
yield* Effect.tryPromise({
|
|
55
|
+
try: () => nova.registerGroup(groupId),
|
|
56
|
+
catch: (e) => new Error(`Failed to register NOVA group: ${e}`),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
yield* Effect.tryPromise({
|
|
60
|
+
try: () => nova.addGroupMember(groupId, gatewayAccount),
|
|
61
|
+
catch: (e) => new Error(`Failed to add gateway to NOVA group: ${e}`),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return groupId;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const uploadSecrets = (
|
|
68
|
+
nova: NovaSdk,
|
|
69
|
+
groupId: string,
|
|
70
|
+
secrets: Record<string, string>
|
|
71
|
+
) =>
|
|
72
|
+
Effect.gen(function* () {
|
|
73
|
+
const secretsData: SecretsData = {
|
|
74
|
+
secrets,
|
|
75
|
+
updatedAt: new Date().toISOString(),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const buffer = Buffer.from(JSON.stringify(secretsData, null, 2));
|
|
79
|
+
|
|
80
|
+
const result = yield* Effect.tryPromise({
|
|
81
|
+
try: () => nova.upload(groupId, buffer, "secrets.json"),
|
|
82
|
+
catch: (e) => new Error(`Failed to upload secrets to NOVA: ${e}`),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
cid: result.cid,
|
|
87
|
+
groupId,
|
|
88
|
+
txHash: result.trans_id,
|
|
89
|
+
} satisfies UploadResult;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export const retrieveSecrets = (nova: NovaSdk, groupId: string, cid: string) =>
|
|
93
|
+
Effect.gen(function* () {
|
|
94
|
+
const result = yield* Effect.tryPromise({
|
|
95
|
+
try: () => nova.retrieve(groupId, cid),
|
|
96
|
+
catch: (e) => new Error(`Failed to retrieve secrets from NOVA: ${e}`),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const secretsData = JSON.parse(result.data.toString()) as SecretsData;
|
|
100
|
+
return secretsData;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
export function parseEnvFile(content: string): Record<string, string> {
|
|
104
|
+
const secrets: Record<string, string> = {};
|
|
105
|
+
const lines = content.split("\n");
|
|
106
|
+
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
const trimmed = line.trim();
|
|
109
|
+
|
|
110
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const eqIndex = trimmed.indexOf("=");
|
|
115
|
+
if (eqIndex === -1) {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
120
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
124
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
125
|
+
) {
|
|
126
|
+
value = value.slice(1, -1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (key) {
|
|
130
|
+
secrets[key] = value;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return secrets;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function filterSecretsToRequired(
|
|
138
|
+
allSecrets: Record<string, string>,
|
|
139
|
+
requiredKeys: string[]
|
|
140
|
+
): Record<string, string> {
|
|
141
|
+
const filtered: Record<string, string> = {};
|
|
142
|
+
|
|
143
|
+
for (const key of requiredKeys) {
|
|
144
|
+
if (key in allSecrets) {
|
|
145
|
+
filtered[key] = allSecrets[key];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return filtered;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function hasNovaCredentials(): boolean {
|
|
153
|
+
return !!(process.env.NOVA_ACCOUNT_ID && process.env.NOVA_SESSION_TOKEN);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getBosEnvPath(): string {
|
|
157
|
+
return `${getConfigDir()}/.env.bos`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const saveNovaCredentials = (accountId: string, sessionToken: string) =>
|
|
161
|
+
Effect.gen(function* () {
|
|
162
|
+
const envPath = getBosEnvPath();
|
|
163
|
+
let content = "";
|
|
164
|
+
|
|
165
|
+
const file = Bun.file(envPath);
|
|
166
|
+
const exists = yield* Effect.promise(() => file.exists());
|
|
167
|
+
|
|
168
|
+
if (exists) {
|
|
169
|
+
content = yield* Effect.tryPromise({
|
|
170
|
+
try: () => file.text(),
|
|
171
|
+
catch: () => new Error("File read failed"),
|
|
172
|
+
}).pipe(Effect.orElseSucceed(() => ""));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const lines = content.split("\n");
|
|
176
|
+
const newLines: string[] = [];
|
|
177
|
+
let foundAccountId = false;
|
|
178
|
+
let foundSessionToken = false;
|
|
179
|
+
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
const trimmed = line.trim();
|
|
182
|
+
if (trimmed.startsWith("NOVA_ACCOUNT_ID=")) {
|
|
183
|
+
newLines.push(`NOVA_ACCOUNT_ID=${accountId}`);
|
|
184
|
+
foundAccountId = true;
|
|
185
|
+
} else if (trimmed.startsWith("NOVA_SESSION_TOKEN=")) {
|
|
186
|
+
newLines.push(`NOVA_SESSION_TOKEN=${sessionToken}`);
|
|
187
|
+
foundSessionToken = true;
|
|
188
|
+
} else {
|
|
189
|
+
newLines.push(line);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!foundAccountId) {
|
|
194
|
+
if (newLines.length > 0 && newLines[newLines.length - 1] !== "") {
|
|
195
|
+
newLines.push("");
|
|
196
|
+
}
|
|
197
|
+
newLines.push(`NOVA_ACCOUNT_ID=${accountId}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (!foundSessionToken) {
|
|
201
|
+
newLines.push(`NOVA_SESSION_TOKEN=${sessionToken}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
yield* Effect.tryPromise({
|
|
205
|
+
try: () => Bun.write(envPath, newLines.join("\n")),
|
|
206
|
+
catch: (e) => new Error(`Failed to save credentials: ${e}`),
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
process.env.NOVA_ACCOUNT_ID = accountId;
|
|
210
|
+
process.env.NOVA_SESSION_TOKEN = sessionToken;
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
export const removeNovaCredentials = Effect.gen(function* () {
|
|
214
|
+
const envPath = getBosEnvPath();
|
|
215
|
+
let content = "";
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
content = yield* Effect.tryPromise({
|
|
219
|
+
try: () => Bun.file(envPath).text(),
|
|
220
|
+
catch: () => "",
|
|
221
|
+
});
|
|
222
|
+
} catch {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const lines = content.split("\n");
|
|
227
|
+
const newLines = lines.filter((line) => {
|
|
228
|
+
const trimmed = line.trim();
|
|
229
|
+
return !trimmed.startsWith("NOVA_ACCOUNT_ID=") && !trimmed.startsWith("NOVA_SESSION_TOKEN=");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
yield* Effect.tryPromise({
|
|
233
|
+
try: () => Bun.write(envPath, newLines.join("\n")),
|
|
234
|
+
catch: (e) => new Error(`Failed to remove credentials: ${e}`),
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
delete process.env.NOVA_ACCOUNT_ID;
|
|
238
|
+
delete process.env.NOVA_SESSION_TOKEN;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
export const verifyNovaCredentials = (accountId: string, sessionToken: string) =>
|
|
242
|
+
Effect.gen(function* () {
|
|
243
|
+
const nova = new NovaSdk(accountId, { sessionToken });
|
|
244
|
+
|
|
245
|
+
yield* Effect.tryPromise({
|
|
246
|
+
try: () => nova.getBalance(),
|
|
247
|
+
catch: (e) => {
|
|
248
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
249
|
+
return new Error(`NOVA verification failed: ${message}`);
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return true;
|
|
254
|
+
});
|