virtualfile-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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sudesh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,222 @@
1
+ # ViewFile
2
+
3
+ ViewFile is a command-line interface (CLI) application that allows you to create, read, edit, and delete text files stored in the cloud. It provides a simple way to access your notes or text content from any machine without relying on local storage.
4
+
5
+ The project consists of a Node.js CLI client and a REST API backend, making it lightweight, fast, and easy to use from the terminal.
6
+
7
+ ---
8
+
9
+ ## Features
10
+
11
+ - Create cloud-based text files
12
+ - Read files directly from the terminal
13
+ - Edit existing files using your default text editor
14
+ - Delete files
15
+ - Simple and intuitive command-line interface
16
+ - Cross-platform support (Windows and Linux)
17
+ - REST API powered backend
18
+ - Global CLI installation via npm
19
+
20
+ ---
21
+
22
+ ## Installation
23
+
24
+ ### Prerequisites
25
+
26
+ - Node.js 18 or later
27
+ - npm
28
+
29
+ ### Install globally
30
+
31
+ ```bash
32
+ npm install -g viewfile
33
+ ```
34
+
35
+ Verify the installation:
36
+
37
+ ```bash
38
+ vf --version
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Usage
44
+
45
+ ### Create a file
46
+
47
+ ```bash
48
+ vf -w <filename>
49
+ ```
50
+
51
+ Example:
52
+
53
+ ```bash
54
+ vf -w notes
55
+ ```
56
+
57
+ This creates a new cloud file and opens your default editor to write its contents.
58
+
59
+ ---
60
+
61
+ ### Read a file
62
+
63
+ ```bash
64
+ vf -r <filename>
65
+ ```
66
+
67
+ Example:
68
+
69
+ ```bash
70
+ vf -r notes
71
+ ```
72
+
73
+ Displays the contents of the specified file.
74
+
75
+ ---
76
+
77
+ ### Edit a file
78
+
79
+ ```bash
80
+ vf -e <filename>
81
+ ```
82
+
83
+ Example:
84
+
85
+ ```bash
86
+ vf -e notes
87
+ ```
88
+
89
+ Opens the existing file in your default editor. Changes are automatically saved back to the cloud.
90
+
91
+ ---
92
+
93
+ ### Delete a file
94
+
95
+ ```bash
96
+ vf -rm <filename>
97
+ ```
98
+
99
+ Example:
100
+
101
+ ```bash
102
+ vf -rm notes
103
+ ```
104
+
105
+ Deletes the specified file from the server.
106
+
107
+ ---
108
+
109
+ ## Command Reference
110
+
111
+ | Command | Description |
112
+ |---------|-------------|
113
+ | `vf -w <filename>` | Create a new file |
114
+ | `vf -r <filename>` | Read an existing file |
115
+ | `vf -e <filename>` | Edit an existing file |
116
+ | `vf -rm <filename>` | Delete a file |
117
+ | `vf --help` | Display help information |
118
+ | `vf --version` | Display the installed version |
119
+
120
+ ---
121
+
122
+ ## Example Workflow
123
+
124
+ Create a file:
125
+
126
+ ```bash
127
+ vf -w todo
128
+ ```
129
+
130
+ Read the file:
131
+
132
+ ```bash
133
+ vf -r todo
134
+ ```
135
+
136
+ Edit the file:
137
+
138
+ ```bash
139
+ vf -e todo
140
+ ```
141
+
142
+ Delete the file:
143
+
144
+ ```bash
145
+ vf -rm todo
146
+ ```
147
+
148
+ ---
149
+
150
+ ## Use Cases
151
+
152
+ - Store quick notes accessible from anywhere
153
+ - Maintain terminal-based documentation
154
+ - Save code snippets
155
+ - Store TODO lists
156
+ - Keep configuration notes
157
+ - Manage text without depending on local files
158
+
159
+ ---
160
+
161
+ ## Technology Stack
162
+
163
+ ### Client
164
+
165
+ - Node.js
166
+ - Axios
167
+ - Chalk
168
+ - Dotenv
169
+
170
+ ### Server
171
+
172
+ - Node.js
173
+ - Express.js
174
+ - MongoDB Atlas
175
+ - Mongoose
176
+
177
+ ### Deployment
178
+
179
+ - Render
180
+
181
+ ---
182
+
183
+ ## Project Structure
184
+
185
+ ```
186
+ ViewFile/
187
+ ├── client/
188
+ │ ├── commands/
189
+ │ ├── services/
190
+ │ ├── utils/
191
+ │ └── index.js
192
+
193
+ ├── server/
194
+ │ ├── controllers/
195
+ │ ├── models/
196
+ │ ├── routes/
197
+ │ ├── config/
198
+ │ └── app.js
199
+
200
+ ├── package.json
201
+ └── README.md
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Requirements
207
+
208
+ - Node.js 18+
209
+ - Internet connection
210
+ - npm
211
+
212
+ ---
213
+
214
+ ## License
215
+
216
+ This project is licensed under the MIT License.
217
+
218
+ ---
219
+
220
+ ## Author
221
+
222
+ Developed by Sudesh.
@@ -0,0 +1,21 @@
1
+ const { deleteFile } = require("../services/api");
2
+ const logger = require("../utils/logger");
3
+
4
+ async function remove(filename) {
5
+ if (!filename) {
6
+ logger.error("Usage: vf -rm <filename>");
7
+ return;
8
+ }
9
+
10
+ try {
11
+ logger.info(`Deleting "${filename}"...`);
12
+
13
+ await deleteFile(filename);
14
+
15
+ logger.success("File deleted successfully.");
16
+ } catch (err) {
17
+ logger.error(err.response?.data?.message || err.message);
18
+ }
19
+ }
20
+
21
+ module.exports = remove;
@@ -0,0 +1,35 @@
1
+ const openEditor = require("../utils/editor");
2
+ const {
3
+ getFile,
4
+ updateFile,
5
+ } = require("../services/api");
6
+ const logger = require("../utils/logger");
7
+
8
+ async function edit(filename) {
9
+
10
+ if (!filename) {
11
+ logger.error("Usage: vf -e <filename>");
12
+ return;
13
+ }
14
+
15
+ try {
16
+
17
+ const file = await getFile(filename);
18
+
19
+ logger.info("Opening editor...");
20
+
21
+ const updatedContent = await openEditor(
22
+ filename,
23
+ file.content
24
+ );
25
+
26
+ await updateFile(filename, updatedContent);
27
+
28
+ logger.success("File updated successfully.");
29
+
30
+ } catch (err) {
31
+ logger.error(err.response?.data?.message || err.message);
32
+ }
33
+ }
34
+
35
+ module.exports = edit;
@@ -0,0 +1,26 @@
1
+ const chalk = require("chalk");
2
+ const { listFiles } = require("../services/api");
3
+ const logger = require("../utils/logger");
4
+
5
+ async function list() {
6
+ try {
7
+ const files = await listFiles();
8
+
9
+ if (files.length === 0) {
10
+ logger.warning("No files found.");
11
+ return;
12
+ }
13
+
14
+ logger.title(`Files (${files.length})`);
15
+ console.log(chalk.blue("─".repeat(60)));
16
+
17
+ files.forEach(file => {
18
+ console.log(`${chalk.green("📄")} ${chalk.white(file.filename)}`);
19
+ });
20
+
21
+ } catch (err) {
22
+ logger.error(err.response?.data?.message || err.message);
23
+ }
24
+ }
25
+
26
+ module.exports = list;
@@ -0,0 +1,24 @@
1
+ const chalk = require("chalk");
2
+ const { getFile } = require("../services/api");
3
+ const logger = require("../utils/logger");
4
+
5
+ async function read(filename) {
6
+ if (!filename) {
7
+ logger.error("Usage: vf -r <filename>");
8
+ return;
9
+ }
10
+
11
+ try {
12
+ const file = await getFile(filename);
13
+
14
+ console.log(chalk.blue("─".repeat(60)));
15
+ logger.title(filename);
16
+ console.log(chalk.blue("─".repeat(60)));
17
+ console.log(file.content);
18
+
19
+ } catch (err) {
20
+ logger.error(err.response?.data?.message || err.message);
21
+ }
22
+ }
23
+
24
+ module.exports = read;
@@ -0,0 +1,35 @@
1
+ const openEditor = require("../utils/editor");
2
+ const {
3
+ fileExists,
4
+ createFile,
5
+ } = require("../services/api");
6
+ const logger = require("../utils/logger");
7
+
8
+ async function write(filename) {
9
+ if (!filename) {
10
+ logger.error("Usage: vf -w <filename>");
11
+ return;
12
+ }
13
+
14
+ const exists = await fileExists(filename);
15
+
16
+ if (exists) {
17
+ logger.warning(`File "${filename}" already exists.`);
18
+ logger.info(`Use: vf -e ${filename}`);
19
+ return;
20
+ }
21
+
22
+ try {
23
+ logger.info("Opening editor...");
24
+
25
+ const content = await openEditor(filename);
26
+
27
+ await createFile(filename, content);
28
+
29
+ logger.success("File saved successfully.");
30
+ } catch (err) {
31
+ logger.error(err.message);
32
+ }
33
+ }
34
+
35
+ module.exports = write;
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ require("dotenv").config({ quiet: true });
3
+ const write = require("./commands/write");
4
+ const read = require("./commands/read");
5
+ const list = require("./commands/list");
6
+ const remove = require("./commands/delete");
7
+ const edit = require("./commands/edit");
8
+ const logger = require("./utils/logger");
9
+
10
+ const { version } = require("../package.json");
11
+
12
+ const args = process.argv.slice(2);
13
+
14
+ function showHelp() {
15
+ logger.title("ViewFile CLI");
16
+ console.log(`Version ${version}
17
+
18
+ Usage:
19
+ vf -w <filename> Create a new file
20
+ vf -e <filename> Edit an existing file
21
+ vf -r <filename> Read a file
22
+ vf -rm <filename> Delete a file
23
+
24
+ Options:
25
+ --help Show this help message
26
+ --version Show CLI version
27
+ `);
28
+ }
29
+
30
+ async function main() {
31
+
32
+ if (args.length === 0) {
33
+ showHelp();
34
+ return;
35
+ }
36
+
37
+ const command = args[0];
38
+
39
+ switch (command) {
40
+
41
+ case "-w":
42
+ await write(args[1]);
43
+ break;
44
+
45
+ case "-e":
46
+ await edit(args[1]);
47
+ break;
48
+
49
+ case "-r":
50
+ await read(args[1]);
51
+ break;
52
+
53
+ case "-rm":
54
+ await remove(args[1]);
55
+ break;
56
+
57
+ // case "ls":
58
+ // await list();
59
+ // break;
60
+
61
+ case "--help":
62
+ case "-h":
63
+ showHelp();
64
+ break;
65
+
66
+ case "--version":
67
+ case "-v":
68
+ console.log(`ViewFile CLI v${version}`);
69
+ break;
70
+
71
+ default:
72
+ logger.error(`Unknown command "${command}"`);
73
+ console.log();
74
+ logger.info('Run "vf --help" to see available commands.');
75
+ }
76
+ }
77
+
78
+ main();
@@ -0,0 +1,59 @@
1
+ const axios = require("axios");
2
+
3
+ const BASE_URL = process.env.API_URL || "https://viewfile-6xjx.onrender.com";
4
+
5
+ const API = `${BASE_URL}/files`;
6
+
7
+ async function createFile(filename, content) {
8
+ const res = await axios.post(API, {
9
+ filename,
10
+ content,
11
+ });
12
+
13
+ return res.data;
14
+ }
15
+
16
+ async function updateFile(filename, content) {
17
+ const res = await axios.put(`${API}/${filename}`, {
18
+ content,
19
+ });
20
+
21
+ return res.data;
22
+ }
23
+
24
+ async function getFile(filename) {
25
+ const res = await axios.get(`${API}/${filename}`);
26
+ return res.data;
27
+ }
28
+
29
+ async function fileExists(filename) {
30
+ try {
31
+ await axios.get(`${API}/${filename}`);
32
+ return true;
33
+ } catch (err) {
34
+ if (err.response?.status === 404) {
35
+ return false;
36
+ }
37
+
38
+ throw err;
39
+ }
40
+ }
41
+
42
+ async function listFiles() {
43
+ const res = await axios.get(API);
44
+ return res.data;
45
+ }
46
+
47
+ async function deleteFile(filename) {
48
+ const res = await axios.delete(`${API}/${filename}`);
49
+ return res.data;
50
+ }
51
+
52
+ module.exports = {
53
+ createFile,
54
+ updateFile,
55
+ getFile,
56
+ listFiles,
57
+ deleteFile,
58
+ fileExists
59
+ };
@@ -0,0 +1,85 @@
1
+ class Buffer {
2
+
3
+ constructor(initialContent = "") {
4
+
5
+ this.lines =
6
+ initialContent.length > 0
7
+ ? initialContent.split("\n")
8
+ : [""];
9
+ }
10
+
11
+ line(y) {
12
+ return this.lines[y];
13
+ }
14
+
15
+ lineCount() {
16
+ return this.lines.length;
17
+ }
18
+
19
+ insert(x, y, char) {
20
+
21
+ const line = this.lines[y];
22
+
23
+ this.lines[y] =
24
+ line.slice(0, x) +
25
+ char +
26
+ line.slice(x);
27
+ }
28
+
29
+ deleteBackward(x, y) {
30
+
31
+ if (x > 0) {
32
+
33
+ const line = this.lines[y];
34
+
35
+ this.lines[y] =
36
+ line.slice(0, x - 1) +
37
+ line.slice(x);
38
+
39
+ return {
40
+ x: x - 1,
41
+ y
42
+ };
43
+ }
44
+
45
+ if (y > 0) {
46
+
47
+ const previousLength =
48
+ this.lines[y - 1].length;
49
+
50
+ this.lines[y - 1] += this.lines[y];
51
+
52
+ this.lines.splice(y, 1);
53
+
54
+ return {
55
+ x: previousLength,
56
+ y: y - 1
57
+ };
58
+ }
59
+
60
+ return { x, y };
61
+ }
62
+
63
+ newline(x, y) {
64
+
65
+ const current = this.lines[y];
66
+
67
+ const left = current.slice(0, x);
68
+ const right = current.slice(x);
69
+
70
+ this.lines[y] = left;
71
+
72
+ this.lines.splice(y + 1, 0, right);
73
+
74
+ return {
75
+ x: 0,
76
+ y: y + 1
77
+ };
78
+ }
79
+
80
+ toString() {
81
+ return this.lines.join("\n");
82
+ }
83
+ }
84
+
85
+ module.exports = Buffer;
@@ -0,0 +1,82 @@
1
+ class Cursor {
2
+
3
+ constructor(buffer) {
4
+ this.buffer = buffer;
5
+
6
+ this.x = 0;
7
+ this.y = 0;
8
+ }
9
+
10
+ loadToEnd() {
11
+ this.y = this.buffer.lineCount() - 1;
12
+ this.x = this.buffer.line(this.y).length;
13
+ }
14
+
15
+ left() {
16
+
17
+ if (this.x > 0) {
18
+ this.x--;
19
+ }
20
+ else if (this.y > 0) {
21
+ this.y--;
22
+ this.x = this.buffer.line(this.y).length;
23
+ }
24
+ }
25
+
26
+ right() {
27
+
28
+ if (this.x < this.buffer.line(this.y).length) {
29
+ this.x++;
30
+ }
31
+ else if (this.y < this.buffer.lineCount() - 1) {
32
+ this.y++;
33
+ this.x = 0;
34
+ }
35
+ }
36
+
37
+ up() {
38
+
39
+ if (this.y > 0) {
40
+ this.y--;
41
+
42
+ this.x = Math.min(
43
+ this.x,
44
+ this.buffer.line(this.y).length
45
+ );
46
+ }
47
+ }
48
+
49
+ down() {
50
+
51
+ if (this.y < this.buffer.lineCount() - 1) {
52
+ this.y++;
53
+
54
+ this.x = Math.min(
55
+ this.x,
56
+ this.buffer.line(this.y).length
57
+ );
58
+ }
59
+ }
60
+
61
+ home() {
62
+ this.x = 0;
63
+ }
64
+
65
+ end() {
66
+ this.x = this.buffer.line(this.y).length;
67
+ }
68
+
69
+ set(x, y) {
70
+ this.x = x;
71
+ this.y = y;
72
+ }
73
+
74
+ position() {
75
+ return {
76
+ x: this.x,
77
+ y: this.y
78
+ };
79
+ }
80
+ }
81
+
82
+ module.exports = Cursor;
@@ -0,0 +1,33 @@
1
+ const { terminal: term } = require("terminal-kit");
2
+
3
+ const Buffer = require("./buffer");
4
+ const Cursor = require("./cursor");
5
+ const Viewport = require("./viewport");
6
+ const Renderer = require("./renderer");
7
+ const Input = require("./input");
8
+
9
+ async function openEditor(filename, initialContent = "") {
10
+
11
+ const buffer = new Buffer(initialContent);
12
+
13
+ const cursor = new Cursor(buffer);
14
+ cursor.loadToEnd();
15
+
16
+ const viewport = new Viewport(term, 5);
17
+
18
+ const renderer = new Renderer(
19
+ buffer,
20
+ cursor,
21
+ viewport
22
+ );
23
+
24
+ const input = new Input(
25
+ buffer,
26
+ cursor,
27
+ renderer
28
+ );
29
+
30
+ return await input.start(filename);
31
+ }
32
+
33
+ module.exports = openEditor;
@@ -0,0 +1,135 @@
1
+ const { terminal: term } = require("terminal-kit");
2
+
3
+ class Input {
4
+
5
+ constructor(buffer, cursor, renderer) {
6
+ this.buffer = buffer;
7
+ this.cursor = cursor;
8
+ this.renderer = renderer;
9
+
10
+ this.renderScheduled = false;
11
+ }
12
+
13
+ scheduleRender(filename) {
14
+
15
+ if (this.renderScheduled) return;
16
+
17
+ this.renderScheduled = true;
18
+
19
+ setImmediate(() => {
20
+ this.renderer.draw(filename);
21
+ this.renderScheduled = false;
22
+ });
23
+ }
24
+
25
+ start(filename) {
26
+
27
+ return new Promise((resolve, reject) => {
28
+
29
+ // Initial render
30
+ this.renderer.draw(filename);
31
+
32
+ term.grabInput(true);
33
+
34
+ const cleanup = () => {
35
+
36
+ term.removeListener("key", onKey);
37
+ term.grabInput(false);
38
+
39
+ if (typeof term.hideCursor === "function") {
40
+ term.hideCursor(false);
41
+ }
42
+
43
+ term.styleReset();
44
+ term.clear();
45
+ term.moveTo(1, 1);
46
+ };
47
+
48
+ const onKey = (name) => {
49
+
50
+ switch (name) {
51
+
52
+ case "CTRL_S":
53
+ cleanup();
54
+ resolve(this.buffer.toString());
55
+ return;
56
+
57
+ case "ESCAPE":
58
+ cleanup();
59
+ reject(new Error("Cancelled"));
60
+ return;
61
+
62
+ case "LEFT":
63
+ this.cursor.left();
64
+ break;
65
+
66
+ case "RIGHT":
67
+ this.cursor.right();
68
+ break;
69
+
70
+ case "UP":
71
+ this.cursor.up();
72
+ break;
73
+
74
+ case "DOWN":
75
+ this.cursor.down();
76
+ break;
77
+
78
+ case "HOME":
79
+ this.cursor.home();
80
+ break;
81
+
82
+ case "END":
83
+ this.cursor.end();
84
+ break;
85
+
86
+ case "ENTER": {
87
+
88
+ const pos = this.buffer.newline(
89
+ this.cursor.x,
90
+ this.cursor.y
91
+ );
92
+
93
+ this.cursor.set(pos.x, pos.y);
94
+ break;
95
+ }
96
+
97
+ case "BACKSPACE": {
98
+
99
+ const pos = this.buffer.deleteBackward(
100
+ this.cursor.x,
101
+ this.cursor.y
102
+ );
103
+
104
+ this.cursor.set(pos.x, pos.y);
105
+ break;
106
+ }
107
+
108
+ default:
109
+
110
+ if (name.length === 1) {
111
+
112
+ this.buffer.insert(
113
+ this.cursor.x,
114
+ this.cursor.y,
115
+ name
116
+ );
117
+
118
+ this.cursor.x++;
119
+ }
120
+
121
+ break;
122
+ }
123
+
124
+ this.scheduleRender(filename);
125
+ };
126
+
127
+ term.on("key", onKey);
128
+
129
+ });
130
+
131
+ }
132
+
133
+ }
134
+
135
+ module.exports = Input;
@@ -0,0 +1,89 @@
1
+ const { terminal: term } = require("terminal-kit");
2
+
3
+ class Renderer {
4
+ constructor(buffer, cursor, viewport) {
5
+ this.buffer = buffer;
6
+ this.cursor = cursor;
7
+ this.viewport = viewport;
8
+ }
9
+
10
+ draw(filename) {
11
+
12
+ // Hide cursor while drawing
13
+ if (term.hideCursor) {
14
+ term.hideCursor(true);
15
+ }
16
+
17
+ term.moveTo(1, 1);
18
+
19
+ // Some versions don't support eraseDisplayBelow()
20
+ if (typeof term.eraseDisplayBelow === "function") {
21
+ term.eraseDisplayBelow();
22
+ } else {
23
+ term.clear();
24
+ }
25
+
26
+ // Header
27
+ term.bold.cyan("ViewFile\n");
28
+
29
+ term.gray("Editing: ");
30
+ term.green(filename);
31
+ term("\n");
32
+
33
+ term.gray("Ctrl+S Save | Esc Cancel | Arrows Move\n");
34
+ term("──────────────────────────────────────────────\n");
35
+
36
+ // Keep cursor visible
37
+ this.viewport.update(this.cursor.y);
38
+
39
+ const start = this.viewport.firstLine();
40
+ const end = this.viewport.lastLine(this.buffer);
41
+
42
+ // Draw only visible lines
43
+ for (let i = start; i < end; i++) {
44
+
45
+ const lineNumber = String(i + 1).padStart(4);
46
+
47
+ term.gray(lineNumber);
48
+ term.gray(" │ ");
49
+
50
+ term(this.buffer.line(i));
51
+ term("\n");
52
+ }
53
+
54
+ // Fill remaining editor area
55
+ const drawn = end - start;
56
+ const remaining = Math.max(
57
+ 0,
58
+ this.viewport.visibleHeight() - drawn - 1
59
+ );
60
+
61
+ for (let i = 0; i < remaining; i++) {
62
+ term.gray(" │\n");
63
+ }
64
+
65
+ // Draw status bar at bottom
66
+ term.moveTo(1, term.height);
67
+
68
+ term.moveTo(1, term.height);
69
+
70
+ term.eraseLine();
71
+
72
+ term.inverse(
73
+ ` Ln ${this.cursor.y + 1} | Col ${this.cursor.x + 1} | ${this.buffer.lineCount()} lines `
74
+ );
75
+
76
+ // Cursor position
77
+ term.moveTo(
78
+ this.cursor.x + 8,
79
+ this.viewport.screenY(this.cursor.y)
80
+ );
81
+
82
+ // Show cursor again
83
+ if (term.hideCursor) {
84
+ term.hideCursor(false);
85
+ }
86
+ }
87
+ }
88
+
89
+ module.exports = Renderer;
@@ -0,0 +1,48 @@
1
+ class Viewport {
2
+
3
+ constructor(term, headerHeight = 6) {
4
+
5
+ this.term = term;
6
+ this.headerHeight = headerHeight;
7
+
8
+ this.scrollY = 0;
9
+ }
10
+
11
+ visibleHeight() {
12
+ return this.term.height - this.headerHeight;
13
+ }
14
+
15
+ update(cursorY) {
16
+
17
+ const visible = this.visibleHeight();
18
+
19
+ // Scroll up
20
+ if (cursorY < this.scrollY) {
21
+ this.scrollY = cursorY;
22
+ }
23
+
24
+ // Scroll down
25
+ else if (cursorY >= this.scrollY + visible) {
26
+ this.scrollY = cursorY - visible + 1;
27
+ }
28
+ }
29
+
30
+ firstLine() {
31
+ return this.scrollY;
32
+ }
33
+
34
+ lastLine(buffer) {
35
+
36
+ return Math.min(
37
+ buffer.lineCount(),
38
+ this.scrollY + this.visibleHeight()
39
+ );
40
+ }
41
+
42
+ screenY(cursorY) {
43
+ return (cursorY - this.scrollY) + this.headerHeight;
44
+ }
45
+
46
+ }
47
+
48
+ module.exports = Viewport;
@@ -0,0 +1,29 @@
1
+ const chalk = require("chalk");
2
+
3
+ module.exports = {
4
+
5
+ success(message) {
6
+ console.log(chalk.green(`✓ ${message}`));
7
+ },
8
+
9
+ error(message) {
10
+ console.log(chalk.red(`✗ ${message}`));
11
+ },
12
+
13
+ warning(message) {
14
+ console.log(chalk.yellow(`⚠ ${message}`));
15
+ },
16
+
17
+ info(message) {
18
+ console.log(chalk.cyan(`ℹ ${message}`));
19
+ },
20
+
21
+ title(message) {
22
+ console.log(chalk.bold.blue(message));
23
+ },
24
+
25
+ plain(message) {
26
+ console.log(message);
27
+ }
28
+
29
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "virtualfile-cli",
3
+ "version": "1.0.0",
4
+ "description": "A terminal-based cloud text file storage CLI.",
5
+ "bin": {
6
+ "vf": "client/index.js"
7
+ },
8
+ "scripts": {
9
+ "start": "node server/app.js",
10
+ "dev": "nodemon server/app.js"
11
+ },
12
+ "keywords": [
13
+ "cli",
14
+ "terminal",
15
+ "text",
16
+ "cloud",
17
+ "notes",
18
+ "nodejs"
19
+ ],
20
+ "author": "Sudesh",
21
+ "license": "MIT",
22
+ "type": "commonjs",
23
+ "dependencies": {
24
+ "axios": "^1.18.1",
25
+ "bcryptjs": "^3.0.3",
26
+ "chalk": "^4.1.2",
27
+ "cors": "^2.8.6",
28
+ "dotenv": "^17.4.2",
29
+ "express": "^5.2.1",
30
+ "jsonwebtoken": "^9.0.3",
31
+ "mongoose": "^9.7.4",
32
+ "ora": "^5.4.1",
33
+ "terminal-kit": "^3.1.4"
34
+ },
35
+ "devDependencies": {
36
+ "nodemon": "^3.1.14"
37
+ }
38
+ }
package/server/app.js ADDED
@@ -0,0 +1,25 @@
1
+ const express = require("express");
2
+ const dotenv = require("dotenv");
3
+ const cors = require("cors");
4
+
5
+ const connectDB = require("./config/db");
6
+ const fileRoutes = require("./routes/fileRoutes");
7
+
8
+ dotenv.config();
9
+
10
+ connectDB();
11
+
12
+ const app = express();
13
+
14
+ app.use(cors());
15
+ app.use(express.json());
16
+ app.use("/files", fileRoutes);
17
+ app.get("/", (req, res) => {
18
+ res.send("ViewFile API Running...");
19
+ });
20
+
21
+ const PORT = process.env.PORT || 5000;
22
+
23
+ app.listen(PORT, () => {
24
+ console.log(`🚀 Server running on port ${PORT}`);
25
+ });
@@ -0,0 +1,14 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const connectDB = async () => {
4
+ try {
5
+ await mongoose.connect(process.env.MONGO_URI);
6
+ console.log("MongoDB Connected");
7
+ } catch (error) {
8
+ console.error(" Database Connection Failed");
9
+ console.error(error.message);
10
+ process.exit(1);
11
+ }
12
+ };
13
+
14
+ module.exports = connectDB;
@@ -0,0 +1,100 @@
1
+ const File = require("../models/File");
2
+
3
+ const createFile = async (req, res) => {
4
+ try {
5
+ const { filename, content } = req.body;
6
+
7
+ const existing = await File.findOne({ filename });
8
+
9
+ if (existing) {
10
+ return res.status(409).json({
11
+ message: "File already exists",
12
+ });
13
+ }
14
+
15
+ const file = await File.create({
16
+ filename,
17
+ content,
18
+ });
19
+
20
+ res.status(201).json(file);
21
+
22
+ } catch (error) {
23
+ res.status(500).json({
24
+ message: error.message,
25
+ });
26
+ }
27
+ };
28
+
29
+ const getFile = async (req, res) => {
30
+ try {
31
+ const file = await File.findOne({ filename: req.params.filename });
32
+
33
+ if (!file) {
34
+ return res.status(404).json({ message: "File not found" });
35
+ }
36
+
37
+ res.json(file);
38
+ } catch (error) {
39
+ res.status(500).json({ message: error.message });
40
+ }
41
+ };
42
+
43
+ const deleteFile = async (req, res) => {
44
+ try {
45
+ const file = await File.findOneAndDelete({
46
+ filename: req.params.filename,
47
+ });
48
+
49
+ if (!file) {
50
+ return res.status(404).json({ message: "File not found" });
51
+ }
52
+
53
+ res.json({ message: "File deleted successfully" });
54
+ } catch (error) {
55
+ res.status(500).json({ message: error.message });
56
+ }
57
+ };
58
+
59
+ const listFiles = async (req, res) => {
60
+ try {
61
+ const files = await File.find({}, "filename");
62
+ res.json(files);
63
+ } catch (error) {
64
+ res.status(500).json({ message: error.message });
65
+ }
66
+ };
67
+
68
+ const updateFile = async (req, res) => {
69
+ try {
70
+
71
+ const file = await File.findOne({
72
+ filename: req.params.filename,
73
+ });
74
+
75
+ if (!file) {
76
+ return res.status(404).json({
77
+ message: "File not found",
78
+ });
79
+ }
80
+
81
+ file.content = req.body.content;
82
+
83
+ await file.save();
84
+
85
+ res.json(file);
86
+
87
+ } catch (error) {
88
+ res.status(500).json({
89
+ message: error.message,
90
+ });
91
+ }
92
+ };
93
+
94
+ module.exports = {
95
+ createFile,
96
+ getFile,
97
+ deleteFile,
98
+ updateFile,
99
+ listFiles
100
+ };
@@ -0,0 +1,21 @@
1
+ const mongoose = require("mongoose");
2
+
3
+ const fileSchema = new mongoose.Schema(
4
+ {
5
+ filename: {
6
+ type: String,
7
+ required: true,
8
+ trim: true,
9
+ unique: true
10
+ },
11
+ content: {
12
+ type: String,
13
+ required: true,
14
+ },
15
+ },
16
+ {
17
+ timestamps: true,
18
+ }
19
+ );
20
+
21
+ module.exports = mongoose.model("File", fileSchema);
@@ -0,0 +1,16 @@
1
+ const express = require("express");
2
+ const router = express.Router();
3
+
4
+ const { createFile, getFile, deleteFile, listFiles, updateFile } = require("../controllers/fileController");
5
+
6
+ router.post("/", createFile);
7
+
8
+ router.put("/:filename", updateFile);
9
+
10
+ router.get("/", listFiles);
11
+
12
+ router.get("/:filename", getFile);
13
+
14
+ router.delete("/:filename", deleteFile);
15
+
16
+ module.exports = router;