kaven-cli 0.4.0 → 0.4.1-alpha.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/EnvManager-GQMEZ6NV.js +158 -0
- package/dist/MarketplaceClient-IJGRQRC4.js +7 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-GHZX5OAA.js +455 -0
- package/dist/commands/aiox/index.js +20 -0
- package/dist/commands/config/features.js +110 -975
- package/dist/commands/init/aiox-bootstrap.js +83 -0
- package/dist/commands/init/index.js +16 -3
- package/dist/commands/module/activate.js +84 -45
- package/dist/commands/module/list.js +51 -0
- package/dist/core/ProjectInitializer.js +17 -0
- package/dist/core/SchemaActivator.js +8 -0
- package/dist/index.js +20 -4
- package/dist/infrastructure/MarketplaceClient.js +26 -0
- package/dist/lib/capabilities-catalog.js +73 -0
- package/dist/lib/module-registry.js +47 -0
- package/dist/lib/schema-modifier.js +40 -0
- package/dist/tier-table-LAL6PAVW.js +52 -0
- package/package.json +2 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import "./chunk-3RG5ZIWI.js";
|
|
2
|
+
|
|
3
|
+
// src/core/EnvManager.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import * as readline from "readline";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
var EnvManager = class {
|
|
9
|
+
async injectEnvVars(moduleSlug, envVars, options) {
|
|
10
|
+
if (options.skipEnv || !envVars || envVars.length === 0) {
|
|
11
|
+
return { added: 0, skipped: 0 };
|
|
12
|
+
}
|
|
13
|
+
const envFilePath = path.join(options.projectDir, options.envFile ?? ".env");
|
|
14
|
+
const existingContent = this.readEnvFile(envFilePath);
|
|
15
|
+
const existingVars = this.parseEnvFile(existingContent);
|
|
16
|
+
const newVars = [];
|
|
17
|
+
let skipped = 0;
|
|
18
|
+
console.log(chalk.bold(`
|
|
19
|
+
Environment variables for '${moduleSlug}':
|
|
20
|
+
`));
|
|
21
|
+
for (const envDef of envVars) {
|
|
22
|
+
if (existingVars.has(envDef.name)) {
|
|
23
|
+
console.log(chalk.dim(` ${envDef.name} \u2014 already set, skipping`));
|
|
24
|
+
skipped++;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
let value;
|
|
28
|
+
if (envDef.sensitive) {
|
|
29
|
+
value = await this.promptPassword(
|
|
30
|
+
` ${envDef.name} (${envDef.description})${envDef.default ? " [****]" : ""}: `
|
|
31
|
+
);
|
|
32
|
+
if (!value && envDef.default) value = envDef.default;
|
|
33
|
+
} else {
|
|
34
|
+
const defaultHint = envDef.default ? ` [${envDef.default}]` : "";
|
|
35
|
+
value = await this.promptInput(
|
|
36
|
+
` ${envDef.name} (${envDef.description})${defaultHint}: `,
|
|
37
|
+
envDef.default
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (envDef.required && !value) {
|
|
41
|
+
console.log(chalk.yellow(` ${envDef.name} is required.`));
|
|
42
|
+
value = envDef.sensitive ? await this.promptPassword(` ${envDef.name}: `) : await this.promptInput(` ${envDef.name}: `);
|
|
43
|
+
if (!value) {
|
|
44
|
+
console.log(chalk.yellow(` Skipping ${envDef.name} \u2014 set it manually in .env`));
|
|
45
|
+
skipped++;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
newVars.push({ name: envDef.name, value });
|
|
50
|
+
}
|
|
51
|
+
if (newVars.length === 0) {
|
|
52
|
+
console.log(chalk.dim(" No new environment variables to add."));
|
|
53
|
+
return { added: 0, skipped };
|
|
54
|
+
}
|
|
55
|
+
const markerBlock = this.buildMarkerBlock(moduleSlug, newVars);
|
|
56
|
+
this.appendToEnvFile(envFilePath, existingContent, markerBlock);
|
|
57
|
+
console.log(
|
|
58
|
+
chalk.green(`
|
|
59
|
+
Added ${newVars.length} environment variable(s) to ${options.envFile ?? ".env"}`)
|
|
60
|
+
);
|
|
61
|
+
return { added: newVars.length, skipped };
|
|
62
|
+
}
|
|
63
|
+
removeEnvVars(moduleSlug, options) {
|
|
64
|
+
const envFiles = [".env", ".env.local", ".env.development", ".env.production"];
|
|
65
|
+
if (options.envFile) envFiles.unshift(options.envFile);
|
|
66
|
+
let totalRemoved = 0;
|
|
67
|
+
for (const envFile of envFiles) {
|
|
68
|
+
const envFilePath = path.join(options.projectDir, envFile);
|
|
69
|
+
if (!fs.existsSync(envFilePath)) continue;
|
|
70
|
+
const content = fs.readFileSync(envFilePath, "utf-8");
|
|
71
|
+
const beginMarker = `# [KAVEN_MODULE:${moduleSlug} BEGIN]`;
|
|
72
|
+
const endMarker = `# [KAVEN_MODULE:${moduleSlug} END]`;
|
|
73
|
+
const beginIdx = content.indexOf(beginMarker);
|
|
74
|
+
const endIdx = content.indexOf(endMarker);
|
|
75
|
+
if (beginIdx === -1 || endIdx === -1) continue;
|
|
76
|
+
const block = content.substring(beginIdx, endIdx + endMarker.length);
|
|
77
|
+
const varCount = block.split("\n").filter((l) => /^[A-Z_]+=/.test(l)).length;
|
|
78
|
+
const before = content.substring(0, beginIdx).replace(/\n+$/, "\n");
|
|
79
|
+
const after = content.substring(endIdx + endMarker.length + 1);
|
|
80
|
+
fs.writeFileSync(envFilePath, before + after);
|
|
81
|
+
totalRemoved += varCount;
|
|
82
|
+
if (varCount > 0) {
|
|
83
|
+
console.log(chalk.dim(` Removed ${varCount} env var(s) from ${envFile}`));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return totalRemoved;
|
|
87
|
+
}
|
|
88
|
+
readEnvFile(filePath) {
|
|
89
|
+
try {
|
|
90
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
91
|
+
} catch {
|
|
92
|
+
return "";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
parseEnvFile(content) {
|
|
96
|
+
const vars = /* @__PURE__ */ new Map();
|
|
97
|
+
for (const line of content.split("\n")) {
|
|
98
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
99
|
+
if (match) vars.set(match[1], match[2]);
|
|
100
|
+
}
|
|
101
|
+
return vars;
|
|
102
|
+
}
|
|
103
|
+
buildMarkerBlock(moduleSlug, vars) {
|
|
104
|
+
return [
|
|
105
|
+
`# [KAVEN_MODULE:${moduleSlug} BEGIN]`,
|
|
106
|
+
...vars.map((v) => `${v.name}=${v.value}`),
|
|
107
|
+
`# [KAVEN_MODULE:${moduleSlug} END]`
|
|
108
|
+
].join("\n");
|
|
109
|
+
}
|
|
110
|
+
appendToEnvFile(filePath, existingContent, block) {
|
|
111
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
112
|
+
const separator = existingContent.endsWith("\n") || existingContent === "" ? "\n" : "\n\n";
|
|
113
|
+
fs.writeFileSync(filePath, existingContent + separator + block + "\n");
|
|
114
|
+
}
|
|
115
|
+
promptInput(message, defaultValue) {
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
118
|
+
rl.question(message, (answer) => {
|
|
119
|
+
rl.close();
|
|
120
|
+
resolve(answer || defaultValue || "");
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
promptPassword(message) {
|
|
125
|
+
return new Promise((resolve) => {
|
|
126
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
127
|
+
process.stdout.write(message);
|
|
128
|
+
process.stdin.setRawMode?.(true);
|
|
129
|
+
process.stdin.resume();
|
|
130
|
+
let password = "";
|
|
131
|
+
const handler = (key) => {
|
|
132
|
+
const char = key.toString();
|
|
133
|
+
if (char === "\r" || char === "\n") {
|
|
134
|
+
process.stdin.setRawMode?.(false);
|
|
135
|
+
process.stdin.pause();
|
|
136
|
+
process.stdin.removeListener("data", handler);
|
|
137
|
+
process.stdout.write("\n");
|
|
138
|
+
rl.close();
|
|
139
|
+
resolve(password);
|
|
140
|
+
} else if (char === "") {
|
|
141
|
+
process.exit();
|
|
142
|
+
} else if (char === "\x7F") {
|
|
143
|
+
password = password.slice(0, -1);
|
|
144
|
+
process.stdout.clearLine(0);
|
|
145
|
+
process.stdout.cursorTo(0);
|
|
146
|
+
process.stdout.write(message + "*".repeat(password.length));
|
|
147
|
+
} else {
|
|
148
|
+
password += char;
|
|
149
|
+
process.stdout.write("*");
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
process.stdin.on("data", handler);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
export {
|
|
157
|
+
EnvManager
|
|
158
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
__require
|
|
10
|
+
};
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
// src/infrastructure/MarketplaceClient.ts
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
|
|
6
|
+
// src/infrastructure/errors.ts
|
|
7
|
+
var MarketplaceError = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "MarketplaceError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var AuthenticationError = class extends MarketplaceError {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.name = "AuthenticationError";
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var LicenseRequiredError = class extends MarketplaceError {
|
|
20
|
+
constructor(requiredTier, message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.requiredTier = requiredTier;
|
|
23
|
+
this.name = "LicenseRequiredError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
var NotFoundError = class extends MarketplaceError {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "NotFoundError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var RateLimitError = class extends MarketplaceError {
|
|
33
|
+
constructor(retryAfter) {
|
|
34
|
+
super(`Rate limited. Try again in ${retryAfter}s`);
|
|
35
|
+
this.retryAfter = retryAfter;
|
|
36
|
+
this.name = "RateLimitError";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var ServerError = class extends MarketplaceError {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "ServerError";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var NetworkError = class extends MarketplaceError {
|
|
46
|
+
constructor(message) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.name = "NetworkError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var SignatureVerificationError = class extends MarketplaceError {
|
|
52
|
+
constructor(message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = "SignatureVerificationError";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// src/infrastructure/MarketplaceClient.ts
|
|
59
|
+
var DEFAULT_BASE_URL = "https://api.kaven.sh";
|
|
60
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
61
|
+
var MAX_RETRIES = 3;
|
|
62
|
+
var INITIAL_RETRY_DELAY_MS = 1e3;
|
|
63
|
+
function debug(message) {
|
|
64
|
+
if (process.env.KAVEN_DEBUG === "1") {
|
|
65
|
+
console.debug(`[kaven:debug] ${message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function loadConfigApiUrl() {
|
|
69
|
+
try {
|
|
70
|
+
const configPath = path.join(os.homedir(), ".kaven", "config.json");
|
|
71
|
+
if (await fs.pathExists(configPath)) {
|
|
72
|
+
const config = await fs.readJson(configPath);
|
|
73
|
+
if (typeof config.apiUrl === "string" && config.apiUrl) {
|
|
74
|
+
return config.apiUrl;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
async function resolveBaseUrl() {
|
|
82
|
+
if (process.env.KAVEN_API_URL) {
|
|
83
|
+
return process.env.KAVEN_API_URL.replace(/\/$/, "");
|
|
84
|
+
}
|
|
85
|
+
const configUrl = await loadConfigApiUrl();
|
|
86
|
+
if (configUrl) {
|
|
87
|
+
return configUrl.replace(/\/$/, "");
|
|
88
|
+
}
|
|
89
|
+
return DEFAULT_BASE_URL;
|
|
90
|
+
}
|
|
91
|
+
function sleep(ms) {
|
|
92
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
93
|
+
}
|
|
94
|
+
function isRetryable(status) {
|
|
95
|
+
return status >= 500;
|
|
96
|
+
}
|
|
97
|
+
var MarketplaceClient = class {
|
|
98
|
+
baseURLPromise;
|
|
99
|
+
authService;
|
|
100
|
+
constructor(authService) {
|
|
101
|
+
this.authService = authService ?? null;
|
|
102
|
+
this.baseURLPromise = resolveBaseUrl();
|
|
103
|
+
}
|
|
104
|
+
/** Resolve a relative API path to an absolute URL. */
|
|
105
|
+
async resolveUrl(path2) {
|
|
106
|
+
const baseURL = await this.baseURLPromise;
|
|
107
|
+
return `${baseURL}${path2}`;
|
|
108
|
+
}
|
|
109
|
+
// ──────────────────────────────────────────────────────────
|
|
110
|
+
// Core HTTP helpers
|
|
111
|
+
// ──────────────────────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* Make an HTTP request with retry logic and typed error mapping.
|
|
114
|
+
* `authenticated` controls whether Authorization header is attached.
|
|
115
|
+
*/
|
|
116
|
+
async request(method, endpoint, options = {}) {
|
|
117
|
+
const baseURL = await this.baseURLPromise;
|
|
118
|
+
const url = `${baseURL}${endpoint}`;
|
|
119
|
+
const { body, authenticated = false } = options;
|
|
120
|
+
let lastError = null;
|
|
121
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
122
|
+
if (attempt > 0) {
|
|
123
|
+
const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
|
|
124
|
+
debug(`Retry attempt ${attempt} for ${method} ${endpoint} (delay ${delay}ms)`);
|
|
125
|
+
await sleep(delay);
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const headers = {
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
Accept: "application/json"
|
|
131
|
+
};
|
|
132
|
+
if (authenticated && this.authService) {
|
|
133
|
+
const token = await this.authService.getValidToken();
|
|
134
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
135
|
+
}
|
|
136
|
+
debug(`${method} ${url}`);
|
|
137
|
+
const controller = new AbortController();
|
|
138
|
+
const timeoutId = setTimeout(
|
|
139
|
+
() => controller.abort(),
|
|
140
|
+
REQUEST_TIMEOUT_MS
|
|
141
|
+
);
|
|
142
|
+
let response;
|
|
143
|
+
try {
|
|
144
|
+
response = await fetch(url, {
|
|
145
|
+
method,
|
|
146
|
+
headers,
|
|
147
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
148
|
+
signal: controller.signal
|
|
149
|
+
});
|
|
150
|
+
} finally {
|
|
151
|
+
clearTimeout(timeoutId);
|
|
152
|
+
}
|
|
153
|
+
debug(`Response: ${response.status} ${response.statusText}`);
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
const errorText = await response.text().catch(() => "");
|
|
156
|
+
let errorMessage = errorText;
|
|
157
|
+
try {
|
|
158
|
+
const errorJson = JSON.parse(errorText);
|
|
159
|
+
errorMessage = errorJson.message || errorJson.error || errorText;
|
|
160
|
+
} catch {
|
|
161
|
+
}
|
|
162
|
+
switch (response.status) {
|
|
163
|
+
case 401:
|
|
164
|
+
throw new AuthenticationError(
|
|
165
|
+
errorMessage || "Authentication required"
|
|
166
|
+
);
|
|
167
|
+
case 403: {
|
|
168
|
+
let requiredTier = "pro";
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse(errorText);
|
|
171
|
+
requiredTier = parsed.requiredTier || requiredTier;
|
|
172
|
+
} catch {
|
|
173
|
+
}
|
|
174
|
+
throw new LicenseRequiredError(
|
|
175
|
+
requiredTier,
|
|
176
|
+
errorMessage || "License required"
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
case 404:
|
|
180
|
+
throw new NotFoundError(errorMessage || "Resource not found");
|
|
181
|
+
case 429: {
|
|
182
|
+
const retryAfter = parseInt(
|
|
183
|
+
response.headers.get("retry-after") ?? "60",
|
|
184
|
+
10
|
|
185
|
+
);
|
|
186
|
+
throw new RateLimitError(isNaN(retryAfter) ? 60 : retryAfter);
|
|
187
|
+
}
|
|
188
|
+
default:
|
|
189
|
+
if (isRetryable(response.status)) {
|
|
190
|
+
lastError = new ServerError(
|
|
191
|
+
errorMessage || `Server error: ${response.status}`
|
|
192
|
+
);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
throw new ServerError(
|
|
196
|
+
errorMessage || `Server error: ${response.status}`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
201
|
+
if (contentType.includes("application/json")) {
|
|
202
|
+
return await response.json();
|
|
203
|
+
}
|
|
204
|
+
return {};
|
|
205
|
+
} catch (error) {
|
|
206
|
+
if (error instanceof AuthenticationError || error instanceof LicenseRequiredError || error instanceof NotFoundError || error instanceof RateLimitError) {
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
if (error instanceof TypeError || error instanceof Error && error.name === "AbortError") {
|
|
210
|
+
const networkError = new NetworkError(
|
|
211
|
+
error.name === "AbortError" ? "Request timed out after 30s" : `Network error: ${error.message}`
|
|
212
|
+
);
|
|
213
|
+
lastError = networkError;
|
|
214
|
+
if (attempt < MAX_RETRIES) continue;
|
|
215
|
+
throw networkError;
|
|
216
|
+
}
|
|
217
|
+
if (error instanceof ServerError) {
|
|
218
|
+
lastError = error;
|
|
219
|
+
if (attempt < MAX_RETRIES) continue;
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
throw lastError ?? new NetworkError("Request failed after retries");
|
|
226
|
+
}
|
|
227
|
+
// ──────────────────────────────────────────────────────────
|
|
228
|
+
// Auth endpoints (unauthenticated)
|
|
229
|
+
// ──────────────────────────────────────────────────────────
|
|
230
|
+
/**
|
|
231
|
+
* Step 1 of Device Code Flow: Request device code from marketplace.
|
|
232
|
+
*/
|
|
233
|
+
async requestDeviceCode() {
|
|
234
|
+
return this.request("POST", "/auth/device-code", {
|
|
235
|
+
body: { client_id: "kaven-cli" },
|
|
236
|
+
authenticated: false
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Step 2 of Device Code Flow: Poll for access token.
|
|
241
|
+
*/
|
|
242
|
+
async pollDeviceToken(deviceCode) {
|
|
243
|
+
try {
|
|
244
|
+
const baseURL = await this.baseURLPromise;
|
|
245
|
+
const url = `${baseURL}/auth/token`;
|
|
246
|
+
const controller = new AbortController();
|
|
247
|
+
const timeoutId = setTimeout(
|
|
248
|
+
() => controller.abort(),
|
|
249
|
+
REQUEST_TIMEOUT_MS
|
|
250
|
+
);
|
|
251
|
+
let response;
|
|
252
|
+
try {
|
|
253
|
+
response = await fetch(url, {
|
|
254
|
+
method: "POST",
|
|
255
|
+
headers: { "Content-Type": "application/json" },
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
device_code: deviceCode,
|
|
258
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
259
|
+
}),
|
|
260
|
+
signal: controller.signal
|
|
261
|
+
});
|
|
262
|
+
} finally {
|
|
263
|
+
clearTimeout(timeoutId);
|
|
264
|
+
}
|
|
265
|
+
if (response.ok) {
|
|
266
|
+
const tokens = await response.json();
|
|
267
|
+
return { status: "success", tokens };
|
|
268
|
+
}
|
|
269
|
+
const errorData = await response.json().catch(() => ({}));
|
|
270
|
+
const errorCode = errorData.error ?? "unknown_error";
|
|
271
|
+
switch (errorCode) {
|
|
272
|
+
case "authorization_pending":
|
|
273
|
+
return { status: "authorization_pending" };
|
|
274
|
+
case "slow_down":
|
|
275
|
+
return { status: "slow_down" };
|
|
276
|
+
case "access_denied":
|
|
277
|
+
return { status: "access_denied" };
|
|
278
|
+
case "expired_token":
|
|
279
|
+
return { status: "expired_token" };
|
|
280
|
+
default:
|
|
281
|
+
throw new Error(`Unexpected error: ${errorCode}`);
|
|
282
|
+
}
|
|
283
|
+
} catch (error) {
|
|
284
|
+
const nodeError = error;
|
|
285
|
+
if (nodeError.code === "ECONNREFUSED" || nodeError.code === "ENOTFOUND") {
|
|
286
|
+
throw new NetworkError(
|
|
287
|
+
"Network error. Check your connection and try again."
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Refresh access token using refresh token.
|
|
295
|
+
*/
|
|
296
|
+
async refreshToken(refreshToken) {
|
|
297
|
+
return this.request("POST", "/auth/refresh", {
|
|
298
|
+
body: { refresh_token: refreshToken },
|
|
299
|
+
authenticated: false
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
// ──────────────────────────────────────────────────────────
|
|
303
|
+
// Module endpoints (authenticated)
|
|
304
|
+
// ──────────────────────────────────────────────────────────
|
|
305
|
+
/**
|
|
306
|
+
* List available modules with optional filters.
|
|
307
|
+
*/
|
|
308
|
+
async listModules(filters) {
|
|
309
|
+
const params = new URLSearchParams();
|
|
310
|
+
if (filters?.category) params.set("category", filters.category);
|
|
311
|
+
if (filters?.tier) params.set("tier", filters.tier);
|
|
312
|
+
if (filters?.q) params.set("q", filters.q);
|
|
313
|
+
if (filters?.page) params.set("page", String(filters.page));
|
|
314
|
+
if (filters?.pageSize) params.set("pageSize", String(filters.pageSize));
|
|
315
|
+
const query = params.toString();
|
|
316
|
+
const endpoint = `/modules${query ? `?${query}` : ""}`;
|
|
317
|
+
return this.request("GET", endpoint);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Get a single module by slug.
|
|
321
|
+
*/
|
|
322
|
+
async getModule(slug) {
|
|
323
|
+
return this.request("GET", `/modules/${slug}`);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Get a module's manifest for a specific version.
|
|
327
|
+
*/
|
|
328
|
+
async getManifest(slug, version) {
|
|
329
|
+
return this.request(
|
|
330
|
+
"GET",
|
|
331
|
+
`/modules/${slug}/versions/${version}/manifest`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Create a download token for a module release.
|
|
336
|
+
*/
|
|
337
|
+
async createDownloadToken(moduleSlug, version) {
|
|
338
|
+
return this.request("POST", "/download-tokens", {
|
|
339
|
+
body: { moduleSlug, version },
|
|
340
|
+
authenticated: true
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
// ──────────────────────────────────────────────────────────
|
|
344
|
+
// License endpoints
|
|
345
|
+
// ──────────────────────────────────────────────────────────
|
|
346
|
+
/**
|
|
347
|
+
* Validate a license key against the required tier.
|
|
348
|
+
*/
|
|
349
|
+
async validateLicense(licenseKey, requiredTier) {
|
|
350
|
+
const response = await this.request(
|
|
351
|
+
"POST",
|
|
352
|
+
"/licenses/validate",
|
|
353
|
+
{
|
|
354
|
+
body: { licenseKey, requiredTier }
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
return response;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Get full license status including expiry information.
|
|
361
|
+
*/
|
|
362
|
+
async getLicenseStatus(licenseKey) {
|
|
363
|
+
return this.request("GET", `/licenses/status?key=${encodeURIComponent(licenseKey)}`);
|
|
364
|
+
}
|
|
365
|
+
// ──────────────────────────────────────────────────────────
|
|
366
|
+
// Legacy / backward-compat methods
|
|
367
|
+
// ──────────────────────────────────────────────────────────
|
|
368
|
+
/**
|
|
369
|
+
* @deprecated Use getManifest(slug, version) instead.
|
|
370
|
+
*/
|
|
371
|
+
async getModuleManifest(moduleId) {
|
|
372
|
+
try {
|
|
373
|
+
return await this.getManifest(moduleId, "latest");
|
|
374
|
+
} catch (error) {
|
|
375
|
+
if (error instanceof NotFoundError) return null;
|
|
376
|
+
throw error;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Get release details for a specific module version.
|
|
381
|
+
* Returns checksum, signature, and publicKey for verification.
|
|
382
|
+
*/
|
|
383
|
+
async getReleaseInfo(slug, version) {
|
|
384
|
+
return this.request(
|
|
385
|
+
"GET",
|
|
386
|
+
`/modules/${slug}/versions/${version}`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
// ──────────────────────────────────────────────────────────
|
|
390
|
+
// Publish endpoints (authenticated)
|
|
391
|
+
// ──────────────────────────────────────────────────────────
|
|
392
|
+
/**
|
|
393
|
+
* Get a presigned S3 upload URL for a new module release.
|
|
394
|
+
*/
|
|
395
|
+
async getUploadUrl(moduleSlug, version, size) {
|
|
396
|
+
return this.request("POST", "/releases/upload-url", {
|
|
397
|
+
body: { moduleSlug, version, size },
|
|
398
|
+
authenticated: true
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Create a release record after uploading the artifact.
|
|
403
|
+
*/
|
|
404
|
+
async createRelease(data) {
|
|
405
|
+
return this.request("POST", "/releases", {
|
|
406
|
+
body: data,
|
|
407
|
+
authenticated: true
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
// ──────────────────────────────────────────────────────────
|
|
411
|
+
// Checkout / upgrade endpoints (authenticated)
|
|
412
|
+
// ──────────────────────────────────────────────────────────
|
|
413
|
+
/**
|
|
414
|
+
* Create a Paddle checkout session for tier upgrade.
|
|
415
|
+
*/
|
|
416
|
+
async createCheckoutSession(tier, licenseKey) {
|
|
417
|
+
return this.request("POST", "/checkout/session", {
|
|
418
|
+
body: { tier, licenseKey },
|
|
419
|
+
authenticated: true
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Poll the status of an ongoing Paddle checkout session.
|
|
424
|
+
*/
|
|
425
|
+
async getCheckoutStatus(sessionId) {
|
|
426
|
+
return this.request(
|
|
427
|
+
"GET",
|
|
428
|
+
`/checkout/session/${sessionId}/status`,
|
|
429
|
+
{ authenticated: true }
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
// ──────────────────────────────────────────────────────────
|
|
433
|
+
// Categories endpoint (authenticated)
|
|
434
|
+
// ──────────────────────────────────────────────────────────
|
|
435
|
+
/**
|
|
436
|
+
* Get all available module categories.
|
|
437
|
+
*/
|
|
438
|
+
async getCategories() {
|
|
439
|
+
const result = await this.request(
|
|
440
|
+
"GET",
|
|
441
|
+
"/modules/categories",
|
|
442
|
+
{ authenticated: true }
|
|
443
|
+
);
|
|
444
|
+
return result.categories;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export {
|
|
449
|
+
AuthenticationError,
|
|
450
|
+
LicenseRequiredError,
|
|
451
|
+
NotFoundError,
|
|
452
|
+
NetworkError,
|
|
453
|
+
SignatureVerificationError,
|
|
454
|
+
MarketplaceClient
|
|
455
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerAioxCommand = registerAioxCommand;
|
|
4
|
+
const aiox_bootstrap_1 = require("../init/aiox-bootstrap");
|
|
5
|
+
/**
|
|
6
|
+
* Registers the AIOX integration commands
|
|
7
|
+
*/
|
|
8
|
+
function registerAioxCommand(program) {
|
|
9
|
+
const aiox = program
|
|
10
|
+
.command("aiox")
|
|
11
|
+
.description("AIOX integration utilities");
|
|
12
|
+
aiox
|
|
13
|
+
.command("bootstrap")
|
|
14
|
+
.description("Run AIOX environment bootstrap in current project")
|
|
15
|
+
.option("--skip-aiox", "Skip AIOX logic (for testing)")
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
const projectDir = process.cwd();
|
|
18
|
+
await (0, aiox_bootstrap_1.runEnvironmentBootstrap)(projectDir, options);
|
|
19
|
+
});
|
|
20
|
+
}
|