@involvex/syncstuff-cli 0.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.
@@ -0,0 +1,174 @@
1
+ import { readConfig, writeConfig } from "./config.js";
2
+
3
+ export interface ApiResponse<T = unknown> {
4
+ success: boolean;
5
+ data?: T;
6
+ error?: string;
7
+ message?: string;
8
+ }
9
+
10
+ export interface LoginResponse {
11
+ token: string;
12
+ user: {
13
+ id: string;
14
+ email: string;
15
+ username: string;
16
+ full_name?: string;
17
+ role: string;
18
+ };
19
+ }
20
+
21
+ export interface UserProfile {
22
+ id: string;
23
+ email: string;
24
+ username: string;
25
+ full_name?: string;
26
+ role: "user" | "admin" | "moderator";
27
+ status: "active" | "suspended" | "pending";
28
+ preferences?: unknown;
29
+ created_at: number;
30
+ updated_at: number;
31
+ }
32
+
33
+ export interface Device {
34
+ id: string;
35
+ name: string;
36
+ type: string;
37
+ platform: string;
38
+ last_seen: number;
39
+ is_online: boolean;
40
+ }
41
+
42
+ class ApiClient {
43
+ private baseUrl: string;
44
+ private token: string | null = null;
45
+
46
+ constructor() {
47
+ this.baseUrl =
48
+ process.env.SYNCSTUFF_API_URL ||
49
+ "https://syncstuff-api.involvex.workers.dev";
50
+ this.loadToken();
51
+ }
52
+
53
+ private loadToken(): void {
54
+ const config = readConfig();
55
+ this.token = config.token || null;
56
+ }
57
+
58
+ private async request<T>(
59
+ endpoint: string,
60
+ options: RequestInit = {},
61
+ ): Promise<ApiResponse<T>> {
62
+ const url = `${this.baseUrl}${endpoint}`;
63
+ const headers: Record<string, string> = {
64
+ "Content-Type": "application/json",
65
+ ...(options.headers as Record<string, string>),
66
+ };
67
+
68
+ if (this.token) {
69
+ headers.Authorization = `Bearer ${this.token}`;
70
+ }
71
+
72
+ try {
73
+ const response = await fetch(url, {
74
+ ...options,
75
+ headers,
76
+ });
77
+
78
+ const contentType = response.headers.get("Content-Type");
79
+ let data: ApiResponse<T>;
80
+
81
+ if (contentType?.includes("application/json")) {
82
+ data = (await response.json()) as ApiResponse<T>;
83
+ } else {
84
+ const text = await response.text();
85
+ try {
86
+ data = JSON.parse(text) as ApiResponse<T>;
87
+ } catch {
88
+ return {
89
+ success: false,
90
+ error: text || `HTTP ${response.status}: ${response.statusText}`,
91
+ };
92
+ }
93
+ }
94
+
95
+ if (!response.ok) {
96
+ return {
97
+ success: false,
98
+ error:
99
+ data.error || `HTTP ${response.status}: ${response.statusText}`,
100
+ };
101
+ }
102
+
103
+ return data;
104
+ } catch (error) {
105
+ return {
106
+ success: false,
107
+ error:
108
+ error instanceof Error ? error.message : "Network error occurred",
109
+ };
110
+ }
111
+ }
112
+
113
+ async login(
114
+ email: string,
115
+ password: string,
116
+ ): Promise<ApiResponse<LoginResponse>> {
117
+ const response = await this.request<LoginResponse>("/api/auth/login", {
118
+ method: "POST",
119
+ body: JSON.stringify({ email, password }),
120
+ });
121
+
122
+ if (response.success && response.data?.token) {
123
+ this.token = response.data.token;
124
+ const config = readConfig();
125
+ config.token = this.token;
126
+ config.user = response.data.user;
127
+ writeConfig(config);
128
+ }
129
+
130
+ return response;
131
+ }
132
+
133
+ async logout(): Promise<void> {
134
+ const config = readConfig();
135
+ config.token = null;
136
+ config.user = null;
137
+ writeConfig(config);
138
+ this.token = null;
139
+ }
140
+
141
+ async getProfile(): Promise<ApiResponse<UserProfile>> {
142
+ return this.request<UserProfile>("/api/user/profile", {
143
+ method: "GET",
144
+ });
145
+ }
146
+
147
+ async getDevices(): Promise<ApiResponse<Device[]>> {
148
+ // TODO: Implement when API endpoint is available
149
+ return this.request<Device[]>("/api/devices", {
150
+ method: "GET",
151
+ });
152
+ }
153
+
154
+ async transferFile(
155
+ deviceId: string,
156
+ filePath: string,
157
+ ): Promise<ApiResponse<{ transferId: string }>> {
158
+ // TODO: Implement when API endpoint is available
159
+ return this.request<{ transferId: string }>("/api/transfer", {
160
+ method: "POST",
161
+ body: JSON.stringify({ deviceId, filePath }),
162
+ });
163
+ }
164
+
165
+ isAuthenticated(): boolean {
166
+ return !!this.token;
167
+ }
168
+
169
+ getToken(): string | null {
170
+ return this.token;
171
+ }
172
+ }
173
+
174
+ export const apiClient = new ApiClient();
@@ -0,0 +1,41 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+
5
+ export interface Config {
6
+ token?: string | null;
7
+ user?: {
8
+ id: string;
9
+ email: string;
10
+ username: string;
11
+ full_name?: string;
12
+ role: string;
13
+ } | null;
14
+ apiUrl?: string;
15
+ }
16
+
17
+ const CONFIG_DIR = join(homedir(), ".syncstuff");
18
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
19
+
20
+ function ensureConfigDir(): void {
21
+ try {
22
+ mkdirSync(CONFIG_DIR, { recursive: true });
23
+ } catch {
24
+ // Directory might already exist
25
+ }
26
+ }
27
+
28
+ export function readConfig(): Config {
29
+ ensureConfigDir();
30
+ try {
31
+ const content = readFileSync(CONFIG_FILE, "utf-8");
32
+ return JSON.parse(content) as Config;
33
+ } catch {
34
+ return {};
35
+ }
36
+ }
37
+
38
+ export function writeConfig(config: Config): void {
39
+ ensureConfigDir();
40
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
41
+ }
@@ -0,0 +1,68 @@
1
+ export interface CommandContext {
2
+ debug: boolean;
3
+ }
4
+
5
+ export interface ParsedArgs {
6
+ command: string | undefined;
7
+ flags: {
8
+ debug: boolean;
9
+ help: boolean;
10
+ };
11
+ commandArgs: string[];
12
+ }
13
+
14
+ /**
15
+ * Parse command line arguments to extract global flags
16
+ * Global flags: -d/--debug, -h/--help
17
+ */
18
+ export function parseArgs(args: string[]): ParsedArgs {
19
+ const flags = {
20
+ debug: false,
21
+ help: false,
22
+ };
23
+
24
+ const remainingArgs: string[] = [];
25
+
26
+ for (const arg of args) {
27
+ if (arg === "-d" || arg === "--debug") {
28
+ flags.debug = true;
29
+ } else if (arg === "-h" || arg === "--help") {
30
+ flags.help = true;
31
+ } else {
32
+ remainingArgs.push(arg);
33
+ }
34
+ }
35
+
36
+ return {
37
+ command: remainingArgs[0],
38
+ flags,
39
+ commandArgs: remainingArgs.slice(1),
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Debug logging - only outputs when debug mode is enabled
45
+ */
46
+ export function debugLog(ctx: CommandContext, ...args: unknown[]): void {
47
+ if (ctx.debug) {
48
+ console.log("[DEBUG]", ...args);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Debug info logging
54
+ */
55
+ export function debugInfo(ctx: CommandContext, ...args: unknown[]): void {
56
+ if (ctx.debug) {
57
+ console.log("[DEBUG INFO]", ...args);
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Debug error logging
63
+ */
64
+ export function debugError(ctx: CommandContext, ...args: unknown[]): void {
65
+ if (ctx.debug) {
66
+ console.error("[DEBUG ERROR]", ...args);
67
+ }
68
+ }
@@ -0,0 +1,83 @@
1
+ import boxen from "boxen";
2
+ import chalk from "chalk";
3
+ import ora from "ora";
4
+ import Table from "table";
5
+
6
+ export function success(message: string): void {
7
+ console.log(chalk.green(`✓ ${message}`));
8
+ }
9
+
10
+ export function error(message: string): void {
11
+ console.log(chalk.red(`✗ ${message}`));
12
+ }
13
+
14
+ export function info(message: string): void {
15
+ console.log(chalk.blue(`ℹ ${message}`));
16
+ }
17
+
18
+ export function warning(message: string): void {
19
+ console.log(chalk.yellow(`⚠ ${message}`));
20
+ }
21
+
22
+ export function createSpinner(text: string): ReturnType<typeof ora> {
23
+ return ora({
24
+ text,
25
+ spinner: "dots",
26
+ color: "cyan",
27
+ });
28
+ }
29
+
30
+ export function createBox(
31
+ content: string,
32
+ options?: Parameters<typeof boxen>[1],
33
+ ): string {
34
+ return boxen(content, {
35
+ padding: 1,
36
+ margin: 1,
37
+ borderStyle: "round",
38
+ borderColor: "cyan",
39
+ ...options,
40
+ });
41
+ }
42
+
43
+ export function createTable(data: string[][], headers?: string[]): string {
44
+ const tableData = headers ? [headers, ...data] : data;
45
+ return Table.table(tableData, {
46
+ border: Table.getBorderCharacters("norc"),
47
+ header: headers
48
+ ? {
49
+ alignment: "center",
50
+ content: headers.join(" | "),
51
+ }
52
+ : undefined,
53
+ });
54
+ }
55
+
56
+ export function animateText(text: string, delay: number = 50): Promise<void> {
57
+ return new Promise(resolve => {
58
+ let index = 0;
59
+ const interval = setInterval(() => {
60
+ process.stdout.write(text[index] || "");
61
+ index++;
62
+ if (index >= text.length) {
63
+ clearInterval(interval);
64
+ process.stdout.write("\n");
65
+ resolve();
66
+ }
67
+ }, delay);
68
+ });
69
+ }
70
+
71
+ export function printHeader(): void {
72
+ const header = chalk.cyan.bold(`
73
+ ╔═══════════════════════════════════════╗
74
+ ║ Syncstuff CLI v0.0.1 ║
75
+ ║ Seamless Sync Across Devices ║
76
+ ╚═══════════════════════════════════════╝
77
+ `);
78
+ console.log(header);
79
+ }
80
+
81
+ export function printSeparator(): void {
82
+ console.log(chalk.gray("─".repeat(50)));
83
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext", "DOM"],
5
+ "target": "ESNext",
6
+ "module": "esnext",
7
+ "moduleDetection": "force",
8
+ "jsx": "react-jsx",
9
+ "allowJs": true,
10
+
11
+ // Bundler mode
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "noEmit": true,
16
+
17
+ // Best practices
18
+ "strict": true,
19
+ "skipLibCheck": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noImplicitOverride": true,
23
+
24
+ // Some stricter flags (disabled by default)
25
+ "noUnusedLocals": false,
26
+ "noUnusedParameters": false,
27
+ "noPropertyAccessFromIndexSignature": false,
28
+ "rootDir": "./",
29
+ "project": "./tsconfig.json"
30
+ }
31
+ }