@shadowob/cli 0.4.0 → 0.4.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,261 @@
1
+ // src/utils/client.ts
2
+ import { ShadowClient, ShadowSocket } from "@shadowob/sdk";
3
+
4
+ // src/config/manager.ts
5
+ import { existsSync } from "fs";
6
+ import { mkdir, readFile, writeFile } from "fs/promises";
7
+ import { homedir } from "os";
8
+ import { dirname, join } from "path";
9
+ var DEFAULT_CONFIG_DIR = join(homedir(), ".shadowob");
10
+ var _DEFAULT_CONFIG_FILE = join(DEFAULT_CONFIG_DIR, "shadowob.config.json");
11
+ var ConfigManager = class {
12
+ config = null;
13
+ configFile;
14
+ constructor(configDir) {
15
+ const dir = configDir ?? DEFAULT_CONFIG_DIR;
16
+ this.configFile = join(dir, "shadowob.config.json");
17
+ }
18
+ async load() {
19
+ if (this.config) return this.config;
20
+ if (!existsSync(this.configFile)) {
21
+ this.config = { profiles: {} };
22
+ return this.config;
23
+ }
24
+ try {
25
+ const content = await readFile(this.configFile, "utf-8");
26
+ this.config = JSON.parse(content);
27
+ return this.config;
28
+ } catch {
29
+ this.config = { profiles: {} };
30
+ return this.config;
31
+ }
32
+ }
33
+ async save() {
34
+ if (!this.config) return;
35
+ await mkdir(dirname(this.configFile), { recursive: true });
36
+ await writeFile(this.configFile, JSON.stringify(this.config, null, 2));
37
+ }
38
+ async getProfile(name) {
39
+ const config = await this.load();
40
+ const profileName = name ?? config.currentProfile;
41
+ if (!profileName) return null;
42
+ return config.profiles[profileName] ?? null;
43
+ }
44
+ async getCurrentProfileName() {
45
+ const config = await this.load();
46
+ return config.currentProfile ?? null;
47
+ }
48
+ async setProfile(name, profile) {
49
+ const config = await this.load();
50
+ config.profiles[name] = profile;
51
+ await this.save();
52
+ }
53
+ async deleteProfile(name) {
54
+ const config = await this.load();
55
+ if (!config.profiles[name]) return false;
56
+ delete config.profiles[name];
57
+ if (config.currentProfile === name) {
58
+ delete config.currentProfile;
59
+ }
60
+ await this.save();
61
+ return true;
62
+ }
63
+ async switchProfile(name) {
64
+ const config = await this.load();
65
+ if (!config.profiles[name]) return false;
66
+ config.currentProfile = name;
67
+ await this.save();
68
+ return true;
69
+ }
70
+ async listProfiles() {
71
+ const config = await this.load();
72
+ return Object.keys(config.profiles);
73
+ }
74
+ getConfigPath() {
75
+ return this.configFile;
76
+ }
77
+ async validate() {
78
+ const result = {
79
+ valid: true,
80
+ errors: [],
81
+ warnings: [],
82
+ profileResults: {}
83
+ };
84
+ if (!existsSync(this.configFile)) {
85
+ result.valid = false;
86
+ result.errors.push("Config file does not exist");
87
+ return result;
88
+ }
89
+ let config;
90
+ try {
91
+ const content = await readFile(this.configFile, "utf-8");
92
+ config = JSON.parse(content);
93
+ } catch (error) {
94
+ result.valid = false;
95
+ result.errors.push(`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
96
+ return result;
97
+ }
98
+ if (!config.profiles || typeof config.profiles !== "object") {
99
+ result.valid = false;
100
+ result.errors.push('Missing or invalid "profiles" field');
101
+ return result;
102
+ }
103
+ if (config.currentProfile) {
104
+ if (!config.profiles[config.currentProfile]) {
105
+ result.valid = false;
106
+ result.errors.push(`Current profile "${config.currentProfile}" does not exist`);
107
+ }
108
+ } else {
109
+ result.warnings.push("No current profile set");
110
+ }
111
+ for (const [name, profile] of Object.entries(config.profiles)) {
112
+ const profileResult = { valid: true };
113
+ if (!profile.serverUrl) {
114
+ profileResult.valid = false;
115
+ result.errors.push(`Profile "${name}" missing serverUrl`);
116
+ } else {
117
+ try {
118
+ new URL(profile.serverUrl);
119
+ } catch {
120
+ profileResult.valid = false;
121
+ result.errors.push(`Profile "${name}" has invalid serverUrl: ${profile.serverUrl}`);
122
+ }
123
+ }
124
+ if (!profile.token) {
125
+ profileResult.valid = false;
126
+ result.errors.push(`Profile "${name}" missing token`);
127
+ } else if (!profile.token.includes(".")) {
128
+ result.warnings.push(`Profile "${name}" token does not look like a JWT`);
129
+ }
130
+ result.profileResults[name] = profileResult;
131
+ if (!profileResult.valid) {
132
+ result.valid = false;
133
+ }
134
+ }
135
+ return result;
136
+ }
137
+ async fix() {
138
+ const changes = [];
139
+ const config = await this.load();
140
+ for (const [name, profile] of Object.entries(config.profiles)) {
141
+ if (!profile.serverUrl || !profile.token) {
142
+ delete config.profiles[name];
143
+ changes.push(`Removed invalid profile "${name}"`);
144
+ }
145
+ }
146
+ if (config.currentProfile && !config.profiles[config.currentProfile]) {
147
+ const remainingProfiles = Object.keys(config.profiles);
148
+ if (remainingProfiles.length > 0) {
149
+ config.currentProfile = remainingProfiles[0];
150
+ changes.push(`Reset current profile to "${config.currentProfile}"`);
151
+ } else {
152
+ delete config.currentProfile;
153
+ changes.push("Removed invalid current profile reference");
154
+ }
155
+ }
156
+ if (!config.profiles) {
157
+ config.profiles = {};
158
+ changes.push("Created empty profiles object");
159
+ }
160
+ await this.save();
161
+ return { fixed: changes.length > 0, changes };
162
+ }
163
+ };
164
+ var configManager = new ConfigManager();
165
+
166
+ // src/utils/client.ts
167
+ async function getConfig(profile) {
168
+ const config = await configManager.getProfile(profile);
169
+ if (!config) {
170
+ throw new Error(
171
+ profile ? `Profile "${profile}" not found. Run: shadowob auth login --profile ${profile}` : "Not authenticated. Run: shadowob auth login"
172
+ );
173
+ }
174
+ return config;
175
+ }
176
+ async function getClient(profile) {
177
+ const config = await getConfig(profile);
178
+ return new ShadowClient(config.serverUrl, config.token);
179
+ }
180
+ async function getSocket(profile) {
181
+ const config = await getConfig(profile);
182
+ return new ShadowSocket({ serverUrl: config.serverUrl, token: config.token });
183
+ }
184
+ function formatError(error) {
185
+ if (error instanceof Error) {
186
+ return error.message;
187
+ }
188
+ return String(error);
189
+ }
190
+ function parseLimit(value, defaultValue = 50, maxValue = 100) {
191
+ if (!value) return defaultValue;
192
+ const parsed = parseInt(value, 10);
193
+ if (Number.isNaN(parsed) || parsed < 1) return defaultValue;
194
+ return Math.min(parsed, maxValue);
195
+ }
196
+ function parsePrice(value) {
197
+ const parsed = parseFloat(value);
198
+ if (Number.isNaN(parsed) || parsed < 0) {
199
+ throw new Error("Price must be a non-negative number");
200
+ }
201
+ return parsed;
202
+ }
203
+ function parseIntOrThrow(value, fieldName) {
204
+ const parsed = parseInt(value, 10);
205
+ if (Number.isNaN(parsed)) {
206
+ throw new Error(`${fieldName} must be a valid integer`);
207
+ }
208
+ return parsed;
209
+ }
210
+ function parsePositiveInt(value, fieldName) {
211
+ const parsed = parseIntOrThrow(value, fieldName);
212
+ if (parsed < 1) {
213
+ throw new Error(`${fieldName} must be a positive integer`);
214
+ }
215
+ return parsed;
216
+ }
217
+ function parseNonNegativeInt(value, fieldName) {
218
+ const parsed = parseIntOrThrow(value, fieldName);
219
+ if (parsed < 0) {
220
+ throw new Error(`${fieldName} must be a non-negative integer`);
221
+ }
222
+ return parsed;
223
+ }
224
+ function parseBoolean(value) {
225
+ if (value === void 0) return void 0;
226
+ if (value === "true" || value === "1") return true;
227
+ if (value === "false" || value === "0") return false;
228
+ return void 0;
229
+ }
230
+ function requireOption(value, name) {
231
+ if (value === void 0 || value === null || value === "") {
232
+ throw new Error(`Missing required option: --${name}`);
233
+ }
234
+ return value;
235
+ }
236
+ async function handleCommand(fn, options, outputFn, errorFn) {
237
+ try {
238
+ const result = await fn();
239
+ outputFn(result, options.json);
240
+ process.exit(0);
241
+ } catch (error) {
242
+ const message = formatError(error);
243
+ errorFn(message, options.json);
244
+ process.exit(1);
245
+ }
246
+ }
247
+
248
+ export {
249
+ configManager,
250
+ getClient,
251
+ getSocket,
252
+ formatError,
253
+ parseLimit,
254
+ parsePrice,
255
+ parseIntOrThrow,
256
+ parsePositiveInt,
257
+ parseNonNegativeInt,
258
+ parseBoolean,
259
+ requireOption,
260
+ handleCommand
261
+ };
@@ -0,0 +1,26 @@
1
+ import {
2
+ formatError,
3
+ getClient,
4
+ getSocket,
5
+ handleCommand,
6
+ parseBoolean,
7
+ parseIntOrThrow,
8
+ parseLimit,
9
+ parseNonNegativeInt,
10
+ parsePositiveInt,
11
+ parsePrice,
12
+ requireOption
13
+ } from "./chunk-T3BKMB7N.js";
14
+ export {
15
+ formatError,
16
+ getClient,
17
+ getSocket,
18
+ handleCommand,
19
+ parseBoolean,
20
+ parseIntOrThrow,
21
+ parseLimit,
22
+ parseNonNegativeInt,
23
+ parsePositiveInt,
24
+ parsePrice,
25
+ requireOption
26
+ };