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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ }