ds-01 0.1.5 → 1.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/.turbo/turbo-build.log +1 -1
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +60 -19
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +35 -29
- package/package.json +1 -1
- package/src/commands/add.ts +104 -25
- package/src/commands/login.ts +47 -36
- package/dist/command/login.d.ts +0 -3
- package/dist/command/login.d.ts.map +0 -1
- package/dist/command/login.js +0 -76
package/.turbo/turbo-build.log
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkDpC,eAAO,MAAM,GAAG,SAwGZ,CAAC"}
|
package/dist/commands/add.js
CHANGED
|
@@ -1,63 +1,104 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { intro, outro, spinner, cancel, note } from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
7
|
import { getToken } from "../utils/config.js";
|
|
8
|
-
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
8
|
+
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
9
|
+
// Helper to verify Tailwind exists before doing anything
|
|
10
|
+
function checkTailwindInstallation() {
|
|
11
|
+
const targetDir = process.cwd();
|
|
12
|
+
const pkgJsonPath = path.join(targetDir, "package.json");
|
|
13
|
+
// 1. Ensure they are in a valid Node.js project
|
|
14
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
15
|
+
cancel(pc.red("No package.json found. Please run this command inside a Node.js project."));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// 2. Read package.json dependencies
|
|
19
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
20
|
+
const allDeps = {
|
|
21
|
+
...pkgJson.dependencies,
|
|
22
|
+
...pkgJson.devDependencies,
|
|
23
|
+
};
|
|
24
|
+
// 3. Strict Tailwind Check
|
|
25
|
+
if (!allDeps["tailwindcss"]) {
|
|
26
|
+
cancel(pc.red("Tailwind CSS is missing from your project dependencies.\n") +
|
|
27
|
+
pc.gray("DS01 components rely strictly on Tailwind CSS for styling.\n\n") +
|
|
28
|
+
pc.white("Kindly install it and configure your project, then retry:\n") +
|
|
29
|
+
pc.cyan(" npm install tailwindcss @tailwindcss/postcss postcss\n\n") +
|
|
30
|
+
pc.gray("Official Setup Guide: ") +
|
|
31
|
+
pc.underline("https://tailwindcss.com/docs/installation"));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
9
35
|
export const add = new Command()
|
|
10
36
|
.name("add")
|
|
11
37
|
.description("Add a component from DS01 to your project")
|
|
12
38
|
.argument("<component>", "The name of the component (e.g., premium-section)")
|
|
13
39
|
.action(async (componentName) => {
|
|
40
|
+
console.log(); // Spacing for visual breathing room
|
|
41
|
+
// 1. Premium Header
|
|
42
|
+
intro(`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray("Adding Component")}`);
|
|
43
|
+
// 2. Run Pre-Flight Tailwind Check
|
|
44
|
+
checkTailwindInstallation();
|
|
14
45
|
const cwd = process.cwd();
|
|
15
46
|
const configPath = path.join(cwd, "ds01.config.json");
|
|
16
47
|
if (!fs.existsSync(configPath)) {
|
|
17
|
-
|
|
18
|
-
console.log(`Run ${chalk.cyan("npx @arpit2023/ds01 init")} first.`);
|
|
48
|
+
cancel(pc.red("ds01.config.json not found. Run 'npx ds-01 init' first."));
|
|
19
49
|
process.exit(1);
|
|
20
50
|
}
|
|
21
51
|
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
22
52
|
const token = getToken();
|
|
23
|
-
const
|
|
53
|
+
const s = spinner();
|
|
54
|
+
s.start(`Fetching ${pc.cyan(`<${componentName} />`)} from registry...`);
|
|
24
55
|
try {
|
|
56
|
+
// 3. Fetch component from your API
|
|
25
57
|
const response = await fetch(`${API_BASE_URL}/api/registry/${componentName}`, {
|
|
26
|
-
headers: {
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Bearer ${config.token}`,
|
|
60
|
+
"X-Machine-ID": config.machineId, // <-- ADD THIS LINE
|
|
61
|
+
},
|
|
27
62
|
});
|
|
28
63
|
if (!response.ok) {
|
|
29
|
-
|
|
64
|
+
s.stop(pc.red("Component fetch failed."));
|
|
65
|
+
cancel(`Component not found or server error (HTTP ${response.status})`);
|
|
66
|
+
process.exit(1);
|
|
30
67
|
}
|
|
31
68
|
const componentData = await response.json();
|
|
32
|
-
// Create
|
|
69
|
+
// 4. Create dedicated component folder
|
|
33
70
|
const targetDir = path.join(cwd, config.componentsPath, componentName);
|
|
34
71
|
if (!fs.existsSync(targetDir)) {
|
|
35
72
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
36
73
|
}
|
|
37
|
-
//
|
|
74
|
+
// 5. Inject the pure code files
|
|
38
75
|
for (const file of componentData.files) {
|
|
39
76
|
const filePath = path.join(targetDir, file.name);
|
|
40
77
|
fs.writeFileSync(filePath, file.content);
|
|
41
78
|
}
|
|
42
|
-
|
|
43
|
-
// Auto-install
|
|
79
|
+
s.stop(pc.green(`Downloaded ${componentData.files.length} files into ${pc.white(`/${config.componentsPath}/${componentName}`)}`));
|
|
80
|
+
// 6. Auto-install Missing Component Dependencies (e.g., framer-motion)
|
|
44
81
|
if (componentData.dependencies && componentData.dependencies.length > 0) {
|
|
45
82
|
const depsToInstall = componentData.dependencies.join(" ");
|
|
46
|
-
|
|
83
|
+
s.start(`Installing missing dependencies: ${pc.cyan(depsToInstall)}...`);
|
|
47
84
|
try {
|
|
85
|
+
// Silently runs the npm install command in the background
|
|
48
86
|
execSync(`npm install ${depsToInstall}`, { stdio: "ignore" });
|
|
49
|
-
|
|
87
|
+
s.stop(pc.green(`Dependencies installed successfully: ${pc.gray(depsToInstall)}`));
|
|
50
88
|
}
|
|
51
89
|
catch (error) {
|
|
52
|
-
|
|
90
|
+
s.stop(pc.red("Failed to auto-install dependencies."));
|
|
91
|
+
note(`Please run: ${pc.cyan(`npm install ${depsToInstall}`)} manually.`, "Manual Action Required");
|
|
53
92
|
}
|
|
54
93
|
}
|
|
55
|
-
|
|
56
|
-
|
|
94
|
+
// 7. Clean Success Outro
|
|
95
|
+
outro(`${pc.white("✔")} ${pc.bold(`Component <${componentName} /> is ready!`)}\n` +
|
|
96
|
+
pc.gray(`Import it: `) +
|
|
97
|
+
pc.cyan(`import { Section } from "@/${config.componentsPath}/${componentName}/Section"`));
|
|
57
98
|
}
|
|
58
99
|
catch (error) {
|
|
59
|
-
|
|
60
|
-
|
|
100
|
+
s.stop(pc.red("An error occurred during injection."));
|
|
101
|
+
cancel(pc.gray(error.message || "Unknown CLI Error"));
|
|
61
102
|
process.exit(1);
|
|
62
103
|
}
|
|
63
104
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,eAAO,MAAM,KAAK,SA4Fd,CAAC"}
|
package/dist/commands/login.js
CHANGED
|
@@ -1,43 +1,49 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { intro, outro, spinner, note, cancel } from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
4
|
import open from "open";
|
|
5
5
|
import machineIdPkg from "node-machine-id";
|
|
6
|
-
import { saveToken } from "../utils/config.js";
|
|
7
|
-
// ⚠️ In production, replace with your actual deployed Vercel domain
|
|
6
|
+
import { saveToken } from "../utils/config.js";
|
|
8
7
|
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
9
8
|
const { machineIdSync } = machineIdPkg;
|
|
10
9
|
export const login = new Command()
|
|
11
10
|
.name("login")
|
|
12
11
|
.description("Authenticate your terminal with DS01 via browser")
|
|
13
12
|
.action(async () => {
|
|
14
|
-
|
|
13
|
+
console.log(); // Spacing for breathing room
|
|
14
|
+
// 1. Header banner
|
|
15
|
+
intro(`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray("v1.0.0 — Terminal Authentication")}`);
|
|
16
|
+
const s = spinner();
|
|
15
17
|
try {
|
|
16
|
-
//
|
|
18
|
+
// 2. Hardware ID Generation & Device Code Request
|
|
19
|
+
s.start("Generating hardware fingerprint & requesting pairing code...");
|
|
17
20
|
const hardwareId = machineIdSync();
|
|
18
|
-
// 2. Request a new Device Code from the Next.js API
|
|
19
21
|
const initResponse = await fetch(`${API_BASE_URL}/api/auth/device/code`, {
|
|
20
22
|
method: "POST",
|
|
21
|
-
headers: {
|
|
22
|
-
"Content-Type": "application/json",
|
|
23
|
-
},
|
|
23
|
+
headers: { "Content-Type": "application/json" },
|
|
24
24
|
body: JSON.stringify({ machineId: hardwareId }),
|
|
25
25
|
});
|
|
26
26
|
if (!initResponse.ok) {
|
|
27
|
-
|
|
27
|
+
s.stop(pc.red("Failed to reach DS01 authorization server."));
|
|
28
|
+
cancel(`Server responded with HTTP ${initResponse.status}`);
|
|
29
|
+
process.exit(1);
|
|
28
30
|
}
|
|
29
31
|
const { deviceCode, userCode, verificationUrl, interval } = await initResponse.json();
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
s.stop(pc.green("Device authorization code generated."));
|
|
33
|
+
// 3. Highlighted Box for User Action
|
|
34
|
+
note(`${pc.bold("Pairing Code:")} ${pc.cyan(pc.bold(` ${userCode} `))}\n` +
|
|
35
|
+
`${pc.bold("Verification URL:")} ${pc.underline(verificationUrl)}`, "Action Required");
|
|
36
|
+
// 4. Auto-launch Browser
|
|
37
|
+
try {
|
|
38
|
+
await open(verificationUrl);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Non-blocking fallback if browser launch fails on headless setups
|
|
42
|
+
}
|
|
43
|
+
// 5. Polling Loop
|
|
44
|
+
s.start("Waiting for web authorization...");
|
|
39
45
|
let token = null;
|
|
40
|
-
const pollInterval = (interval || 5) * 1000;
|
|
46
|
+
const pollInterval = (interval || 5) * 1000;
|
|
41
47
|
while (!token) {
|
|
42
48
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
43
49
|
const tokenResponse = await fetch(`${API_BASE_URL}/api/auth/device/token`, {
|
|
@@ -50,20 +56,20 @@ export const login = new Command()
|
|
|
50
56
|
token = tokenData.accessToken;
|
|
51
57
|
}
|
|
52
58
|
else if (tokenData.error !== "authorization_pending") {
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
s.stop(pc.red("Authorization failed or expired."));
|
|
60
|
+
cancel(`Reason: ${tokenData.error || "Unknown authorization error"}`);
|
|
55
61
|
process.exit(1);
|
|
56
62
|
}
|
|
57
63
|
}
|
|
58
|
-
//
|
|
59
|
-
// We pass the token and hardwareId into the wallet.
|
|
64
|
+
// 6. Save token to local wallet
|
|
60
65
|
saveToken(token, hardwareId);
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
s.stop(pc.green("Hardware signature linked & token verified!"));
|
|
67
|
+
// 7. Clean Success Outro
|
|
68
|
+
outro(`${pc.white("✔")} ${pc.bold("Terminal synced successfully")} ${pc.gray("Session token saved locally.")}`);
|
|
63
69
|
}
|
|
64
70
|
catch (error) {
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
s.stop(pc.red("An error occurred during login."));
|
|
72
|
+
cancel(pc.gray(error.message || "Unknown CLI Error"));
|
|
67
73
|
process.exit(1);
|
|
68
74
|
}
|
|
69
75
|
});
|
package/package.json
CHANGED
package/src/commands/add.ts
CHANGED
|
@@ -1,76 +1,155 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { intro, outro, spinner, cancel, note } from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { execSync } from "child_process";
|
|
7
7
|
import { getToken } from "../utils/config.js";
|
|
8
8
|
|
|
9
|
-
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
9
|
+
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
10
|
+
|
|
11
|
+
// Helper to verify Tailwind exists before doing anything
|
|
12
|
+
function checkTailwindInstallation() {
|
|
13
|
+
const targetDir = process.cwd();
|
|
14
|
+
const pkgJsonPath = path.join(targetDir, "package.json");
|
|
15
|
+
|
|
16
|
+
// 1. Ensure they are in a valid Node.js project
|
|
17
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
18
|
+
cancel(
|
|
19
|
+
pc.red(
|
|
20
|
+
"No package.json found. Please run this command inside a Node.js project.",
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 2. Read package.json dependencies
|
|
27
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
|
|
28
|
+
const allDeps = {
|
|
29
|
+
...pkgJson.dependencies,
|
|
30
|
+
...pkgJson.devDependencies,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// 3. Strict Tailwind Check
|
|
34
|
+
if (!allDeps["tailwindcss"]) {
|
|
35
|
+
cancel(
|
|
36
|
+
pc.red("Tailwind CSS is missing from your project dependencies.\n") +
|
|
37
|
+
pc.gray(
|
|
38
|
+
"DS01 components rely strictly on Tailwind CSS for styling.\n\n",
|
|
39
|
+
) +
|
|
40
|
+
pc.white(
|
|
41
|
+
"Kindly install it and configure your project, then retry:\n",
|
|
42
|
+
) +
|
|
43
|
+
pc.cyan(" npm install tailwindcss @tailwindcss/postcss postcss\n\n") +
|
|
44
|
+
pc.gray("Official Setup Guide: ") +
|
|
45
|
+
pc.underline("https://tailwindcss.com/docs/installation"),
|
|
46
|
+
);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
10
50
|
|
|
11
51
|
export const add = new Command()
|
|
12
52
|
.name("add")
|
|
13
53
|
.description("Add a component from DS01 to your project")
|
|
14
54
|
.argument("<component>", "The name of the component (e.g., premium-section)")
|
|
15
55
|
.action(async (componentName: string) => {
|
|
56
|
+
console.log(); // Spacing for visual breathing room
|
|
57
|
+
|
|
58
|
+
// 1. Premium Header
|
|
59
|
+
intro(
|
|
60
|
+
`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray("Adding Component")}`,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// 2. Run Pre-Flight Tailwind Check
|
|
64
|
+
checkTailwindInstallation();
|
|
65
|
+
|
|
16
66
|
const cwd = process.cwd();
|
|
17
67
|
const configPath = path.join(cwd, "ds01.config.json");
|
|
18
68
|
|
|
19
69
|
if (!fs.existsSync(configPath)) {
|
|
20
|
-
|
|
21
|
-
console.log(`Run ${chalk.cyan("npx @arpit2023/ds01 init")} first.`);
|
|
70
|
+
cancel(pc.red("ds01.config.json not found. Run 'npx ds-01 init' first."));
|
|
22
71
|
process.exit(1);
|
|
23
72
|
}
|
|
24
73
|
|
|
25
74
|
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
26
75
|
const token = getToken();
|
|
27
76
|
|
|
28
|
-
const
|
|
77
|
+
const s = spinner();
|
|
78
|
+
s.start(`Fetching ${pc.cyan(`<${componentName} />`)} from registry...`);
|
|
29
79
|
|
|
30
80
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
81
|
+
// 3. Fetch component from your API
|
|
82
|
+
const response = await fetch(
|
|
83
|
+
`${API_BASE_URL}/api/registry/${componentName}`,
|
|
84
|
+
{
|
|
85
|
+
headers: {
|
|
86
|
+
Authorization: `Bearer ${config.token}`,
|
|
87
|
+
"X-Machine-ID": config.machineId, // <-- ADD THIS LINE
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
);
|
|
34
91
|
|
|
35
92
|
if (!response.ok) {
|
|
36
|
-
|
|
93
|
+
s.stop(pc.red("Component fetch failed."));
|
|
94
|
+
cancel(`Component not found or server error (HTTP ${response.status})`);
|
|
95
|
+
process.exit(1);
|
|
37
96
|
}
|
|
38
97
|
|
|
39
98
|
const componentData = await response.json();
|
|
40
99
|
|
|
41
|
-
// Create
|
|
100
|
+
// 4. Create dedicated component folder
|
|
42
101
|
const targetDir = path.join(cwd, config.componentsPath, componentName);
|
|
43
102
|
if (!fs.existsSync(targetDir)) {
|
|
44
103
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
45
104
|
}
|
|
46
105
|
|
|
47
|
-
//
|
|
106
|
+
// 5. Inject the pure code files
|
|
48
107
|
for (const file of componentData.files) {
|
|
49
108
|
const filePath = path.join(targetDir, file.name);
|
|
50
109
|
fs.writeFileSync(filePath, file.content);
|
|
51
110
|
}
|
|
52
|
-
|
|
53
|
-
spinner.succeed(chalk.green(`Downloaded ${componentData.files.length} files for ${componentName}`));
|
|
54
111
|
|
|
55
|
-
|
|
112
|
+
s.stop(
|
|
113
|
+
pc.green(
|
|
114
|
+
`Downloaded ${componentData.files.length} files into ${pc.white(`/${config.componentsPath}/${componentName}`)}`,
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// 6. Auto-install Missing Component Dependencies (e.g., framer-motion)
|
|
56
119
|
if (componentData.dependencies && componentData.dependencies.length > 0) {
|
|
57
120
|
const depsToInstall = componentData.dependencies.join(" ");
|
|
58
|
-
|
|
59
|
-
|
|
121
|
+
s.start(
|
|
122
|
+
`Installing missing dependencies: ${pc.cyan(depsToInstall)}...`,
|
|
123
|
+
);
|
|
124
|
+
|
|
60
125
|
try {
|
|
126
|
+
// Silently runs the npm install command in the background
|
|
61
127
|
execSync(`npm install ${depsToInstall}`, { stdio: "ignore" });
|
|
62
|
-
|
|
128
|
+
s.stop(
|
|
129
|
+
pc.green(
|
|
130
|
+
`Dependencies installed successfully: ${pc.gray(depsToInstall)}`,
|
|
131
|
+
),
|
|
132
|
+
);
|
|
63
133
|
} catch (error) {
|
|
64
|
-
|
|
134
|
+
s.stop(pc.red("Failed to auto-install dependencies."));
|
|
135
|
+
note(
|
|
136
|
+
`Please run: ${pc.cyan(`npm install ${depsToInstall}`)} manually.`,
|
|
137
|
+
"Manual Action Required",
|
|
138
|
+
);
|
|
65
139
|
}
|
|
66
140
|
}
|
|
67
141
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
142
|
+
// 7. Clean Success Outro
|
|
143
|
+
outro(
|
|
144
|
+
`${pc.white("✔")} ${pc.bold(`Component <${componentName} /> is ready!`)}\n` +
|
|
145
|
+
pc.gray(`Import it: `) +
|
|
146
|
+
pc.cyan(
|
|
147
|
+
`import { Section } from "@/${config.componentsPath}/${componentName}/Section"`,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
71
150
|
} catch (error: any) {
|
|
72
|
-
|
|
73
|
-
|
|
151
|
+
s.stop(pc.red("An error occurred during injection."));
|
|
152
|
+
cancel(pc.gray(error.message || "Unknown CLI Error"));
|
|
74
153
|
process.exit(1);
|
|
75
154
|
}
|
|
76
|
-
});
|
|
155
|
+
});
|
package/src/commands/login.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { intro, outro, spinner, note, cancel } from "@clack/prompts";
|
|
3
|
+
import pc from "picocolors";
|
|
4
4
|
import open from "open";
|
|
5
5
|
import machineIdPkg from "node-machine-id";
|
|
6
|
-
import { saveToken } from "../utils/config.js";
|
|
6
|
+
import { saveToken } from "../utils/config.js";
|
|
7
7
|
|
|
8
|
-
// ⚠️ In production, replace with your actual deployed Vercel domain
|
|
9
8
|
const API_BASE_URL = process.env.DS01_API_URL || "https://ds-01.vercel.app";
|
|
10
9
|
const { machineIdSync } = machineIdPkg;
|
|
11
10
|
|
|
@@ -13,43 +12,56 @@ export const login = new Command()
|
|
|
13
12
|
.name("login")
|
|
14
13
|
.description("Authenticate your terminal with DS01 via browser")
|
|
15
14
|
.action(async () => {
|
|
16
|
-
|
|
15
|
+
console.log(); // Spacing for breathing room
|
|
16
|
+
|
|
17
|
+
// 1. Header banner
|
|
18
|
+
intro(
|
|
19
|
+
`${pc.bgWhite(pc.black(pc.bold(" DS01 ")))} ${pc.gray("v1.0.0 — Terminal Authentication")}`
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const s = spinner();
|
|
17
23
|
|
|
18
24
|
try {
|
|
19
|
-
//
|
|
25
|
+
// 2. Hardware ID Generation & Device Code Request
|
|
26
|
+
s.start("Generating hardware fingerprint & requesting pairing code...");
|
|
20
27
|
const hardwareId = machineIdSync();
|
|
21
28
|
|
|
22
|
-
// 2. Request a new Device Code from the Next.js API
|
|
23
29
|
const initResponse = await fetch(`${API_BASE_URL}/api/auth/device/code`, {
|
|
24
30
|
method: "POST",
|
|
25
|
-
headers: {
|
|
26
|
-
|
|
27
|
-
},
|
|
28
|
-
body: JSON.stringify({ machineId: hardwareId }),
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
body: JSON.stringify({ machineId: hardwareId }),
|
|
29
33
|
});
|
|
30
34
|
|
|
31
35
|
if (!initResponse.ok) {
|
|
32
|
-
|
|
36
|
+
s.stop(pc.red("Failed to reach DS01 authorization server."));
|
|
37
|
+
cancel(`Server responded with HTTP ${initResponse.status}`);
|
|
38
|
+
process.exit(1);
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
const { deviceCode, userCode, verificationUrl, interval } =
|
|
36
42
|
await initResponse.json();
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
s.stop(pc.green("Device authorization code generated."));
|
|
45
|
+
|
|
46
|
+
// 3. Highlighted Box for User Action
|
|
47
|
+
note(
|
|
48
|
+
`${pc.bold("Pairing Code:")} ${pc.cyan(pc.bold(` ${userCode} `))}\n` +
|
|
49
|
+
`${pc.bold("Verification URL:")} ${pc.underline(verificationUrl)}`,
|
|
50
|
+
"Action Required"
|
|
42
51
|
);
|
|
43
|
-
console.log(`If your browser does not open automatically, visit:`);
|
|
44
|
-
console.log(chalk.underline.blue(verificationUrl) + "\n");
|
|
45
52
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
53
|
+
// 4. Auto-launch Browser
|
|
54
|
+
try {
|
|
55
|
+
await open(verificationUrl);
|
|
56
|
+
} catch {
|
|
57
|
+
// Non-blocking fallback if browser launch fails on headless setups
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 5. Polling Loop
|
|
61
|
+
s.start("Waiting for web authorization...");
|
|
49
62
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const pollInterval = (interval || 5) * 1000; // Updated to match our 5s secure interval
|
|
63
|
+
let token: string | null = null;
|
|
64
|
+
const pollInterval = (interval || 5) * 1000;
|
|
53
65
|
|
|
54
66
|
while (!token) {
|
|
55
67
|
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
@@ -60,7 +72,7 @@ export const login = new Command()
|
|
|
60
72
|
method: "POST",
|
|
61
73
|
headers: { "Content-Type": "application/json" },
|
|
62
74
|
body: JSON.stringify({ deviceCode, machineId: hardwareId }),
|
|
63
|
-
}
|
|
75
|
+
}
|
|
64
76
|
);
|
|
65
77
|
|
|
66
78
|
const tokenData = await tokenResponse.json();
|
|
@@ -68,25 +80,24 @@ export const login = new Command()
|
|
|
68
80
|
if (tokenResponse.ok && tokenData.accessToken) {
|
|
69
81
|
token = tokenData.accessToken;
|
|
70
82
|
} else if (tokenData.error !== "authorization_pending") {
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
s.stop(pc.red("Authorization failed or expired."));
|
|
84
|
+
cancel(`Reason: ${tokenData.error || "Unknown authorization error"}`);
|
|
73
85
|
process.exit(1);
|
|
74
86
|
}
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
//
|
|
78
|
-
// We pass the token and hardwareId into the wallet.
|
|
89
|
+
// 6. Save token to local wallet
|
|
79
90
|
saveToken(token, hardwareId);
|
|
80
91
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
92
|
+
s.stop(pc.green("Hardware signature linked & token verified!"));
|
|
93
|
+
|
|
94
|
+
// 7. Clean Success Outro
|
|
95
|
+
outro(
|
|
96
|
+
`${pc.white("✔")} ${pc.bold("Terminal synced successfully")} ${pc.gray("Session token saved locally.")}`
|
|
86
97
|
);
|
|
87
98
|
} catch (error: any) {
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
s.stop(pc.red("An error occurred during login."));
|
|
100
|
+
cancel(pc.gray(error.message || "Unknown CLI Error"));
|
|
90
101
|
process.exit(1);
|
|
91
102
|
}
|
|
92
103
|
});
|
package/dist/command/login.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/command/login.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,eAAO,MAAM,KAAK,SA4Fd,CAAC"}
|
package/dist/command/login.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import ora from "ora";
|
|
3
|
-
import chalk from "chalk";
|
|
4
|
-
import open from "open";
|
|
5
|
-
import { machineIdSync } from "node-machine-id";
|
|
6
|
-
import fs from "fs";
|
|
7
|
-
import path from "path";
|
|
8
|
-
import os from "os";
|
|
9
|
-
// ⚠️ In production, replace with your actual deployed Vercel domain
|
|
10
|
-
const API_BASE_URL = process.env.DS01_API_URL || "http://localhost:3000";
|
|
11
|
-
const CONFIG_DIR = path.join(os.homedir(), ".ds01");
|
|
12
|
-
const AUTH_FILE = path.join(CONFIG_DIR, "auth.json");
|
|
13
|
-
export const login = new Command()
|
|
14
|
-
.name("login")
|
|
15
|
-
.description("Authenticate your terminal with DS01 via browser")
|
|
16
|
-
.action(async () => {
|
|
17
|
-
const spinner = ora("Initializing secure login flow...").start();
|
|
18
|
-
try {
|
|
19
|
-
// 1. Generate Hardware Fingerprint Hash (MAC/CPU bound)
|
|
20
|
-
const hardwareId = machineIdSync();
|
|
21
|
-
// 2. Request a new Device Code from the Next.js API
|
|
22
|
-
const initResponse = await fetch(`${API_BASE_URL}/api/auth/device/code`, {
|
|
23
|
-
method: "POST",
|
|
24
|
-
});
|
|
25
|
-
if (!initResponse.ok) {
|
|
26
|
-
throw new Error("Failed to reach DS01 authorization server.");
|
|
27
|
-
}
|
|
28
|
-
const { deviceCode, userCode, verificationUrl, interval } = await initResponse.json();
|
|
29
|
-
spinner.stop();
|
|
30
|
-
console.log("\n" + chalk.cyan.bold("=== DS01 Secure Terminal Login ==="));
|
|
31
|
-
console.log(`\nYour device pairing code is: ${chalk.bgCyan.black.bold(` ${userCode} `)}\n`);
|
|
32
|
-
console.log(`If your browser does not open automatically, visit:`);
|
|
33
|
-
console.log(chalk.underline.blue(verificationUrl) + "\n");
|
|
34
|
-
// 3. Automatically open the user's default browser
|
|
35
|
-
await open(verificationUrl);
|
|
36
|
-
spinner.start("Waiting for web approval...");
|
|
37
|
-
// 4. Poll the Token Endpoint
|
|
38
|
-
let token = null;
|
|
39
|
-
const pollInterval = (interval || 2) * 1000;
|
|
40
|
-
while (!token) {
|
|
41
|
-
// Wait before polling again
|
|
42
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
43
|
-
const tokenResponse = await fetch(`${API_BASE_URL}/api/auth/device/token`, {
|
|
44
|
-
method: "POST",
|
|
45
|
-
headers: { "Content-Type": "application/json" },
|
|
46
|
-
body: JSON.stringify({ deviceCode, machineId: hardwareId }),
|
|
47
|
-
});
|
|
48
|
-
const tokenData = await tokenResponse.json();
|
|
49
|
-
if (tokenResponse.ok && tokenData.accessToken) {
|
|
50
|
-
token = tokenData.accessToken;
|
|
51
|
-
}
|
|
52
|
-
else if (tokenData.error !== "authorization_pending") {
|
|
53
|
-
// If error is anything OTHER than pending, fail out.
|
|
54
|
-
spinner.fail(chalk.red("Authorization failed or expired."));
|
|
55
|
-
console.error(chalk.red(`Reason: ${tokenData.error}`));
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
// 5. Save Token & Machine ID to ~/.ds01/auth.json
|
|
60
|
-
if (!fs.existsSync(CONFIG_DIR)) {
|
|
61
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
62
|
-
}
|
|
63
|
-
fs.writeFileSync(AUTH_FILE, JSON.stringify({
|
|
64
|
-
token,
|
|
65
|
-
machineId: hardwareId,
|
|
66
|
-
updatedAt: new Date().toISOString(),
|
|
67
|
-
}, null, 2));
|
|
68
|
-
spinner.succeed(chalk.green.bold("Terminal paired successfully!"));
|
|
69
|
-
console.log(chalk.gray(`Session token bound to hardware signature and saved locally.`));
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
spinner.fail(chalk.red("An error occurred during login."));
|
|
73
|
-
console.error(chalk.red(error.message));
|
|
74
|
-
process.exit(1);
|
|
75
|
-
}
|
|
76
|
-
});
|