draftify-cli 1.0.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/README.md +47 -0
- package/dist/commands/login.js +70 -0
- package/dist/commands/refactor.js +64 -0
- package/dist/index.js +51 -0
- package/dist/repl.js +1102 -0
- package/dist/utils/api.js +106 -0
- package/dist/utils/chats.js +52 -0
- package/dist/utils/config.js +73 -0
- package/dist/utils/skills.js +45 -0
- package/dist/utils/ui.js +135 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Draftify CLI
|
|
2
|
+
|
|
3
|
+
Draftify CLI is a command-line interface for interacting with Draftify AI directly from your terminal. It allows you to seamlessly refactor files, chat with an AI assistant in your project, and much more.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
You can install the CLI globally using npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g draftify-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
### Getting Started
|
|
16
|
+
|
|
17
|
+
If you simply type `draftify` into your terminal, it will automatically initiate the login flow or start the interactive REPL if you are already authenticated.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
draftify
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Commands
|
|
24
|
+
|
|
25
|
+
- `draftify login`
|
|
26
|
+
Authenticates your terminal session with the Draftify Website. You will be redirected to the web browser to confirm login securely.
|
|
27
|
+
|
|
28
|
+
- `draftify refactor <file> -i <instruction>`
|
|
29
|
+
Refactor a specific file using Draftify AI based on the provided instruction.
|
|
30
|
+
|
|
31
|
+
**Example:**
|
|
32
|
+
```bash
|
|
33
|
+
draftify refactor src/index.ts -i "Add error handling to all promises"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Security & Privacy
|
|
37
|
+
|
|
38
|
+
The CLI is completely key-less. It communicates with the Draftify Website using short-lived session tokens. Your API keys remain securely on your local server/website configuration and are never exposed directly to the CLI or saved on the local filesystem of your projects.
|
|
39
|
+
|
|
40
|
+
## Development
|
|
41
|
+
|
|
42
|
+
To build the CLI locally:
|
|
43
|
+
|
|
44
|
+
1. Clone the repository
|
|
45
|
+
2. Run `npm install`
|
|
46
|
+
3. Run `npm run build`
|
|
47
|
+
4. You can link it locally using `npm link`
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loginCommand = loginCommand;
|
|
7
|
+
const open_1 = __importDefault(require("open"));
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const config_1 = require("../utils/config");
|
|
11
|
+
const ui_1 = require("../utils/ui");
|
|
12
|
+
const kleur_1 = require("kleur");
|
|
13
|
+
// Default Draftify URL, can be overridden by env
|
|
14
|
+
const DRAFTIFY_WEB_URL = process.env.DRAFTIFY_WEB_URL || "http://localhost:3000";
|
|
15
|
+
async function loginCommand() {
|
|
16
|
+
const code = crypto_1.default.randomBytes(4).toString("hex");
|
|
17
|
+
const authUrl = `${DRAFTIFY_WEB_URL}/auth/cli?code=${code}`;
|
|
18
|
+
ui_1.ui.blank();
|
|
19
|
+
ui_1.ui.box("Draftify Authentication", [
|
|
20
|
+
"To continue, we need to authenticate this device.",
|
|
21
|
+
"",
|
|
22
|
+
"If your browser doesn't open automatically, please visit:",
|
|
23
|
+
(0, kleur_1.bold)((0, kleur_1.yellow)(authUrl)),
|
|
24
|
+
"",
|
|
25
|
+
`Your device code is: ${(0, kleur_1.bold)(code)}`,
|
|
26
|
+
"",
|
|
27
|
+
(0, kleur_1.dim)("Waiting for authorization...")
|
|
28
|
+
]);
|
|
29
|
+
ui_1.ui.blank();
|
|
30
|
+
try {
|
|
31
|
+
await (0, open_1.default)(authUrl);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
// Ignore open errors, user can click the link manually
|
|
35
|
+
}
|
|
36
|
+
// Polling logic
|
|
37
|
+
const pollInterval = 2000; // 2 seconds
|
|
38
|
+
const maxAttempts = 150; // 5 minutes timeout
|
|
39
|
+
let attempts = 0;
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const intervalId = setInterval(async () => {
|
|
42
|
+
attempts++;
|
|
43
|
+
if (attempts > maxAttempts) {
|
|
44
|
+
clearInterval(intervalId);
|
|
45
|
+
ui_1.ui.error("Authentication timed out. Please try running '/login' again.");
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const response = await axios_1.default.post(`${DRAFTIFY_WEB_URL}/api/cli/poll`, { code });
|
|
50
|
+
if (response.data && response.data.status === "AUTHORIZED" && response.data.token) {
|
|
51
|
+
clearInterval(intervalId);
|
|
52
|
+
// Save the token
|
|
53
|
+
(0, config_1.saveConfig)({ token: response.data.token });
|
|
54
|
+
const username = response.data.username || "Developer";
|
|
55
|
+
resolve(username);
|
|
56
|
+
}
|
|
57
|
+
// If status is PENDING, we just continue polling
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
// Ignore network errors during polling (e.g. server restarting), just keep trying
|
|
61
|
+
// but if we get a persistent 400 error, we should exit.
|
|
62
|
+
if (err.response && err.response.status === 400) {
|
|
63
|
+
clearInterval(intervalId);
|
|
64
|
+
ui_1.ui.error("Invalid request during polling.");
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}, pollInterval);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.refactorCommand = refactorCommand;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const ui_1 = require("../utils/ui");
|
|
10
|
+
const config_1 = require("../utils/config");
|
|
11
|
+
const api_1 = require("../utils/api");
|
|
12
|
+
const MAX_FILE_SIZE_BYTES = 500 * 1024; // 500 KB
|
|
13
|
+
async function refactorCommand(file, options) {
|
|
14
|
+
ui_1.ui.header("Draftify CLI");
|
|
15
|
+
const token = (0, config_1.getToken)();
|
|
16
|
+
if (!token) {
|
|
17
|
+
ui_1.ui.error("No CLI token found. Please run 'draftify login' first.");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const instruction = options.instruction?.trim();
|
|
21
|
+
if (!instruction) {
|
|
22
|
+
ui_1.ui.error("Instruction is required. Use -i or --instruction flag.");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const absolutePath = path_1.default.resolve(process.cwd(), file);
|
|
26
|
+
// Validate File Exists
|
|
27
|
+
if (!fs_1.default.existsSync(absolutePath)) {
|
|
28
|
+
ui_1.ui.error(`File not found: ${absolutePath}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
// Validate File Size
|
|
32
|
+
const stat = fs_1.default.statSync(absolutePath);
|
|
33
|
+
if (stat.size > MAX_FILE_SIZE_BYTES) {
|
|
34
|
+
ui_1.ui.error(`File is too large (${Math.round(stat.size / 1024)} KB). Maximum allowed size is 500 KB.`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
ui_1.ui.step(`Target : ${path_1.default.basename(absolutePath)}`);
|
|
38
|
+
ui_1.ui.step(`Action : ${instruction}`);
|
|
39
|
+
ui_1.ui.blank();
|
|
40
|
+
let code;
|
|
41
|
+
try {
|
|
42
|
+
code = fs_1.default.readFileSync(absolutePath, "utf-8");
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
ui_1.ui.error(`Failed to read file: ${error.message}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
const spinner = (0, ui_1.createSpinner)("Analyzing and refactoring...").start();
|
|
49
|
+
try {
|
|
50
|
+
const result = await (0, api_1.refactorCodeApi)(path_1.default.basename(file), code, instruction, [], (0, config_1.getModel)(), [], // empty skills array
|
|
51
|
+
"Medium" // default thinking level
|
|
52
|
+
);
|
|
53
|
+
spinner.stop();
|
|
54
|
+
ui_1.ui.success("Refactoring complete");
|
|
55
|
+
ui_1.ui.divider();
|
|
56
|
+
console.log(result);
|
|
57
|
+
ui_1.ui.divider();
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
spinner.stop();
|
|
61
|
+
ui_1.ui.error(error.message);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const login_1 = require("./commands/login");
|
|
6
|
+
const refactor_1 = require("./commands/refactor");
|
|
7
|
+
const repl_1 = require("./repl");
|
|
8
|
+
const ui_1 = require("./utils/ui");
|
|
9
|
+
const config_1 = require("./utils/config");
|
|
10
|
+
const program = new commander_1.Command();
|
|
11
|
+
program
|
|
12
|
+
.name("draftify")
|
|
13
|
+
.description("Draftify AI Command Line Interface")
|
|
14
|
+
.version("1.0.0");
|
|
15
|
+
program
|
|
16
|
+
.command("login")
|
|
17
|
+
.description("Authenticate with your Draftify CLI token")
|
|
18
|
+
.action(async () => {
|
|
19
|
+
await (0, login_1.loginCommand)();
|
|
20
|
+
});
|
|
21
|
+
program
|
|
22
|
+
.command("refactor <file>")
|
|
23
|
+
.description("Refactor a specific file using Draftify AI")
|
|
24
|
+
.requiredOption("-i, --instruction <text>", "Instruction for the AI on how to refactor the file")
|
|
25
|
+
.action(async (file, options) => {
|
|
26
|
+
await (0, refactor_1.refactorCommand)(file, options);
|
|
27
|
+
});
|
|
28
|
+
// Handle unknown commands
|
|
29
|
+
program.on('command:*', function () {
|
|
30
|
+
ui_1.ui.error(`Invalid command: ${program.args.join(' ')}\nSee --help for a list of available commands.`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
// Custom logic for when `draftify` is run without any arguments
|
|
34
|
+
if (process.argv.slice(2).length === 0) {
|
|
35
|
+
const token = (0, config_1.getToken)();
|
|
36
|
+
if (!token) {
|
|
37
|
+
// No token, run interactive login flow
|
|
38
|
+
(0, login_1.loginCommand)().then((username) => (0, repl_1.startRepl)(username)).catch(() => process.exit(1));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Token exists, start REPL
|
|
42
|
+
(0, repl_1.startRepl)().catch((err) => {
|
|
43
|
+
ui_1.ui.error(`REPL crashed: ${err.message}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Otherwise, let commander handle the parsed args
|
|
50
|
+
program.parse(process.argv);
|
|
51
|
+
}
|