@thangnqdev/ui 0.2.0 → 0.2.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/bin/cli.js +125 -0
- package/llms.txt +1 -1
- package/package.json +8 -1
- package/scripts/postinstall.js +125 -0
- package/ui-contract.json +1 -1
package/bin/cli.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import readline from 'node:readline';
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), '.thangnqdev-ui');
|
|
10
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'telemetry.json');
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const command = args[0] || 'help';
|
|
14
|
+
|
|
15
|
+
function getGitRemote() {
|
|
16
|
+
try {
|
|
17
|
+
return execSync('git remote get-url origin', {
|
|
18
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
19
|
+
timeout: 1000,
|
|
20
|
+
})
|
|
21
|
+
.toString()
|
|
22
|
+
.trim();
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadConfig() {
|
|
29
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
32
|
+
} catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function saveConfig(data) {
|
|
40
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
41
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
fs.writeFileSync(
|
|
44
|
+
CONFIG_FILE,
|
|
45
|
+
JSON.stringify(
|
|
46
|
+
{
|
|
47
|
+
...loadConfig(),
|
|
48
|
+
...data,
|
|
49
|
+
updatedAt: new Date().toISOString(),
|
|
50
|
+
},
|
|
51
|
+
null,
|
|
52
|
+
2
|
|
53
|
+
)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
switch (command) {
|
|
58
|
+
case 'showcase':
|
|
59
|
+
case 'register': {
|
|
60
|
+
const detected = getGitRemote();
|
|
61
|
+
const rl = readline.createInterface({
|
|
62
|
+
input: process.stdin,
|
|
63
|
+
output: process.stdout,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log('\n\x1b[36m✦ AETHER Community Showcase Registration\x1b[0m');
|
|
67
|
+
console.log('Share your project or repository using @thangnqdev/ui:\n');
|
|
68
|
+
|
|
69
|
+
rl.question(` Repository URL [${detected || 'none'}]: `, (repoUrl) => {
|
|
70
|
+
const finalRepo = repoUrl.trim() || detected || 'custom';
|
|
71
|
+
rl.question(' Project / Company Name (optional): ', (name) => {
|
|
72
|
+
saveConfig({
|
|
73
|
+
enabled: true,
|
|
74
|
+
repo: finalRepo,
|
|
75
|
+
projectName: name.trim() || undefined,
|
|
76
|
+
prompted: true,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
console.log('\n\x1b[32m✔ Successfully registered for AETHER Community Showcase!\x1b[0m');
|
|
80
|
+
console.log(` Repo: \x1b[33m${finalRepo}\x1b[0m\n`);
|
|
81
|
+
rl.close();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case 'telemetry': {
|
|
88
|
+
const sub = args[1] || 'status';
|
|
89
|
+
if (sub === 'enable') {
|
|
90
|
+
const remote = getGitRemote();
|
|
91
|
+
saveConfig({ enabled: true, repo: remote, prompted: true });
|
|
92
|
+
console.log('\x1b[32m✔ Repository sharing & showcase telemetry enabled.\x1b[0m');
|
|
93
|
+
} else if (sub === 'disable') {
|
|
94
|
+
saveConfig({ enabled: false, repo: null, prompted: true });
|
|
95
|
+
console.log('\x1b[90m✔ Repository sharing & showcase telemetry disabled.\x1b[0m');
|
|
96
|
+
} else {
|
|
97
|
+
const cfg = loadConfig();
|
|
98
|
+
console.log('\n\x1b[36m✦ AETHER Telemetry & Showcase Status\x1b[0m');
|
|
99
|
+
console.log(` Status: ${cfg.enabled ? '\x1b[32mEnabled (Sharing active)\x1b[0m' : '\x1b[90mDisabled (Anonymous)\x1b[0m'}`);
|
|
100
|
+
if (cfg.repo) console.log(` Repository: \x1b[33m${cfg.repo}\x1b[0m`);
|
|
101
|
+
console.log('\n Commands:');
|
|
102
|
+
console.log(' npx @thangnqdev/ui showcase # Register project for showcase');
|
|
103
|
+
console.log(' npx @thangnqdev/ui telemetry enable # Enable repo telemetry');
|
|
104
|
+
console.log(' npx @thangnqdev/ui telemetry disable # Disable repo telemetry\n');
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
case 'help':
|
|
110
|
+
default: {
|
|
111
|
+
console.log(`
|
|
112
|
+
\x1b[1m@thangnqdev/ui CLI\x1b[0m (AETHER System)
|
|
113
|
+
|
|
114
|
+
Usage:
|
|
115
|
+
npx @thangnqdev/ui <command>
|
|
116
|
+
|
|
117
|
+
Commands:
|
|
118
|
+
showcase Register repository for AETHER Community Showcase
|
|
119
|
+
telemetry status View repository sharing & telemetry status
|
|
120
|
+
telemetry enable Opt-in to share repository telemetry
|
|
121
|
+
telemetry disable Opt-out from repository telemetry
|
|
122
|
+
`);
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
package/llms.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @thangnqdev/ui — Full Architecture Component System Contract
|
|
2
2
|
|
|
3
3
|
> Machine-readable specification for AI Coding Agents (Claude Code, Antigravity, Cursor, Qwen, Codex).
|
|
4
|
-
> Package: `@thangnqdev/ui` (v0.2.
|
|
4
|
+
> Package: `@thangnqdev/ui` (v0.2.1)
|
|
5
5
|
> Total Registered SSOT Components: 145
|
|
6
6
|
> Aesthetic Core: Spatial Optics, Editorial Bento, and Enterprise-grade Accessible Primitives.
|
|
7
7
|
> Sub-module Exports: `core`, `layout`, `typography`, `buttons`, `forms`, `overlays`, `navigation`, `data-display`, `feedback`, `glass`, `bento`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thangnqdev/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Spatial Optics & Editorial Bento UI System with Machine-Readable AI Contracts",
|
|
6
6
|
"license": "MIT",
|
|
@@ -104,15 +104,22 @@
|
|
|
104
104
|
"./glass/styles.css": "./dist/glass/styles.css",
|
|
105
105
|
"./bento/styles.css": "./dist/bento/styles.css"
|
|
106
106
|
},
|
|
107
|
+
"bin": {
|
|
108
|
+
"@thangnqdev/ui": "./bin/cli.js",
|
|
109
|
+
"aether-ui": "./bin/cli.js"
|
|
110
|
+
},
|
|
107
111
|
"files": [
|
|
108
112
|
"dist",
|
|
109
113
|
"src",
|
|
114
|
+
"bin",
|
|
115
|
+
"scripts/postinstall.js",
|
|
110
116
|
"llms.txt",
|
|
111
117
|
"ui-contract.json",
|
|
112
118
|
"LICENSE"
|
|
113
119
|
],
|
|
114
120
|
"scripts": {
|
|
115
121
|
"dev": "vite",
|
|
122
|
+
"postinstall": "node scripts/postinstall.js",
|
|
116
123
|
"build:manifests": "node scripts/generate-manifests.js",
|
|
117
124
|
"build:css": "node scripts/build-css.js",
|
|
118
125
|
"build:ts": "tsup",
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import readline from 'node:readline';
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), '.thangnqdev-ui');
|
|
10
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'telemetry.json');
|
|
11
|
+
|
|
12
|
+
// 1. Silent bailouts for CI, non-interactive environments, and opt-out flags
|
|
13
|
+
if (
|
|
14
|
+
process.env.CI ||
|
|
15
|
+
process.env.CONTINUOUS_INTEGRATION ||
|
|
16
|
+
process.env.DO_NOT_TRACK === '1' ||
|
|
17
|
+
process.env.AETHER_TELEMETRY_DISABLED === '1' ||
|
|
18
|
+
!process.stdout.isTTY ||
|
|
19
|
+
!process.stdin.isTTY
|
|
20
|
+
) {
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 2. Check if user has already configured preference
|
|
25
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
26
|
+
try {
|
|
27
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
|
|
28
|
+
if (config.prompted) {
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// Ignore corrupted config
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getGitRemote() {
|
|
37
|
+
try {
|
|
38
|
+
const remote = execSync('git remote get-url origin', {
|
|
39
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
40
|
+
timeout: 1000,
|
|
41
|
+
})
|
|
42
|
+
.toString()
|
|
43
|
+
.trim();
|
|
44
|
+
return remote;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function saveConfig(data) {
|
|
51
|
+
try {
|
|
52
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
53
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
fs.writeFileSync(
|
|
56
|
+
CONFIG_FILE,
|
|
57
|
+
JSON.stringify(
|
|
58
|
+
{
|
|
59
|
+
...data,
|
|
60
|
+
updatedAt: new Date().toISOString(),
|
|
61
|
+
prompted: true,
|
|
62
|
+
},
|
|
63
|
+
null,
|
|
64
|
+
2
|
|
65
|
+
)
|
|
66
|
+
);
|
|
67
|
+
} catch {
|
|
68
|
+
// Ignore filesystem permissions error on read-only environments
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function promptUser() {
|
|
73
|
+
const detectedRepo = getGitRemote();
|
|
74
|
+
|
|
75
|
+
console.log('\n\x1b[36m╭────────────────────────────────────────────────────────────────────────╮\x1b[0m');
|
|
76
|
+
console.log('\x1b[36m│\x1b[0m \x1b[1m✦ Thanks for installing @thangnqdev/ui (AETHER Spatial UI)\x1b[0m \x1b[36m│\x1b[0m');
|
|
77
|
+
console.log('\x1b[36m│\x1b[0m \x1b[36m│\x1b[0m');
|
|
78
|
+
console.log('\x1b[36m│\x1b[0m Would you like to share your public repository info to be featured \x1b[36m│\x1b[0m');
|
|
79
|
+
console.log('\x1b[36m│\x1b[0m in the AETHER Community Showcase & project ecosystem? \x1b[36m│\x1b[0m');
|
|
80
|
+
if (detectedRepo) {
|
|
81
|
+
console.log(`\x1b[36m│\x1b[0m \x1b[90mDetected repo:\x1b[0m \x1b[32m${detectedRepo.slice(0, 52)}\x1b[0m \x1b[36m│\x1b[0m`);
|
|
82
|
+
}
|
|
83
|
+
console.log('\x1b[36m│\x1b[0m \x1b[36m│\x1b[0m');
|
|
84
|
+
console.log('\x1b[36m│\x1b[0m \x1b[1m[y]\x1b[0m Yes, share repo info \x1b[1m[n]\x1b[0m No, keep anonymous (Default: n) \x1b[36m│\x1b[0m');
|
|
85
|
+
console.log('\x1b[36m╰────────────────────────────────────────────────────────────────────────╯\x1b[0m');
|
|
86
|
+
|
|
87
|
+
const rl = readline.createInterface({
|
|
88
|
+
input: process.stdin,
|
|
89
|
+
output: process.stdout,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
// Timeout prompt after 15 seconds so installation never hangs
|
|
94
|
+
const timer = setTimeout(() => {
|
|
95
|
+
saveConfig({ enabled: false, repo: null });
|
|
96
|
+
rl.close();
|
|
97
|
+
resolve();
|
|
98
|
+
}, 15000);
|
|
99
|
+
|
|
100
|
+
rl.question(' \x1b[33mShare repository info? (y/N):\x1b[0m ', (answer) => {
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
const isYes = answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes';
|
|
103
|
+
|
|
104
|
+
if (isYes) {
|
|
105
|
+
saveConfig({
|
|
106
|
+
enabled: true,
|
|
107
|
+
repo: detectedRepo || 'manual-opt-in',
|
|
108
|
+
optedInAt: new Date().toISOString(),
|
|
109
|
+
});
|
|
110
|
+
console.log(' \x1b[32m✔ Thank you! Repository info shared with AETHER Community Showcase.\x1b[0m\n');
|
|
111
|
+
} else {
|
|
112
|
+
saveConfig({
|
|
113
|
+
enabled: false,
|
|
114
|
+
repo: null,
|
|
115
|
+
});
|
|
116
|
+
console.log(' \x1b[90m✔ Understood. Anonymous mode active. (Manage anytime via npx @thangnqdev/ui showcase)\x1b[0m\n');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
rl.close();
|
|
120
|
+
resolve();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
promptUser().catch(() => process.exit(0));
|