delivered-cli 0.1.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/dist/index.d.ts +2 -0
- package/dist/index.js +267 -0
- package/package.json +35 -0
- package/src/index.ts +270 -0
- package/tsconfig.json +16 -0
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const conf_1 = __importDefault(require("conf"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const readline = __importStar(require("readline"));
|
|
45
|
+
const config = new conf_1.default({
|
|
46
|
+
projectName: "delivered-cli",
|
|
47
|
+
});
|
|
48
|
+
const API_URL = config.get("apiUrl") || "https://delivered.md/api/v1";
|
|
49
|
+
const program = new commander_1.Command();
|
|
50
|
+
program
|
|
51
|
+
.name("delivered")
|
|
52
|
+
.description("CLI for delivered.md - Focus. Ship. Repeat.")
|
|
53
|
+
.version("0.1.0");
|
|
54
|
+
// Helper to get API key
|
|
55
|
+
function getApiKey() {
|
|
56
|
+
const apiKey = config.get("apiKey");
|
|
57
|
+
if (!apiKey) {
|
|
58
|
+
console.error("Error: Not logged in. Run 'delivered login' first.");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
return apiKey;
|
|
62
|
+
}
|
|
63
|
+
// Helper for API requests
|
|
64
|
+
async function apiRequest(endpoint, options = {}) {
|
|
65
|
+
const apiKey = getApiKey();
|
|
66
|
+
const url = `${API_URL}${endpoint}`;
|
|
67
|
+
const response = await fetch(url, {
|
|
68
|
+
...options,
|
|
69
|
+
headers: {
|
|
70
|
+
Authorization: `Bearer ${apiKey}`,
|
|
71
|
+
...options.headers,
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
return response;
|
|
75
|
+
}
|
|
76
|
+
// Login command
|
|
77
|
+
program
|
|
78
|
+
.command("login")
|
|
79
|
+
.description("Login with your API key")
|
|
80
|
+
.action(async () => {
|
|
81
|
+
const rl = readline.createInterface({
|
|
82
|
+
input: process.stdin,
|
|
83
|
+
output: process.stdout,
|
|
84
|
+
});
|
|
85
|
+
console.log("\nTo get your API key:");
|
|
86
|
+
console.log("1. Go to https://delivered.md/settings");
|
|
87
|
+
console.log("2. Create a new API key");
|
|
88
|
+
console.log("3. Copy and paste it below\n");
|
|
89
|
+
rl.question("API Key: ", async (apiKey) => {
|
|
90
|
+
rl.close();
|
|
91
|
+
if (!apiKey || !apiKey.startsWith("dlv_")) {
|
|
92
|
+
console.error("Error: Invalid API key format. Keys start with 'dlv_'");
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
// Test the key
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetch(`${API_URL}/page`, {
|
|
98
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
99
|
+
});
|
|
100
|
+
if (response.status === 401) {
|
|
101
|
+
console.error("Error: Invalid API key");
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
config.set("apiKey", apiKey);
|
|
105
|
+
console.log("\nLogged in successfully!");
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.error("Error: Failed to validate API key");
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
// Logout command
|
|
114
|
+
program
|
|
115
|
+
.command("logout")
|
|
116
|
+
.description("Remove stored API key")
|
|
117
|
+
.action(() => {
|
|
118
|
+
config.delete("apiKey");
|
|
119
|
+
console.log("Logged out successfully.");
|
|
120
|
+
});
|
|
121
|
+
// Pull command
|
|
122
|
+
program
|
|
123
|
+
.command("pull")
|
|
124
|
+
.description("Download your page as markdown")
|
|
125
|
+
.option("-o, --output <file>", "Output file path")
|
|
126
|
+
.action(async (options) => {
|
|
127
|
+
try {
|
|
128
|
+
const response = await apiRequest("/page");
|
|
129
|
+
if (!response.ok) {
|
|
130
|
+
const error = await response.json();
|
|
131
|
+
console.error(`Error: ${error.error || "Failed to fetch page"}`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
const markdown = await response.text();
|
|
135
|
+
if (options.output) {
|
|
136
|
+
const outputPath = path.resolve(options.output);
|
|
137
|
+
fs.writeFileSync(outputPath, markdown);
|
|
138
|
+
console.log(`Page saved to ${outputPath}`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Output to stdout
|
|
142
|
+
console.log(markdown);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error("Error: Failed to pull page");
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
// Push command
|
|
151
|
+
program
|
|
152
|
+
.command("push [file]")
|
|
153
|
+
.description("Upload markdown content to your page")
|
|
154
|
+
.action(async (file) => {
|
|
155
|
+
try {
|
|
156
|
+
let content;
|
|
157
|
+
if (file) {
|
|
158
|
+
// Read from file
|
|
159
|
+
const filePath = path.resolve(file);
|
|
160
|
+
if (!fs.existsSync(filePath)) {
|
|
161
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
// Read from stdin
|
|
168
|
+
const chunks = [];
|
|
169
|
+
for await (const chunk of process.stdin) {
|
|
170
|
+
chunks.push(chunk);
|
|
171
|
+
}
|
|
172
|
+
content = Buffer.concat(chunks).toString("utf-8");
|
|
173
|
+
}
|
|
174
|
+
if (!content.trim()) {
|
|
175
|
+
console.error("Error: Content cannot be empty");
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
const response = await apiRequest("/page", {
|
|
179
|
+
method: "PUT",
|
|
180
|
+
headers: { "Content-Type": "text/markdown" },
|
|
181
|
+
body: content,
|
|
182
|
+
});
|
|
183
|
+
if (!response.ok) {
|
|
184
|
+
const error = await response.json();
|
|
185
|
+
console.error(`Error: ${error.error || "Failed to push page"}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
console.log("Page updated successfully!");
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.error("Error: Failed to push page");
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
// Snapshots command
|
|
196
|
+
program
|
|
197
|
+
.command("snapshots")
|
|
198
|
+
.description("List your page snapshots")
|
|
199
|
+
.action(async () => {
|
|
200
|
+
try {
|
|
201
|
+
const response = await apiRequest("/snapshots");
|
|
202
|
+
if (!response.ok) {
|
|
203
|
+
const error = await response.json();
|
|
204
|
+
console.error(`Error: ${error.error || "Failed to fetch snapshots"}`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
const data = await response.json();
|
|
208
|
+
const snapshots = data.snapshots;
|
|
209
|
+
if (snapshots.length === 0) {
|
|
210
|
+
console.log("No snapshots found.");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
console.log("\nSnapshots:\n");
|
|
214
|
+
snapshots.forEach((snapshot, index) => {
|
|
215
|
+
const date = new Date(snapshot.createdAt).toLocaleString();
|
|
216
|
+
const accessIcon = snapshot.accessible ? " " : "[locked]";
|
|
217
|
+
console.log(`${index + 1}. ${date} ${accessIcon}`);
|
|
218
|
+
console.log(` ID: ${snapshot.id}\n`);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
console.error("Error: Failed to fetch snapshots");
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
// Revert command
|
|
227
|
+
program
|
|
228
|
+
.command("revert <snapshot-id>")
|
|
229
|
+
.description("Revert to a specific snapshot")
|
|
230
|
+
.action(async (snapshotId) => {
|
|
231
|
+
try {
|
|
232
|
+
const response = await apiRequest("/revert", {
|
|
233
|
+
method: "POST",
|
|
234
|
+
headers: { "Content-Type": "application/json" },
|
|
235
|
+
body: JSON.stringify({ snapshotId }),
|
|
236
|
+
});
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
const error = await response.json();
|
|
239
|
+
console.error(`Error: ${error.error || "Failed to revert"}`);
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
const data = await response.json();
|
|
243
|
+
console.log(`Reverted successfully to snapshot from ${new Date(data.revertedTo).toLocaleString()}`);
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
console.error("Error: Failed to revert");
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
// Config command
|
|
251
|
+
program
|
|
252
|
+
.command("config")
|
|
253
|
+
.description("View or set configuration")
|
|
254
|
+
.option("--api-url <url>", "Set custom API URL")
|
|
255
|
+
.option("--show", "Show current configuration")
|
|
256
|
+
.action((options) => {
|
|
257
|
+
if (options.apiUrl) {
|
|
258
|
+
config.set("apiUrl", options.apiUrl);
|
|
259
|
+
console.log(`API URL set to: ${options.apiUrl}`);
|
|
260
|
+
}
|
|
261
|
+
if (options.show || (!options.apiUrl)) {
|
|
262
|
+
console.log("\nConfiguration:");
|
|
263
|
+
console.log(` API URL: ${config.get("apiUrl") || API_URL} (default)`);
|
|
264
|
+
console.log(` Logged in: ${config.get("apiKey") ? "Yes" : "No"}`);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "delivered-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for delivered.md - Focus. Ship. Repeat.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"delivered": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc -w",
|
|
12
|
+
"start": "node dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"delivered",
|
|
16
|
+
"markdown",
|
|
17
|
+
"productivity",
|
|
18
|
+
"goals",
|
|
19
|
+
"cli"
|
|
20
|
+
],
|
|
21
|
+
"author": "",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"commander": "^12.1.0",
|
|
25
|
+
"conf": "^12.0.0",
|
|
26
|
+
"node-fetch": "^3.3.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.10.0",
|
|
30
|
+
"typescript": "^5.3.3"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import Conf from "conf";
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import * as readline from "readline";
|
|
8
|
+
|
|
9
|
+
const config = new Conf<{ apiKey?: string; apiUrl?: string }>({
|
|
10
|
+
projectName: "delivered-cli",
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const API_URL = config.get("apiUrl") || "https://delivered.md/api/v1";
|
|
14
|
+
|
|
15
|
+
const program = new Command();
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name("delivered")
|
|
19
|
+
.description("CLI for delivered.md - Focus. Ship. Repeat.")
|
|
20
|
+
.version("0.1.0");
|
|
21
|
+
|
|
22
|
+
// Helper to get API key
|
|
23
|
+
function getApiKey(): string {
|
|
24
|
+
const apiKey = config.get("apiKey");
|
|
25
|
+
if (!apiKey) {
|
|
26
|
+
console.error("Error: Not logged in. Run 'delivered login' first.");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
return apiKey;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Helper for API requests
|
|
33
|
+
async function apiRequest(
|
|
34
|
+
endpoint: string,
|
|
35
|
+
options: RequestInit = {}
|
|
36
|
+
): Promise<Response> {
|
|
37
|
+
const apiKey = getApiKey();
|
|
38
|
+
const url = `${API_URL}${endpoint}`;
|
|
39
|
+
|
|
40
|
+
const response = await fetch(url, {
|
|
41
|
+
...options,
|
|
42
|
+
headers: {
|
|
43
|
+
Authorization: `Bearer ${apiKey}`,
|
|
44
|
+
...options.headers,
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return response;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Login command
|
|
52
|
+
program
|
|
53
|
+
.command("login")
|
|
54
|
+
.description("Login with your API key")
|
|
55
|
+
.action(async () => {
|
|
56
|
+
const rl = readline.createInterface({
|
|
57
|
+
input: process.stdin,
|
|
58
|
+
output: process.stdout,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log("\nTo get your API key:");
|
|
62
|
+
console.log("1. Go to https://delivered.md/settings");
|
|
63
|
+
console.log("2. Create a new API key");
|
|
64
|
+
console.log("3. Copy and paste it below\n");
|
|
65
|
+
|
|
66
|
+
rl.question("API Key: ", async (apiKey) => {
|
|
67
|
+
rl.close();
|
|
68
|
+
|
|
69
|
+
if (!apiKey || !apiKey.startsWith("dlv_")) {
|
|
70
|
+
console.error("Error: Invalid API key format. Keys start with 'dlv_'");
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Test the key
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetch(`${API_URL}/page`, {
|
|
77
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (response.status === 401) {
|
|
81
|
+
console.error("Error: Invalid API key");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
config.set("apiKey", apiKey);
|
|
86
|
+
console.log("\nLogged in successfully!");
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("Error: Failed to validate API key");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Logout command
|
|
95
|
+
program
|
|
96
|
+
.command("logout")
|
|
97
|
+
.description("Remove stored API key")
|
|
98
|
+
.action(() => {
|
|
99
|
+
config.delete("apiKey");
|
|
100
|
+
console.log("Logged out successfully.");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Pull command
|
|
104
|
+
program
|
|
105
|
+
.command("pull")
|
|
106
|
+
.description("Download your page as markdown")
|
|
107
|
+
.option("-o, --output <file>", "Output file path")
|
|
108
|
+
.action(async (options) => {
|
|
109
|
+
try {
|
|
110
|
+
const response = await apiRequest("/page");
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
const error = await response.json();
|
|
114
|
+
console.error(`Error: ${error.error || "Failed to fetch page"}`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const markdown = await response.text();
|
|
119
|
+
|
|
120
|
+
if (options.output) {
|
|
121
|
+
const outputPath = path.resolve(options.output);
|
|
122
|
+
fs.writeFileSync(outputPath, markdown);
|
|
123
|
+
console.log(`Page saved to ${outputPath}`);
|
|
124
|
+
} else {
|
|
125
|
+
// Output to stdout
|
|
126
|
+
console.log(markdown);
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
console.error("Error: Failed to pull page");
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Push command
|
|
135
|
+
program
|
|
136
|
+
.command("push [file]")
|
|
137
|
+
.description("Upload markdown content to your page")
|
|
138
|
+
.action(async (file) => {
|
|
139
|
+
try {
|
|
140
|
+
let content: string;
|
|
141
|
+
|
|
142
|
+
if (file) {
|
|
143
|
+
// Read from file
|
|
144
|
+
const filePath = path.resolve(file);
|
|
145
|
+
if (!fs.existsSync(filePath)) {
|
|
146
|
+
console.error(`Error: File not found: ${filePath}`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
150
|
+
} else {
|
|
151
|
+
// Read from stdin
|
|
152
|
+
const chunks: Buffer[] = [];
|
|
153
|
+
for await (const chunk of process.stdin) {
|
|
154
|
+
chunks.push(chunk);
|
|
155
|
+
}
|
|
156
|
+
content = Buffer.concat(chunks).toString("utf-8");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (!content.trim()) {
|
|
160
|
+
console.error("Error: Content cannot be empty");
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const response = await apiRequest("/page", {
|
|
165
|
+
method: "PUT",
|
|
166
|
+
headers: { "Content-Type": "text/markdown" },
|
|
167
|
+
body: content,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
const error = await response.json();
|
|
172
|
+
console.error(`Error: ${error.error || "Failed to push page"}`);
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log("Page updated successfully!");
|
|
177
|
+
} catch (error) {
|
|
178
|
+
console.error("Error: Failed to push page");
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// Snapshots command
|
|
184
|
+
program
|
|
185
|
+
.command("snapshots")
|
|
186
|
+
.description("List your page snapshots")
|
|
187
|
+
.action(async () => {
|
|
188
|
+
try {
|
|
189
|
+
const response = await apiRequest("/snapshots");
|
|
190
|
+
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
const error = await response.json();
|
|
193
|
+
console.error(`Error: ${error.error || "Failed to fetch snapshots"}`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const data = await response.json();
|
|
198
|
+
const snapshots = data.snapshots;
|
|
199
|
+
|
|
200
|
+
if (snapshots.length === 0) {
|
|
201
|
+
console.log("No snapshots found.");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log("\nSnapshots:\n");
|
|
206
|
+
snapshots.forEach(
|
|
207
|
+
(
|
|
208
|
+
snapshot: { id: string; createdAt: number; accessible: boolean },
|
|
209
|
+
index: number
|
|
210
|
+
) => {
|
|
211
|
+
const date = new Date(snapshot.createdAt).toLocaleString();
|
|
212
|
+
const accessIcon = snapshot.accessible ? " " : "[locked]";
|
|
213
|
+
console.log(`${index + 1}. ${date} ${accessIcon}`);
|
|
214
|
+
console.log(` ID: ${snapshot.id}\n`);
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.error("Error: Failed to fetch snapshots");
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Revert command
|
|
224
|
+
program
|
|
225
|
+
.command("revert <snapshot-id>")
|
|
226
|
+
.description("Revert to a specific snapshot")
|
|
227
|
+
.action(async (snapshotId) => {
|
|
228
|
+
try {
|
|
229
|
+
const response = await apiRequest("/revert", {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: { "Content-Type": "application/json" },
|
|
232
|
+
body: JSON.stringify({ snapshotId }),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (!response.ok) {
|
|
236
|
+
const error = await response.json();
|
|
237
|
+
console.error(`Error: ${error.error || "Failed to revert"}`);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const data = await response.json();
|
|
242
|
+
console.log(
|
|
243
|
+
`Reverted successfully to snapshot from ${new Date(data.revertedTo).toLocaleString()}`
|
|
244
|
+
);
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error("Error: Failed to revert");
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// Config command
|
|
252
|
+
program
|
|
253
|
+
.command("config")
|
|
254
|
+
.description("View or set configuration")
|
|
255
|
+
.option("--api-url <url>", "Set custom API URL")
|
|
256
|
+
.option("--show", "Show current configuration")
|
|
257
|
+
.action((options) => {
|
|
258
|
+
if (options.apiUrl) {
|
|
259
|
+
config.set("apiUrl", options.apiUrl);
|
|
260
|
+
console.log(`API URL set to: ${options.apiUrl}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (options.show || (!options.apiUrl)) {
|
|
264
|
+
console.log("\nConfiguration:");
|
|
265
|
+
console.log(` API URL: ${config.get("apiUrl") || API_URL} (default)`);
|
|
266
|
+
console.log(` Logged in: ${config.get("apiKey") ? "Yes" : "No"}`);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
program.parse();
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|