@darksol/terminal 0.4.1-beta.1 → 0.4.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@darksol/terminal",
3
- "version": "0.4.1-beta.1",
3
+ "version": "0.4.2",
4
4
  "description": "DARKSOL Terminal — unified CLI for all DARKSOL services. Market intel, trading, oracle, casino, and more.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -787,6 +787,42 @@ export function cli(argv) {
787
787
  }
788
788
  });
789
789
 
790
+ // ═══════════════════════════════════════
791
+ // FUZZY / NATURAL LANGUAGE FALLBACK
792
+ // ═══════════════════════════════════════
793
+ // If someone types something Commander doesn't recognize,
794
+ // try routing it through AI before saying "unknown command"
795
+ program.on('command:*', async (operands) => {
796
+ const input = operands.join(' ');
797
+ const { hasAnyLLM } = await import('./config/keys.js');
798
+
799
+ if (hasAnyLLM()) {
800
+ const { parseIntent } = await import('./llm/intent.js');
801
+ const { info, error: showError } = await import('./ui/components.js');
802
+
803
+ console.log('');
804
+ info(`"${input}" isn't a command — asking AI...`);
805
+ console.log('');
806
+
807
+ try {
808
+ const result = await parseIntent(input);
809
+ if (result && result.action !== 'error' && result.action !== 'unknown') {
810
+ const { executeIntent } = await import('./llm/intent.js');
811
+ await executeIntent(result);
812
+ return;
813
+ }
814
+ } catch {}
815
+
816
+ showError(`Unknown command: ${input}`);
817
+ info('Try: darksol help');
818
+ } else {
819
+ const { error: showError, info } = await import('./ui/components.js');
820
+ showError(`Unknown command: ${input}`);
821
+ info('Tip: Set up AI (darksol setup) for natural language commands');
822
+ info('Run: darksol help');
823
+ }
824
+ });
825
+
790
826
  program.parse(argv);
791
827
  }
792
828
 
@@ -1,4 +1,4 @@
1
- import fetch from 'node-fetch';
1
+ import { fetchJSON } from '../utils/fetch.js';
2
2
  import { getServiceURL } from '../config/store.js';
3
3
  import { theme } from '../ui/theme.js';
4
4
  import { spinner, table, kvDisplay, error } from '../ui/components.js';
