@tangle-network/tcloud 0.2.0 → 0.3.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/dist/cli.js CHANGED
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TCloud
4
- } from "./chunk-STNZT6YR.js";
4
+ } from "./chunk-W7PUZZRE.js";
5
5
  import {
6
6
  generateWallet
7
- } from "./chunk-VD4RNZOC.js";
8
- import "./chunk-HL4CXKET.js";
7
+ } from "./chunk-ADGH2R2R.js";
8
+ import "./chunk-LN7XX5KG.js";
9
9
 
10
10
  // src/cli.ts
11
11
  import { Command } from "commander";
12
12
  import * as fs from "fs";
13
13
  import * as path from "path";
14
14
  import * as readline from "readline";
15
+ import { spawn } from "child_process";
15
16
  var CONFIG_DIR = path.join(process.env.HOME || "~", ".tcloud");
16
17
  var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
17
18
  var WALLETS_FILE = path.join(CONFIG_DIR, "wallets.json");
@@ -60,23 +61,52 @@ program.command("config").description("View or update configuration").option("--
60
61
  console.log(JSON.stringify(c, null, 2));
61
62
  });
62
63
  var auth = program.command("auth").description("Authentication");
63
- auth.command("login").description("Log in via browser (device flow)").action(async () => {
64
+ function openBrowser(url) {
65
+ if (process.env.NO_BROWSER || process.env.CI) return false;
66
+ if (!process.stdout.isTTY) return false;
67
+ try {
68
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
69
+ const child = spawn(cmd, [url], { stdio: "ignore", detached: true });
70
+ child.unref();
71
+ return true;
72
+ } catch {
73
+ return false;
74
+ }
75
+ }
76
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
77
+ async function runDeviceFlow(mode) {
64
78
  const config = loadConfig();
79
+ const initRes = await fetch(`${config.apiUrl}/api/auth/device`, { method: "POST" });
80
+ if (!initRes.ok) {
81
+ console.error(`
82
+ \u2717 Auth server returned ${initRes.status}.`);
83
+ process.exit(1);
84
+ }
85
+ const d = await initRes.json();
86
+ const urlWithCode = `${d.verification_url}?user_code=${encodeURIComponent(d.user_code)}`;
87
+ const opened = openBrowser(urlWithCode);
88
+ const header = mode === "signup" ? "Creating your Tangle account" : "Signing you in";
89
+ console.log(`
90
+ ${header}
91
+ `);
92
+ console.log(` ${opened ? "Browser opened:" : "Open this URL:"}`);
93
+ console.log(` ${urlWithCode}
94
+ `);
95
+ console.log(` Verification code: ${d.user_code}
96
+ `);
97
+ const deadline = Date.now() + (d.expires_in || 600) * 1e3;
98
+ const interval = Math.max(2, d.interval || 5) * 1e3;
99
+ const spinnerEnabled = process.stdout.isTTY && !process.env.CI;
100
+ let frame = 0;
101
+ function tickSpinner() {
102
+ if (!spinnerEnabled) return;
103
+ const remaining = Math.max(0, Math.round((deadline - Date.now()) / 1e3));
104
+ process.stdout.write(`\r ${SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length]} waiting for browser confirmation (${remaining}s remaining) `);
105
+ }
106
+ const spinnerTimer = spinnerEnabled ? setInterval(tickSpinner, 100) : null;
65
107
  try {
66
- const res = await fetch(`${config.apiUrl}/api/auth/device`, { method: "POST" });
67
- if (!res.ok) {
68
- console.error("Auth server error");
69
- process.exit(1);
70
- }
71
- const d = await res.json();
72
- console.log(`
73
- Open: ${d.verification_url}
74
- Code: ${d.user_code}
75
-
76
- Waiting...`);
77
- const deadline = Date.now() + (d.expires_in || 600) * 1e3;
78
108
  while (Date.now() < deadline) {
79
- await new Promise((r2) => setTimeout(r2, (d.interval || 5) * 1e3));
109
+ await new Promise((r2) => setTimeout(r2, interval));
80
110
  const r = await fetch(`${config.apiUrl}/api/auth/device/token`, {
81
111
  method: "POST",
82
112
  headers: { "Content-Type": "application/json" },
@@ -86,31 +116,112 @@ auth.command("login").description("Log in via browser (device flow)").action(asy
86
116
  if (t.access_token) {
87
117
  config.apiKey = t.access_token;
88
118
  saveConfig(config);
89
- console.log("\n Authenticated!");
119
+ if (spinnerTimer) {
120
+ clearInterval(spinnerTimer);
121
+ process.stdout.write("\r" + " ".repeat(80) + "\r");
122
+ }
123
+ console.log(` \u2713 Authenticated`);
124
+ console.log(` \u2713 API key saved to ${CONFIG_FILE}
125
+ `);
126
+ console.log(` Try it:`);
127
+ console.log(` tcloud whoami`);
128
+ console.log(` tcloud chat "hello world"
129
+ `);
90
130
  return;
91
131
  }
92
132
  if (t.error === "expired_token") {
93
- console.error("\n Code expired.");
133
+ if (spinnerTimer) {
134
+ clearInterval(spinnerTimer);
135
+ process.stdout.write("\r" + " ".repeat(80) + "\r");
136
+ }
137
+ console.error(` \u2717 Code expired. Run 'tcloud auth ${mode}' again.`);
94
138
  process.exit(1);
95
139
  }
96
- process.stdout.write(".");
97
140
  }
98
- console.error("\n Timed out.");
99
- } catch (e) {
100
- console.error("Failed:", e.message);
141
+ if (spinnerTimer) {
142
+ clearInterval(spinnerTimer);
143
+ process.stdout.write("\r" + " ".repeat(80) + "\r");
144
+ }
145
+ console.error(` \u2717 Timed out after ${Math.round((d.expires_in || 600) / 60)} minutes.`);
146
+ process.exit(1);
147
+ } finally {
148
+ if (spinnerTimer) clearInterval(spinnerTimer);
101
149
  }
150
+ }
151
+ auth.command("signup").description("Create an account via browser (device flow)").action(() => runDeviceFlow("signup"));
152
+ auth.command("login").description("Log in via browser (device flow)").action(() => runDeviceFlow("login"));
153
+ auth.command("logout").description("Remove stored credentials").action(() => {
154
+ const c = loadConfig();
155
+ delete c.apiKey;
156
+ saveConfig(c);
157
+ console.log(` \u2713 Logged out. Config kept at ${CONFIG_FILE}.`);
102
158
  });
103
159
  auth.command("set-key").description("Set API key directly").argument("<key>").action((key) => {
104
160
  const c = loadConfig();
105
161
  c.apiKey = key;
106
162
  saveConfig(c);
107
- console.log("API key saved.");
163
+ console.log(" \u2713 API key saved.");
108
164
  });
109
165
  auth.command("status").description("Show auth status").action(() => {
110
166
  const c = loadConfig();
111
- console.log(c.apiKey ? `Authenticated: ${c.apiKey.slice(0, 15)}...` : "Not authenticated");
167
+ console.log(c.apiKey ? ` \u2713 Authenticated: ${c.apiKey.slice(0, 15)}...${c.apiKey.slice(-4)}` : " \u2717 Not authenticated. Run: tcloud auth signup");
112
168
  const w = loadWallets();
113
- if (w.length) console.log(`Shielded wallets: ${w.length}`);
169
+ if (w.length) console.log(` Shielded wallets: ${w.length}`);
170
+ });
171
+ auth.command("whoami").description("Show logged-in account details").action(async () => {
172
+ const c = loadConfig();
173
+ if (!c.apiKey) {
174
+ console.log(" \u2717 Not authenticated. Run: tcloud auth signup");
175
+ return;
176
+ }
177
+ try {
178
+ const res = await fetch(`${c.apiUrl}/api/auth/userinfo`, {
179
+ headers: { Authorization: `Bearer ${c.apiKey}` }
180
+ });
181
+ if (!res.ok) {
182
+ console.log(` \u2717 Auth check failed (${res.status}). Your key may be revoked \u2014 run: tcloud auth login`);
183
+ return;
184
+ }
185
+ const me = await res.json();
186
+ const user = me.user ?? {};
187
+ const sub = me.subscription;
188
+ console.log(` Email: ${user.email ?? "n/a"}`);
189
+ console.log(` User: ${user.name ?? user.id ?? "n/a"}`);
190
+ console.log(` Plan: ${sub?.plan ?? "free"}`);
191
+ console.log(` Balance: $${Number(me.balance ?? 0).toFixed(4)}`);
192
+ console.log(` Key: ${c.apiKey.slice(0, 15)}...${c.apiKey.slice(-4)}`);
193
+ console.log(` API: ${c.apiUrl}`);
194
+ } catch (e) {
195
+ console.log(` \u2717 ${e.message ?? e}`);
196
+ }
197
+ });
198
+ program.command("signup").description("Create an account via browser (alias for `auth signup`)").action(() => runDeviceFlow("signup"));
199
+ program.command("login").description("Log in via browser (alias for `auth login`)").action(() => runDeviceFlow("login"));
200
+ program.command("logout").description("Remove stored credentials (alias for `auth logout`)").action(() => {
201
+ const c = loadConfig();
202
+ delete c.apiKey;
203
+ saveConfig(c);
204
+ console.log(` \u2713 Logged out.`);
205
+ });
206
+ program.command("whoami").description("Show logged-in account (alias for `auth whoami`)").action(async () => {
207
+ const c = loadConfig();
208
+ if (!c.apiKey) {
209
+ console.log(" \u2717 Not authenticated. Run: tcloud signup");
210
+ return;
211
+ }
212
+ try {
213
+ const res = await fetch(`${c.apiUrl}/api/auth/userinfo`, { headers: { Authorization: `Bearer ${c.apiKey}` } });
214
+ if (!res.ok) {
215
+ console.log(` \u2717 Auth failed (${res.status}). Run: tcloud login`);
216
+ return;
217
+ }
218
+ const me = await res.json();
219
+ const user = me.user ?? {};
220
+ const sub = me.subscription;
221
+ console.log(` ${user.email ?? user.name ?? user.id} \xB7 $${Number(me.balance ?? 0).toFixed(4)} \xB7 ${sub?.plan ?? "free"}`);
222
+ } catch (e) {
223
+ console.log(` \u2717 ${e.message ?? e}`);
224
+ }
114
225
  });
115
226
  var wallet = program.command("wallet").description("Shielded wallet management");
116
227
  wallet.command("generate").description("Generate ephemeral wallet").option("-l, --label <name>").action((opts) => {
@@ -231,7 +342,10 @@ credits.command("add").description("Add credits").argument("<amount>").action(as
231
342
  const client = getClient();
232
343
  try {
233
344
  const data = await client.addCredits(parseFloat(amount));
234
- console.log(`Credits added. New balance: $${data.balance.toFixed(4)}`);
345
+ if (data.url) {
346
+ console.log(`Checkout URL: ${data.url}`);
347
+ console.log("Complete payment to add credits.");
348
+ }
235
349
  } catch (e) {
236
350
  console.error("Error:", e.message);
237
351
  }