@@ -9,8 +9,7 @@ const getURL = () => getServiceURL('builders') || 'https://builders.darksol.net'
9
9
  export async function buildersLeaderboard(opts = {}) {
10
10
  const spin = spinner('Loading builder leaderboard...').start();
11
11
  try {
12
- const resp = await fetch(`${getURL()}/api/leaderboard?limit=${opts.limit || 20}`);
13
- const data = await resp.json();
12
+ const data = await fetchJSON(`${getURL()}/api/leaderboard?limit=${opts.limit || 20}`);
14
13
  spin.succeed('Leaderboard loaded');
15
14
 
16
15
  showSection('ERC-8021 BUILDER LEADERBOARD');
@@ -34,8 +33,7 @@ export async function buildersLeaderboard(opts = {}) {
34
33
  export async function buildersLookup(code) {
35
34
  const spin = spinner(`Looking up builder: ${code}...`).start();
36
35
  try {
37
- const resp = await fetch(`${getURL()}/api/builders/${code}`);
38
- const data = await resp.json();
36
+ const data = await fetchJSON(`${getURL()}/api/builders/${code}`);
39
37
  spin.succeed('Builder found');
40
38
 
41
39
  showSection(`BUILDER — ${code}`);
@@ -49,8 +47,7 @@ export async function buildersLookup(code) {
49
47
  export async function buildersFeed(opts = {}) {
50
48
  const spin = spinner('Loading builder feed...').start();
51
49
  try {
52
- const resp = await fetch(`${getURL()}/api/feed?limit=${opts.limit || 20}`);
53
- const data = await resp.json();
50
+ const data = await fetchJSON(`${getURL()}/api/feed?limit=${opts.limit || 20}`);
54
51
  spin.succeed('Feed loaded');
55
52
 
56
53
  showSection('BUILDER FEED');
@@ -1,7 +1,7 @@
1
- import fetch from 'node-fetch';
1
+ import { fetchJSON } from '../utils/fetch.js';
2
2
  import { getServiceURL } from '../config/store.js';
3
3
  import { theme } from '../ui/theme.js';
4
- import { spinner, kvDisplay, error, table } from '../ui/components.js';
4
+ import { spinner, kvDisplay, error, info, table } from '../ui/components.js';
5
5
  import { showSection } from '../ui/banner.js';
6
6
 
7
7
  const getURL = () => getServiceURL('cards') || 'https://acp.darksol.net/cards';
@@ -9,8 +9,7 @@ const getURL = () => getServiceURL('cards') || 'https://acp.darksol.net/cards';
9
9
  export async function cardsCatalog() {
10
10
  const spin = spinner('Loading card catalog...').start();
11
11
  try {
12
- const resp = await fetch(`${getURL()}/api/cards/catalog`);
13
- const data = await resp.json();
12
+ const data = await fetchJSON(`${getURL()}/api/cards/catalog`);
14
13
  spin.succeed('Catalog loaded');
15
14
 
16
15
  showSection('PREPAID CARDS');
@@ -29,18 +28,18 @@ export async function cardsCatalog() {
29
28
  } catch (err) {
30
29
  spin.fail('Catalog failed');
31
30
  error(err.message);
31
+ info('Cards service: https://acp.darksol.net/cards');
32
32
  }
33
33
  }
34
34
 
35
35
  export async function cardsOrder(provider, amount) {
36
36
  const spin = spinner('Processing card order...').start();
37
37
  try {
38
- const resp = await fetch(`${getURL()}/api/cards/order`, {
38
+ const data = await fetchJSON(`${getURL()}/api/cards/order`, {
39
39
  method: 'POST',
40
40
  headers: { 'Content-Type': 'application/json' },
41
41
  body: JSON.stringify({ provider, amount }),
42
42
  });
43
- const data = await resp.json();
44
43
  spin.succeed('Order placed');
45
44
 
46
45
  showSection('CARD ORDER');
@@ -48,14 +47,14 @@ export async function cardsOrder(provider, amount) {
48
47
  } catch (err) {
49
48
  spin.fail('Order failed');
50
49
  error(err.message);
50
+ info('The cards order API may not be live yet. Check: https://acp.darksol.net/cards');
51
51
  }
52
52
  }
53
53
 
54
54
  export async function cardsStatus(orderId) {
55
55
  const spin = spinner('Checking order...').start();
56
56
  try {
57
- const resp = await fetch(`${getURL()}/api/cards/status?orderId=${orderId}`);
58
- const data = await resp.json();
57
+ const data = await fetchJSON(`${getURL()}/api/cards/status?orderId=${orderId}`);
59
58
  spin.succeed('Status loaded');
60
59
 
61
60
  showSection(`CARD ORDER — ${orderId}`);
@@ -1,3 +1,4 @@
1
+ import { fetchJSON } from '../utils/fetch.js';
1
2
  import fetch from 'node-fetch';
2
3
  import { getServiceURL } from '../config/store.js';
3
4
  import { theme } from '../ui/theme.js';
@@ -9,7 +10,7 @@ const getURL = () => getServiceURL('casino') || 'https://casino.darksol.net';
9
10
  export async function casinoBet(game, opts = {}) {
10
11
  const spin = spinner(`Placing ${game} bet...`).start();
11
12
  try {
12
- const resp = await fetch(`${getURL()}/api/bet`, {
13
+ const data = await fetchJSON(`${getURL()}/api/bet`, {
13
14
  method: 'POST',
14
15
  headers: { 'Content-Type': 'application/json' },
15
16
  body: JSON.stringify({
@@ -19,7 +20,6 @@ export async function casinoBet(game, opts = {}) {
19
20
  wallet: opts.wallet,
20
21
  }),
21
22
  });
22
- const data = await resp.json();
23
23
  spin.succeed('Bet placed');
24
24
 
25
25
  showSection(`CASINO — ${game.toUpperCase()}`);
@@ -40,8 +40,7 @@ export async function casinoBet(game, opts = {}) {
40
40
  export async function casinoTables() {
41
41
  const spin = spinner('Loading tables...').start();
42
42
  try {
43
- const resp = await fetch(`${getURL()}/api/tables`);
44
- const data = await resp.json();
43
+ const data = await fetchJSON(`${getURL()}/api/tables`);
45
44
  spin.succeed('Tables loaded');
46
45
 
47
46
  showSection('CASINO TABLES');
@@ -66,8 +65,7 @@ export async function casinoTables() {
66
65
  export async function casinoStats() {
67
66
  const spin = spinner('Loading stats...').start();
68
67
  try {
69
- const resp = await fetch(`${getURL()}/api/stats`);
70
- const data = await resp.json();
68
+ const data = await fetchJSON(`${getURL()}/api/stats`);
71
69
  spin.succeed('Stats loaded');
72
70
 
73
71
  showSection('CASINO STATS');
@@ -81,8 +79,7 @@ export async function casinoStats() {
81
79
  export async function casinoReceipt(id) {
82
80
  const spin = spinner(`Loading receipt ${id}...`).start();
83
81
  try {
84
- const resp = await fetch(`${getURL()}/api/receipt/${id}`);
85
- const data = await resp.json();
82
+ const data = await fetchJSON(`${getURL()}/api/receipt/${id}`);
86
83
  spin.succeed('Receipt loaded');
87
84
 
88
85
  showSection(`CASINO RECEIPT — ${id}`);
@@ -92,3 +89,4 @@ export async function casinoReceipt(id) {
92
89
  error(err.message);
93
90
  }
94
91
  }
92
+
@@ -1,4 +1,4 @@
1
- import fetch from 'node-fetch';
1
+ import { fetchJSON } from '../utils/fetch.js';
2
2
  import { getServiceURL } from '../config/store.js';
3
3
  import { theme } from '../ui/theme.js';
4
4
  import { spinner, kvDisplay, error } from '../ui/components.js';
@@ -9,8 +9,7 @@ const getURL = () => getServiceURL('facilitator') || 'https://facilitator.darkso
9
9
  export async function facilitatorHealth() {
10
10
  const spin = spinner('Checking facilitator...').start();
11
11
  try {
12
- const resp = await fetch(`${getURL()}/api/health`);
13
- const data = await resp.json();
12
+ const data = await fetchJSON(`${getURL()}/api/health`);
14
13
  spin.succeed('Facilitator online');
15
14
 
16
15
  showSection('FACILITATOR STATUS');
@@ -24,12 +23,11 @@ export async function facilitatorHealth() {
24
23
  export async function facilitatorVerify(payment) {
25
24
  const spin = spinner('Verifying payment...').start();
26
25
  try {
27
- const resp = await fetch(`${getURL()}/api/verify`, {
26
+ const data = await fetchJSON(`${getURL()}/api/verify`, {
28
27
  method: 'POST',
29
28
  headers: { 'Content-Type': 'application/json' },
30
29
  body: JSON.stringify(typeof payment === 'string' ? JSON.parse(payment) : payment),
31
30
  });
32
- const data = await resp.json();
33
31
  spin.succeed(data.valid ? 'Payment valid' : 'Payment invalid');
34
32
 
35
33
  showSection('PAYMENT VERIFICATION');
@@ -43,12 +41,11 @@ export async function facilitatorVerify(payment) {
43
41
  export async function facilitatorSettle(payment) {
44
42
  const spin = spinner('Settling on-chain...').start();
45
43
  try {
46
- const resp = await fetch(`${getURL()}/api/settle`, {
44
+ const data = await fetchJSON(`${getURL()}/api/settle`, {
47
45
  method: 'POST',
48
46
  headers: { 'Content-Type': 'application/json' },
49
47
  body: JSON.stringify(typeof payment === 'string' ? JSON.parse(payment) : payment),
50
48
  });
51
- const data = await resp.json();
52
49
  spin.succeed('Settlement complete');
53
50
 
54
51
  showSection('SETTLEMENT');
@@ -1,3 +1,4 @@
1
+ import { fetchJSON } from '../utils/fetch.js';
1
2
  import fetch from 'node-fetch';
2
3
  import { getServiceURL } from '../config/store.js';
3
4
  import { theme } from '../ui/theme.js';
@@ -9,8 +10,7 @@ const getURL = () => getServiceURL('oracle') || 'https://acp.darksol.net/oracle'
9
10
  export async function oracleFlip() {
10
11
  const spin = spinner('Flipping coin...').start();
11
12
  try {
12
- const resp = await fetch(`${getURL()}/api/coin`);
13
- const data = await resp.json();
13
+ const data = await fetchJSON(`${getURL()}/api/coin`);
14
14
  spin.succeed('Coin flipped');
15
15
  showSection('ORACLE — COIN FLIP');
16
16
  kvDisplay([
@@ -26,8 +26,7 @@ export async function oracleFlip() {
26
26
  export async function oracleDice(sides = 6) {
27
27
  const spin = spinner(`Rolling d${sides}...`).start();
28
28
  try {
29
- const resp = await fetch(`${getURL()}/api/dice?sides=${sides}`);
30
- const data = await resp.json();
29
+ const data = await fetchJSON(`${getURL()}/api/dice?sides=${sides}`);
31
30
  spin.succeed('Dice rolled');
32
31
  showSection(`ORACLE — D${sides}`);
33
32
  kvDisplay([
@@ -44,8 +43,7 @@ export async function oracleDice(sides = 6) {
44
43
  export async function oracleNumber(min = 1, max = 100) {
45
44
  const spin = spinner(`Generating number ${min}-${max}...`).start();
46
45
  try {
47
- const resp = await fetch(`${getURL()}/api/number?min=${min}&max=${max}`);
48
- const data = await resp.json();
46
+ const data = await fetchJSON(`${getURL()}/api/number?min=${min}&max=${max}`);
49
47
  spin.succeed('Number generated');
50
48
  showSection('ORACLE — RANDOM NUMBER');
51
49
  kvDisplay([
@@ -62,12 +60,11 @@ export async function oracleNumber(min = 1, max = 100) {
62
60
  export async function oracleShuffle(items) {
63
61
  const spin = spinner('Shuffling...').start();
64
62
  try {
65
- const resp = await fetch(`${getURL()}/api/shuffle`, {
63
+ const data = await fetchJSON(`${getURL()}/api/shuffle`, {
66
64
  method: 'POST',
67
65
  headers: { 'Content-Type': 'application/json' },
68
66
  body: JSON.stringify({ items }),
69
67
  });
70
- const data = await resp.json();
71
68
  spin.succeed('Shuffled');
72
69
  showSection('ORACLE — SHUFFLE');
73
70
  console.log(theme.gold(' Result: ') + (data.result || data.value || []).join(', '));
@@ -80,8 +77,7 @@ export async function oracleShuffle(items) {
80
77
  export async function oracleHealth() {
81
78
  const spin = spinner('Checking oracle...').start();
82
79
  try {
83
- const resp = await fetch(`${getURL()}/api/health`);
84
- const data = await resp.json();
80
+ const data = await fetchJSON(`${getURL()}/api/health`);
85
81
  spin.succeed('Oracle online');
86
82
  showSection('ORACLE STATUS');
87
83
  kvDisplay(Object.entries(data).map(([k, v]) => [k, String(v)]));
package/src/ui/banner.js CHANGED
@@ -26,7 +26,7 @@ export function showBanner(opts = {}) {
26
26
  );
27
27
  console.log(
28
28
  theme.dim(' ║ ') +
29
- theme.subtle(' v0.4.1-beta.1') +
29
+ theme.subtle(' v0.4.2') +
30
30
  theme.dim(' ') +
31
31
  theme.gold('🌑') +
32
32
  theme.dim(' ║')
@@ -44,7 +44,7 @@ export function showBanner(opts = {}) {
44
44
 
45
45
  export function showMiniBanner() {
46
46
  console.log('');
47
- console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(' v0.4.1-beta.1'));
47
+ console.log(theme.gold.bold(' 🌑 DARKSOL TERMINAL') + theme.dim(' v0.4.2'));
48
48
  console.log(theme.dim(' ─────────────────────────────'));
49
49
  console.log('');
50
50
  }
@@ -73,3 +73,5 @@ export function showDivider() {
73
73
 
74
74
 
75
75
 
76
+
77
+
@@ -0,0 +1,28 @@
1
+ import fetch from 'node-fetch';
2
+
3
+ /**
4
+ * Fetch JSON safely — handles HTML error pages, non-JSON responses,
5
+ * and invalid JSON gracefully instead of crashing with cryptic errors.
6
+ */
7
+ export async function fetchJSON(url, options = {}) {
8
+ const resp = await fetch(url, options);
9
+ const contentType = resp.headers.get('content-type') || '';
10
+ const text = await resp.text();
11
+
12
+ if (!contentType.includes('application/json') && !contentType.includes('text/json')) {
13
+ const preview = text.substring(0, 60).replace(/\n/g, ' ');
14
+ throw new Error(
15
+ `Expected JSON but got ${contentType.split(';')[0] || 'unknown'} (HTTP ${resp.status}). ` +
16
+ `Response: "${preview}..."`
17
+ );
18
+ }
19
+
20
+ try {
21
+ return JSON.parse(text);
22
+ } catch {
23
+ const preview = text.substring(0, 60).replace(/\n/g, ' ');
24
+ throw new Error(`Invalid JSON (HTTP ${resp.status}): "${preview}..."`);
25
+ }
26
+ }
27
+
28
+ export default fetchJSON;