@sylphx/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/README.md +136 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1156 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# @sylphx/cli
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@sylphx/cli)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
|
|
7
|
+
The official CLI tool for the [Sylphx Platform](https://sylphx.com). Deploy, manage logs, environment variables, domains, and more — directly from your terminal.
|
|
8
|
+
|
|
9
|
+
📖 **Full documentation:** [sylphx.com/docs/cli](https://sylphx.com/docs/cli)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @sylphx/cli
|
|
15
|
+
# or
|
|
16
|
+
bun add -g @sylphx/cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Authenticate
|
|
23
|
+
sylphx login
|
|
24
|
+
|
|
25
|
+
# Link your project to a Sylphx app
|
|
26
|
+
cd my-project
|
|
27
|
+
sylphx link
|
|
28
|
+
|
|
29
|
+
# Deploy!
|
|
30
|
+
sylphx deploy
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
### Authentication
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
sylphx login # Opens browser for OAuth flow
|
|
39
|
+
sylphx login --token <TOKEN> # Direct token auth (CI/CD)
|
|
40
|
+
sylphx logout # Clear stored credentials
|
|
41
|
+
sylphx whoami # Show current user + org + app
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Project Management
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
sylphx link # Link current directory to a Sylphx app
|
|
48
|
+
sylphx status # Show deployment status
|
|
49
|
+
sylphx open # Open app in browser
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Deployments
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
sylphx deploy # Deploy to default environment
|
|
56
|
+
sylphx deploy --env staging # Deploy to staging
|
|
57
|
+
sylphx rollback # Rollback to previous deployment
|
|
58
|
+
sylphx rollback --env production # Rollback production
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Logs
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
sylphx logs # Show recent logs
|
|
65
|
+
sylphx logs -f # Follow logs (stream continuously)
|
|
66
|
+
sylphx logs --env staging # Logs for staging environment
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Environment Variables
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
sylphx env list # List all env vars
|
|
73
|
+
sylphx env list --env staging # List staging env vars
|
|
74
|
+
sylphx env set DATABASE_URL=postgres://...
|
|
75
|
+
sylphx env set PORT=3000 --env staging
|
|
76
|
+
sylphx env rm DATABASE_URL # Remove an env var
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Domains
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
sylphx domains list # List custom domains
|
|
83
|
+
sylphx domains add example.com # Add a domain
|
|
84
|
+
sylphx domains rm example.com # Remove a domain
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
Config is stored in `~/.sylphx/config.json`:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"token": "slx_cli_...",
|
|
94
|
+
"defaultOrg": "my-org",
|
|
95
|
+
"apps": {
|
|
96
|
+
"/path/to/project": {
|
|
97
|
+
"appId": "app-uuid",
|
|
98
|
+
"orgId": "org-slug",
|
|
99
|
+
"defaultEnv": "production"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## CI/CD Usage
|
|
106
|
+
|
|
107
|
+
For automated environments, use a pre-generated token:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
sylphx login --token $SYLPHX_API_TOKEN
|
|
111
|
+
sylphx deploy --env production
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Or set the env var:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
SYLPHX_API_TOKEN=slx_cli_... sylphx deploy
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Environment Variables
|
|
121
|
+
|
|
122
|
+
| Variable | Description |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `SYLPHX_API_URL` | Override API base URL (default: `https://sylphx.com`) |
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
bun install
|
|
130
|
+
bun run dev -- --help # Run CLI in dev mode
|
|
131
|
+
bun run build # Build for production
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## License
|
|
135
|
+
|
|
136
|
+
MIT © Sylphx
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,1156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
28
|
+
var import_commander12 = require("commander");
|
|
29
|
+
|
|
30
|
+
// src/commands/deploy.ts
|
|
31
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
32
|
+
var import_commander = require("commander");
|
|
33
|
+
var import_ora = __toESM(require("ora"));
|
|
34
|
+
|
|
35
|
+
// src/config.ts
|
|
36
|
+
var import_node_os = __toESM(require("os"));
|
|
37
|
+
var import_node_path = __toESM(require("path"));
|
|
38
|
+
var import_conf = __toESM(require("conf"));
|
|
39
|
+
var store = new import_conf.default({
|
|
40
|
+
projectName: "sylphx",
|
|
41
|
+
projectSuffix: "",
|
|
42
|
+
cwd: import_node_path.default.join(import_node_os.default.homedir(), ".sylphx"),
|
|
43
|
+
defaults: {
|
|
44
|
+
apps: {}
|
|
45
|
+
},
|
|
46
|
+
schema: {
|
|
47
|
+
token: {
|
|
48
|
+
type: "string"
|
|
49
|
+
},
|
|
50
|
+
defaultOrg: {
|
|
51
|
+
type: "string"
|
|
52
|
+
},
|
|
53
|
+
apps: {
|
|
54
|
+
type: "object"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
var config = {
|
|
59
|
+
/** Get the stored API token */
|
|
60
|
+
getToken() {
|
|
61
|
+
return store.get("token");
|
|
62
|
+
},
|
|
63
|
+
/** Set the API token */
|
|
64
|
+
setToken(token) {
|
|
65
|
+
store.set("token", token);
|
|
66
|
+
},
|
|
67
|
+
/** Clear the API token */
|
|
68
|
+
clearToken() {
|
|
69
|
+
store.delete("token");
|
|
70
|
+
},
|
|
71
|
+
/** Get default org */
|
|
72
|
+
getDefaultOrg() {
|
|
73
|
+
return store.get("defaultOrg");
|
|
74
|
+
},
|
|
75
|
+
/** Set default org */
|
|
76
|
+
setDefaultOrg(org) {
|
|
77
|
+
store.set("defaultOrg", org);
|
|
78
|
+
},
|
|
79
|
+
/** Get linked app for a directory */
|
|
80
|
+
getLinkedApp(dir) {
|
|
81
|
+
const key = dir ?? process.cwd();
|
|
82
|
+
const apps = store.get("apps") ?? {};
|
|
83
|
+
return apps[key];
|
|
84
|
+
},
|
|
85
|
+
/** Link an app to a directory */
|
|
86
|
+
linkApp(link, dir) {
|
|
87
|
+
const key = dir ?? process.cwd();
|
|
88
|
+
const apps = store.get("apps") ?? {};
|
|
89
|
+
apps[key] = link;
|
|
90
|
+
store.set("apps", apps);
|
|
91
|
+
},
|
|
92
|
+
/** Unlink an app from a directory */
|
|
93
|
+
unlinkApp(dir) {
|
|
94
|
+
const key = dir ?? process.cwd();
|
|
95
|
+
const apps = store.get("apps") ?? {};
|
|
96
|
+
delete apps[key];
|
|
97
|
+
store.set("apps", apps);
|
|
98
|
+
},
|
|
99
|
+
/** Get the config file path */
|
|
100
|
+
getConfigPath() {
|
|
101
|
+
return store.path;
|
|
102
|
+
},
|
|
103
|
+
/** Clear all config */
|
|
104
|
+
clear() {
|
|
105
|
+
store.clear();
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// src/api.ts
|
|
110
|
+
var BASE_URL = process.env.SYLPHX_API_URL ?? "https://sylphx.com";
|
|
111
|
+
var API_BASE = `${BASE_URL}/api/console/v1`;
|
|
112
|
+
var ApiError = class extends Error {
|
|
113
|
+
constructor(status, message, body) {
|
|
114
|
+
super(message);
|
|
115
|
+
this.status = status;
|
|
116
|
+
this.body = body;
|
|
117
|
+
this.name = "ApiError";
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
async function request(method, path3, options) {
|
|
121
|
+
const token = options?.token ?? config.getToken();
|
|
122
|
+
if (!token) {
|
|
123
|
+
throw new ApiError(401, "Not authenticated. Run `sylphx login` first.");
|
|
124
|
+
}
|
|
125
|
+
let url = `${API_BASE}${path3}`;
|
|
126
|
+
if (options?.query) {
|
|
127
|
+
const params = new URLSearchParams(options.query);
|
|
128
|
+
url += `?${params.toString()}`;
|
|
129
|
+
}
|
|
130
|
+
const headers = {
|
|
131
|
+
Authorization: `Bearer ${token}`,
|
|
132
|
+
"Content-Type": "application/json",
|
|
133
|
+
"User-Agent": "sylphx-cli/0.1.0"
|
|
134
|
+
};
|
|
135
|
+
const res = await fetch(url, {
|
|
136
|
+
method,
|
|
137
|
+
headers,
|
|
138
|
+
body: options?.body ? JSON.stringify(options.body) : void 0
|
|
139
|
+
});
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
let body;
|
|
142
|
+
try {
|
|
143
|
+
body = await res.json();
|
|
144
|
+
} catch {
|
|
145
|
+
body = await res.text();
|
|
146
|
+
}
|
|
147
|
+
const message = typeof body === "object" && body !== null && "message" in body ? String(body.message) : `HTTP ${res.status}`;
|
|
148
|
+
throw new ApiError(res.status, message, body);
|
|
149
|
+
}
|
|
150
|
+
if (res.status === 204) {
|
|
151
|
+
return void 0;
|
|
152
|
+
}
|
|
153
|
+
return res.json();
|
|
154
|
+
}
|
|
155
|
+
var api = {
|
|
156
|
+
/** Get deployment status for an app */
|
|
157
|
+
async getDeploymentStatus(appId) {
|
|
158
|
+
return request("GET", "/deployments/status", {
|
|
159
|
+
query: { appId }
|
|
160
|
+
});
|
|
161
|
+
},
|
|
162
|
+
/** Trigger a deploy for an environment */
|
|
163
|
+
async triggerDeploy(envId) {
|
|
164
|
+
return request("POST", `/deployments/${envId}/deploy`);
|
|
165
|
+
},
|
|
166
|
+
/** Get deployment history for an environment */
|
|
167
|
+
async getDeploymentHistory(envId) {
|
|
168
|
+
return request("GET", `/deployments/${envId}/history`);
|
|
169
|
+
},
|
|
170
|
+
/** Rollback to previous deployment */
|
|
171
|
+
async rollback(envId) {
|
|
172
|
+
return request("POST", `/deployments/${envId}/rollback`);
|
|
173
|
+
},
|
|
174
|
+
/** List env vars for an environment */
|
|
175
|
+
async listEnvVars(envId) {
|
|
176
|
+
return request("GET", `/deployments/${envId}/envvars`);
|
|
177
|
+
},
|
|
178
|
+
/** Set an env var */
|
|
179
|
+
async setEnvVar(envId, key, value) {
|
|
180
|
+
return request("POST", `/deployments/${envId}/envvars`, {
|
|
181
|
+
body: { key, value }
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
/** Delete an env var */
|
|
185
|
+
async deleteEnvVar(envId, key) {
|
|
186
|
+
return request(
|
|
187
|
+
"DELETE",
|
|
188
|
+
`/deployments/${envId}/envvars/${encodeURIComponent(key)}`
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
/** List domains for an environment */
|
|
192
|
+
async listDomains(envId) {
|
|
193
|
+
return request("GET", `/deployments/${envId}/domains`);
|
|
194
|
+
},
|
|
195
|
+
/** Add a domain */
|
|
196
|
+
async addDomain(envId, domain) {
|
|
197
|
+
return request("POST", `/deployments/${envId}/domains`, {
|
|
198
|
+
body: { domain }
|
|
199
|
+
});
|
|
200
|
+
},
|
|
201
|
+
/** Remove a domain */
|
|
202
|
+
async removeDomain(envId, domain) {
|
|
203
|
+
return request(
|
|
204
|
+
"DELETE",
|
|
205
|
+
`/deployments/${envId}/domains/${encodeURIComponent(domain)}`
|
|
206
|
+
);
|
|
207
|
+
},
|
|
208
|
+
/** Get current user and orgs */
|
|
209
|
+
async whoami() {
|
|
210
|
+
return request("GET", "/auth/me");
|
|
211
|
+
},
|
|
212
|
+
/** List apps for an org */
|
|
213
|
+
async listApps(orgId) {
|
|
214
|
+
return request("GET", "/apps", {
|
|
215
|
+
query: { orgId }
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
/** CLI auth: poll for token */
|
|
219
|
+
async pollCliAuth(code) {
|
|
220
|
+
const res = await fetch(
|
|
221
|
+
`${BASE_URL}/api/auth/cli/poll?code=${encodeURIComponent(code)}`,
|
|
222
|
+
{
|
|
223
|
+
headers: { "User-Agent": "sylphx-cli/0.1.0" }
|
|
224
|
+
}
|
|
225
|
+
);
|
|
226
|
+
if (!res.ok) {
|
|
227
|
+
throw new ApiError(res.status, "Failed to poll auth status");
|
|
228
|
+
}
|
|
229
|
+
return res.json();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
function createLogStream(envId) {
|
|
233
|
+
return {
|
|
234
|
+
url: `${API_BASE}/deployments/${envId}/logs`,
|
|
235
|
+
token: config.getToken()
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/utils/logs.ts
|
|
240
|
+
var import_chalk = __toESM(require("chalk"));
|
|
241
|
+
function formatLogEntry(raw) {
|
|
242
|
+
let entry;
|
|
243
|
+
try {
|
|
244
|
+
entry = JSON.parse(raw);
|
|
245
|
+
} catch {
|
|
246
|
+
return ` ${import_chalk.default.white(raw)}`;
|
|
247
|
+
}
|
|
248
|
+
const message = entry.message ?? entry.msg ?? raw;
|
|
249
|
+
const level = (entry.level ?? "info").toLowerCase();
|
|
250
|
+
const timestamp = entry.timestamp ?? entry.ts;
|
|
251
|
+
const prefix = timestamp ? `${import_chalk.default.dim(`[${new Date(timestamp).toLocaleTimeString()}]`)} ` : "";
|
|
252
|
+
const status = entry.status?.toLowerCase();
|
|
253
|
+
const effectiveLevel = status === "error" || status === "failed" ? "error" : status === "success" || status === "done" ? "success" : level;
|
|
254
|
+
switch (effectiveLevel) {
|
|
255
|
+
case "error":
|
|
256
|
+
return ` ${prefix}${import_chalk.default.red("\u2717")} ${import_chalk.default.red(message)}`;
|
|
257
|
+
case "warn":
|
|
258
|
+
return ` ${prefix}${import_chalk.default.yellow("\u26A0")} ${import_chalk.default.yellow(message)}`;
|
|
259
|
+
case "success":
|
|
260
|
+
return ` ${prefix}${import_chalk.default.green("\u2713")} ${import_chalk.default.green(message)}`;
|
|
261
|
+
case "debug":
|
|
262
|
+
return ` ${prefix}${import_chalk.default.dim(message)}`;
|
|
263
|
+
default:
|
|
264
|
+
return ` ${prefix}${import_chalk.default.white(message)}`;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function streamLogs(url, token, opts = {}) {
|
|
268
|
+
return new Promise((resolve) => {
|
|
269
|
+
const { EventSource } = require("eventsource");
|
|
270
|
+
const headers = {
|
|
271
|
+
"User-Agent": "sylphx-cli/0.1.0"
|
|
272
|
+
};
|
|
273
|
+
if (token) {
|
|
274
|
+
headers.Authorization = `Bearer ${token}`;
|
|
275
|
+
}
|
|
276
|
+
const es = new EventSource(url, {
|
|
277
|
+
fetch: (input, init) => fetch(input, {
|
|
278
|
+
...init,
|
|
279
|
+
headers: {
|
|
280
|
+
...typeof init?.headers === "object" ? init.headers : {},
|
|
281
|
+
...headers
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
});
|
|
285
|
+
let succeeded = false;
|
|
286
|
+
let timeout;
|
|
287
|
+
const finish = (success) => {
|
|
288
|
+
clearTimeout(timeout);
|
|
289
|
+
es.close();
|
|
290
|
+
resolve(success);
|
|
291
|
+
};
|
|
292
|
+
if (opts.exitOnDone) {
|
|
293
|
+
timeout = setTimeout(
|
|
294
|
+
() => {
|
|
295
|
+
finish(succeeded);
|
|
296
|
+
},
|
|
297
|
+
5 * 60 * 1e3
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
es.addEventListener("message", (event) => {
|
|
301
|
+
const data = String(event.data ?? "");
|
|
302
|
+
if (!data || data === "ping") return;
|
|
303
|
+
console.log(formatLogEntry(data));
|
|
304
|
+
try {
|
|
305
|
+
const parsed = JSON.parse(data);
|
|
306
|
+
const status = parsed.status?.toLowerCase() ?? "";
|
|
307
|
+
const level = parsed.level?.toLowerCase() ?? "";
|
|
308
|
+
const msg = (parsed.message ?? parsed.msg ?? "").toLowerCase();
|
|
309
|
+
if (status === "success" || status === "done" || level === "success" || msg.includes("deployment successful") || msg.includes("build complete")) {
|
|
310
|
+
succeeded = true;
|
|
311
|
+
if (opts.exitOnDone) {
|
|
312
|
+
setTimeout(() => finish(true), 500);
|
|
313
|
+
}
|
|
314
|
+
} else if (status === "error" || status === "failed" || level === "error" || msg.includes("deployment failed") || msg.includes("build failed")) {
|
|
315
|
+
succeeded = false;
|
|
316
|
+
if (opts.exitOnDone) {
|
|
317
|
+
setTimeout(() => finish(false), 500);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
} catch {
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
es.addEventListener("done", () => {
|
|
324
|
+
if (opts.exitOnDone) {
|
|
325
|
+
finish(succeeded);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
es.addEventListener("error", (event) => {
|
|
329
|
+
const errorEvent = event;
|
|
330
|
+
if (errorEvent.message) {
|
|
331
|
+
console.log(import_chalk.default.red(` Stream error: ${errorEvent.message}`));
|
|
332
|
+
}
|
|
333
|
+
if (opts.exitOnDone) {
|
|
334
|
+
finish(succeeded);
|
|
335
|
+
} else {
|
|
336
|
+
es.close();
|
|
337
|
+
resolve(false);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
if (!opts.follow) {
|
|
341
|
+
timeout = setTimeout(() => finish(succeeded), 1e4);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/commands/deploy.ts
|
|
347
|
+
var deployCommand = new import_commander.Command("deploy").description("Trigger a deployment for the linked app").option("--env <env>", "Environment to deploy to (e.g. production, staging)").action(async (opts) => {
|
|
348
|
+
const token = config.getToken();
|
|
349
|
+
if (!token) {
|
|
350
|
+
console.log(import_chalk2.default.red("Not authenticated. Run `sylphx login` first."));
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
const linked = config.getLinkedApp();
|
|
354
|
+
if (!linked) {
|
|
355
|
+
console.log(import_chalk2.default.red("No app linked. Run `sylphx link` first."));
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
const env = opts.env ?? linked.defaultEnv;
|
|
359
|
+
console.log("");
|
|
360
|
+
console.log(
|
|
361
|
+
import_chalk2.default.bold(
|
|
362
|
+
` Deploying ${import_chalk2.default.cyan(linked.appId)} \u2192 ${import_chalk2.default.white(env)}`
|
|
363
|
+
)
|
|
364
|
+
);
|
|
365
|
+
console.log("");
|
|
366
|
+
const spinner = (0, import_ora.default)("Triggering deployment...").start();
|
|
367
|
+
let envId;
|
|
368
|
+
try {
|
|
369
|
+
envId = linked.appId;
|
|
370
|
+
const status = await api.getDeploymentStatus(linked.appId);
|
|
371
|
+
envId = linked.appId;
|
|
372
|
+
const result = await api.triggerDeploy(linked.appId);
|
|
373
|
+
spinner.succeed(import_chalk2.default.green(`Deployment triggered: ${result.status}`));
|
|
374
|
+
if (result.deploymentId) {
|
|
375
|
+
console.log(import_chalk2.default.dim(` Deployment ID: ${result.deploymentId}`));
|
|
376
|
+
}
|
|
377
|
+
} catch (err) {
|
|
378
|
+
spinner.fail(
|
|
379
|
+
import_chalk2.default.red(
|
|
380
|
+
`Deploy failed: ${err instanceof Error ? err.message : String(err)}`
|
|
381
|
+
)
|
|
382
|
+
);
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
console.log("");
|
|
386
|
+
console.log(import_chalk2.default.bold(" Build Logs"));
|
|
387
|
+
console.log(import_chalk2.default.dim(` ${"\u2500".repeat(50)}`));
|
|
388
|
+
console.log("");
|
|
389
|
+
const logStream = createLogStream(envId);
|
|
390
|
+
const success = await streamLogs(logStream.url, logStream.token, {
|
|
391
|
+
follow: true,
|
|
392
|
+
exitOnDone: true
|
|
393
|
+
});
|
|
394
|
+
if (!success) {
|
|
395
|
+
console.log("");
|
|
396
|
+
console.log(import_chalk2.default.red(" Deployment failed."));
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
console.log("");
|
|
400
|
+
console.log(import_chalk2.default.green.bold(" \u2713 Deployment successful!"));
|
|
401
|
+
console.log("");
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// src/commands/domains.ts
|
|
405
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
406
|
+
var import_commander2 = require("commander");
|
|
407
|
+
var import_ora2 = __toESM(require("ora"));
|
|
408
|
+
function requireLinkedApp() {
|
|
409
|
+
const token = config.getToken();
|
|
410
|
+
if (!token) {
|
|
411
|
+
console.log(import_chalk3.default.red("Not authenticated. Run `sylphx login` first."));
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
const linked = config.getLinkedApp();
|
|
415
|
+
if (!linked) {
|
|
416
|
+
console.log(import_chalk3.default.red("No app linked. Run `sylphx link` first."));
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
return linked;
|
|
420
|
+
}
|
|
421
|
+
var domainsListCommand = new import_commander2.Command("list").description("List custom domains").action(async () => {
|
|
422
|
+
const linked = requireLinkedApp();
|
|
423
|
+
const envId = linked.appId;
|
|
424
|
+
const spinner = (0, import_ora2.default)("Fetching domains...").start();
|
|
425
|
+
try {
|
|
426
|
+
const domains = await api.listDomains(envId);
|
|
427
|
+
spinner.stop();
|
|
428
|
+
if (domains.length === 0) {
|
|
429
|
+
console.log(import_chalk3.default.dim("\n No custom domains configured.\n"));
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
console.log("");
|
|
433
|
+
console.log(import_chalk3.default.bold(" Custom Domains"));
|
|
434
|
+
console.log(import_chalk3.default.dim(` ${"\u2500".repeat(50)}`));
|
|
435
|
+
console.log("");
|
|
436
|
+
for (const d of domains) {
|
|
437
|
+
const status = d.status ? import_chalk3.default.dim(` [${d.status}]`) : "";
|
|
438
|
+
const ssl = d.ssl ? import_chalk3.default.green(" \u{1F512}") : import_chalk3.default.yellow(" \u26A0 no SSL");
|
|
439
|
+
console.log(` ${import_chalk3.default.cyan(d.domain)}${status}${ssl}`);
|
|
440
|
+
}
|
|
441
|
+
console.log("");
|
|
442
|
+
} catch (err) {
|
|
443
|
+
spinner.fail(
|
|
444
|
+
import_chalk3.default.red(
|
|
445
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
446
|
+
)
|
|
447
|
+
);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
var domainsAddCommand = new import_commander2.Command("add").description("Add a custom domain").argument("<domain>", "Domain to add (e.g. example.com)").action(async (domain) => {
|
|
452
|
+
const linked = requireLinkedApp();
|
|
453
|
+
const envId = linked.appId;
|
|
454
|
+
const spinner = (0, import_ora2.default)(`Adding domain ${domain}...`).start();
|
|
455
|
+
try {
|
|
456
|
+
const result = await api.addDomain(envId, domain);
|
|
457
|
+
spinner.succeed(
|
|
458
|
+
import_chalk3.default.green(`\u2713 Added domain ${import_chalk3.default.bold(result.domain ?? domain)}`)
|
|
459
|
+
);
|
|
460
|
+
if (result.status) {
|
|
461
|
+
console.log(import_chalk3.default.dim(` Status: ${result.status}`));
|
|
462
|
+
}
|
|
463
|
+
} catch (err) {
|
|
464
|
+
spinner.fail(
|
|
465
|
+
import_chalk3.default.red(
|
|
466
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
467
|
+
)
|
|
468
|
+
);
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
var domainsRmCommand = new import_commander2.Command("rm").description("Remove a custom domain").argument("<domain>", "Domain to remove (e.g. example.com)").action(async (domain) => {
|
|
473
|
+
const linked = requireLinkedApp();
|
|
474
|
+
const envId = linked.appId;
|
|
475
|
+
const spinner = (0, import_ora2.default)(`Removing domain ${domain}...`).start();
|
|
476
|
+
try {
|
|
477
|
+
await api.removeDomain(envId, domain);
|
|
478
|
+
spinner.succeed(import_chalk3.default.green(`\u2713 Removed domain ${import_chalk3.default.bold(domain)}`));
|
|
479
|
+
} catch (err) {
|
|
480
|
+
spinner.fail(
|
|
481
|
+
import_chalk3.default.red(
|
|
482
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
483
|
+
)
|
|
484
|
+
);
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
var domainsCommand = new import_commander2.Command("domains").description("Manage custom domains").addCommand(domainsListCommand).addCommand(domainsAddCommand).addCommand(domainsRmCommand);
|
|
489
|
+
|
|
490
|
+
// src/commands/env.ts
|
|
491
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
492
|
+
var import_commander3 = require("commander");
|
|
493
|
+
var import_ora3 = __toESM(require("ora"));
|
|
494
|
+
function requireLinkedApp2() {
|
|
495
|
+
const token = config.getToken();
|
|
496
|
+
if (!token) {
|
|
497
|
+
console.log(import_chalk4.default.red("Not authenticated. Run `sylphx login` first."));
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
const linked = config.getLinkedApp();
|
|
501
|
+
if (!linked) {
|
|
502
|
+
console.log(import_chalk4.default.red("No app linked. Run `sylphx link` first."));
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
return linked;
|
|
506
|
+
}
|
|
507
|
+
var envListCommand = new import_commander3.Command("list").description("List environment variables").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
|
|
508
|
+
const linked = requireLinkedApp2();
|
|
509
|
+
const env = opts.env ?? linked.defaultEnv;
|
|
510
|
+
const envId = linked.appId;
|
|
511
|
+
const spinner = (0, import_ora3.default)(`Fetching env vars [${env}]...`).start();
|
|
512
|
+
try {
|
|
513
|
+
const vars = await api.listEnvVars(envId);
|
|
514
|
+
spinner.stop();
|
|
515
|
+
if (vars.length === 0) {
|
|
516
|
+
console.log(import_chalk4.default.dim("\n No environment variables set.\n"));
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
console.log("");
|
|
520
|
+
console.log(import_chalk4.default.bold(` Environment Variables [${import_chalk4.default.white(env)}]`));
|
|
521
|
+
console.log(import_chalk4.default.dim(` ${"\u2500".repeat(50)}`));
|
|
522
|
+
console.log("");
|
|
523
|
+
const maxKeyLen = Math.max(...vars.map((v) => v.key.length));
|
|
524
|
+
for (const v of vars) {
|
|
525
|
+
const key = import_chalk4.default.cyan(v.key.padEnd(maxKeyLen));
|
|
526
|
+
const value = v.isSecret ? import_chalk4.default.dim("****** (secret)") : import_chalk4.default.white(v.value);
|
|
527
|
+
console.log(` ${key} ${value}`);
|
|
528
|
+
}
|
|
529
|
+
console.log("");
|
|
530
|
+
} catch (err) {
|
|
531
|
+
spinner.fail(
|
|
532
|
+
import_chalk4.default.red(
|
|
533
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
534
|
+
)
|
|
535
|
+
);
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
var envSetCommand = new import_commander3.Command("set").description("Set an environment variable (KEY=VALUE)").argument(
|
|
540
|
+
"<assignment>",
|
|
541
|
+
"Key=Value assignment, e.g. DATABASE_URL=postgres://..."
|
|
542
|
+
).option("--env <env>", "Environment (e.g. production, staging)").option("--secret", "Mark as secret").action(
|
|
543
|
+
async (assignment, opts) => {
|
|
544
|
+
const linked = requireLinkedApp2();
|
|
545
|
+
const env = opts.env ?? linked.defaultEnv;
|
|
546
|
+
const envId = linked.appId;
|
|
547
|
+
const eqIdx = assignment.indexOf("=");
|
|
548
|
+
if (eqIdx < 1) {
|
|
549
|
+
console.log(
|
|
550
|
+
import_chalk4.default.red(
|
|
551
|
+
"Invalid format. Use KEY=VALUE (e.g. sylphx env set DATABASE_URL=postgres://...)"
|
|
552
|
+
)
|
|
553
|
+
);
|
|
554
|
+
process.exit(1);
|
|
555
|
+
}
|
|
556
|
+
const key = assignment.slice(0, eqIdx).trim();
|
|
557
|
+
const value = assignment.slice(eqIdx + 1);
|
|
558
|
+
if (!key) {
|
|
559
|
+
console.log(import_chalk4.default.red("Key cannot be empty."));
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
const spinner = (0, import_ora3.default)(`Setting ${key} [${env}]...`).start();
|
|
563
|
+
try {
|
|
564
|
+
await api.setEnvVar(envId, key, value);
|
|
565
|
+
spinner.succeed(import_chalk4.default.green(`\u2713 Set ${import_chalk4.default.bold(key)} in ${env}`));
|
|
566
|
+
} catch (err) {
|
|
567
|
+
spinner.fail(
|
|
568
|
+
import_chalk4.default.red(
|
|
569
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
570
|
+
)
|
|
571
|
+
);
|
|
572
|
+
process.exit(1);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
);
|
|
576
|
+
var envRmCommand = new import_commander3.Command("rm").description("Remove an environment variable").argument("<key>", "Variable name to remove").option("--env <env>", "Environment (e.g. production, staging)").action(async (key, opts) => {
|
|
577
|
+
const linked = requireLinkedApp2();
|
|
578
|
+
const env = opts.env ?? linked.defaultEnv;
|
|
579
|
+
const envId = linked.appId;
|
|
580
|
+
const spinner = (0, import_ora3.default)(`Removing ${key} [${env}]...`).start();
|
|
581
|
+
try {
|
|
582
|
+
await api.deleteEnvVar(envId, key);
|
|
583
|
+
spinner.succeed(import_chalk4.default.green(`\u2713 Removed ${import_chalk4.default.bold(key)} from ${env}`));
|
|
584
|
+
} catch (err) {
|
|
585
|
+
spinner.fail(
|
|
586
|
+
import_chalk4.default.red(
|
|
587
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
588
|
+
)
|
|
589
|
+
);
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
var envCommand = new import_commander3.Command("env").description("Manage environment variables").addCommand(envListCommand).addCommand(envSetCommand).addCommand(envRmCommand);
|
|
594
|
+
|
|
595
|
+
// src/commands/link.ts
|
|
596
|
+
var import_node_fs = __toESM(require("fs"));
|
|
597
|
+
var import_node_path2 = __toESM(require("path"));
|
|
598
|
+
var import_node_readline = __toESM(require("readline"));
|
|
599
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
600
|
+
var import_commander4 = require("commander");
|
|
601
|
+
var import_ora4 = __toESM(require("ora"));
|
|
602
|
+
function readPackageName() {
|
|
603
|
+
try {
|
|
604
|
+
const pkgPath = import_node_path2.default.join(process.cwd(), "package.json");
|
|
605
|
+
const pkg = JSON.parse(import_node_fs.default.readFileSync(pkgPath, "utf-8"));
|
|
606
|
+
return pkg.name;
|
|
607
|
+
} catch {
|
|
608
|
+
return void 0;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function prompt(question) {
|
|
612
|
+
return new Promise((resolve) => {
|
|
613
|
+
const rl = import_node_readline.default.createInterface({
|
|
614
|
+
input: process.stdin,
|
|
615
|
+
output: process.stdout
|
|
616
|
+
});
|
|
617
|
+
rl.question(question, (answer) => {
|
|
618
|
+
rl.close();
|
|
619
|
+
resolve(answer.trim());
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
var linkCommand = new import_commander4.Command("link").description("Link current directory to a Sylphx app").option("--org <org>", "Organization slug").action(async (opts) => {
|
|
624
|
+
const token = config.getToken();
|
|
625
|
+
if (!token) {
|
|
626
|
+
console.log(import_chalk5.default.red("Not authenticated. Run `sylphx login` first."));
|
|
627
|
+
process.exit(1);
|
|
628
|
+
}
|
|
629
|
+
const pkgName = readPackageName();
|
|
630
|
+
if (pkgName) {
|
|
631
|
+
console.log(import_chalk5.default.dim(` Detected package: ${import_chalk5.default.white(pkgName)}`));
|
|
632
|
+
}
|
|
633
|
+
let orgId = opts.org ?? config.getDefaultOrg();
|
|
634
|
+
if (!orgId) {
|
|
635
|
+
const spinner = (0, import_ora4.default)("Fetching your organizations...").start();
|
|
636
|
+
let orgs = [];
|
|
637
|
+
try {
|
|
638
|
+
const me = await api.whoami();
|
|
639
|
+
orgs = me.orgs;
|
|
640
|
+
spinner.stop();
|
|
641
|
+
} catch (err) {
|
|
642
|
+
spinner.fail(
|
|
643
|
+
import_chalk5.default.red(
|
|
644
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
645
|
+
)
|
|
646
|
+
);
|
|
647
|
+
process.exit(1);
|
|
648
|
+
}
|
|
649
|
+
if (orgs.length === 0) {
|
|
650
|
+
console.log(import_chalk5.default.red("No organizations found."));
|
|
651
|
+
process.exit(1);
|
|
652
|
+
}
|
|
653
|
+
if (orgs.length === 1 && orgs[0]) {
|
|
654
|
+
orgId = orgs[0].slug;
|
|
655
|
+
console.log(import_chalk5.default.dim(` Using org: ${import_chalk5.default.white(orgId)}`));
|
|
656
|
+
} else {
|
|
657
|
+
console.log(import_chalk5.default.bold("\n Your organizations:\n"));
|
|
658
|
+
orgs.forEach((org, i) => {
|
|
659
|
+
console.log(
|
|
660
|
+
` ${import_chalk5.default.cyan(String(i + 1))}. ${org.slug} ${import_chalk5.default.dim(`(${org.name})`)}`
|
|
661
|
+
);
|
|
662
|
+
});
|
|
663
|
+
console.log("");
|
|
664
|
+
const choice = await prompt(" Select org (number): ");
|
|
665
|
+
const idx = Number.parseInt(choice, 10) - 1;
|
|
666
|
+
if (Number.isNaN(idx) || idx < 0 || idx >= orgs.length || !orgs[idx]) {
|
|
667
|
+
console.log(import_chalk5.default.red("Invalid selection."));
|
|
668
|
+
process.exit(1);
|
|
669
|
+
}
|
|
670
|
+
orgId = orgs[idx]?.slug;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const spinner2 = (0, import_ora4.default)(`Fetching apps for org ${orgId}...`).start();
|
|
674
|
+
let apps = [];
|
|
675
|
+
try {
|
|
676
|
+
apps = await api.listApps(orgId);
|
|
677
|
+
spinner2.stop();
|
|
678
|
+
} catch (err) {
|
|
679
|
+
spinner2.fail(
|
|
680
|
+
import_chalk5.default.red(
|
|
681
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
682
|
+
)
|
|
683
|
+
);
|
|
684
|
+
process.exit(1);
|
|
685
|
+
}
|
|
686
|
+
if (apps.length === 0) {
|
|
687
|
+
console.log(import_chalk5.default.yellow("No apps found for this org."));
|
|
688
|
+
process.exit(1);
|
|
689
|
+
}
|
|
690
|
+
console.log(import_chalk5.default.bold("\n Available apps:\n"));
|
|
691
|
+
apps.forEach((app, i) => {
|
|
692
|
+
console.log(
|
|
693
|
+
` ${import_chalk5.default.cyan(String(i + 1))}. ${app.slug} ${import_chalk5.default.dim(`(${app.name})`)}`
|
|
694
|
+
);
|
|
695
|
+
});
|
|
696
|
+
console.log("");
|
|
697
|
+
const appChoice = await prompt(" Select app (number): ");
|
|
698
|
+
const appIdx = Number.parseInt(appChoice, 10) - 1;
|
|
699
|
+
if (Number.isNaN(appIdx) || appIdx < 0 || appIdx >= apps.length || !apps[appIdx]) {
|
|
700
|
+
console.log(import_chalk5.default.red("Invalid selection."));
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
const selectedApp = apps[appIdx];
|
|
704
|
+
if (!selectedApp) {
|
|
705
|
+
console.log(import_chalk5.default.red("Invalid selection."));
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
let defaultEnv = "production";
|
|
709
|
+
const envs = selectedApp.environments ?? [];
|
|
710
|
+
if (envs.length > 1) {
|
|
711
|
+
console.log(import_chalk5.default.bold("\n Available environments:\n"));
|
|
712
|
+
envs.forEach((env, i) => {
|
|
713
|
+
console.log(
|
|
714
|
+
` ${import_chalk5.default.cyan(String(i + 1))}. ${env.slug} ${import_chalk5.default.dim(`(${env.name})`)}`
|
|
715
|
+
);
|
|
716
|
+
});
|
|
717
|
+
console.log("");
|
|
718
|
+
const envChoice = await prompt(
|
|
719
|
+
" Select default environment (number, or Enter for production): "
|
|
720
|
+
);
|
|
721
|
+
if (envChoice) {
|
|
722
|
+
const envIdx = Number.parseInt(envChoice, 10) - 1;
|
|
723
|
+
if (!Number.isNaN(envIdx) && envIdx >= 0 && envIdx < envs.length && envs[envIdx]) {
|
|
724
|
+
defaultEnv = envs[envIdx]?.slug;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
config.linkApp({
|
|
729
|
+
appId: selectedApp.id,
|
|
730
|
+
orgId,
|
|
731
|
+
defaultEnv
|
|
732
|
+
});
|
|
733
|
+
if (orgId) {
|
|
734
|
+
config.setDefaultOrg(orgId);
|
|
735
|
+
}
|
|
736
|
+
console.log("");
|
|
737
|
+
console.log(import_chalk5.default.green(`\u2713 Linked to ${import_chalk5.default.bold(selectedApp.slug)}`));
|
|
738
|
+
console.log(
|
|
739
|
+
import_chalk5.default.dim(
|
|
740
|
+
` App ID: ${selectedApp.id} | Env: ${defaultEnv} | Dir: ${process.cwd()}`
|
|
741
|
+
)
|
|
742
|
+
);
|
|
743
|
+
console.log("");
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
// src/commands/login.ts
|
|
747
|
+
var import_node_crypto = __toESM(require("crypto"));
|
|
748
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
749
|
+
var import_commander5 = require("commander");
|
|
750
|
+
var import_ora5 = __toESM(require("ora"));
|
|
751
|
+
var BASE_URL2 = process.env.SYLPHX_API_URL ?? "https://sylphx.com";
|
|
752
|
+
var POLL_INTERVAL_MS = 2e3;
|
|
753
|
+
var POLL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
754
|
+
async function initCode(code) {
|
|
755
|
+
const res = await fetch(`${BASE_URL2}/api/auth/cli/init`, {
|
|
756
|
+
method: "POST",
|
|
757
|
+
headers: {
|
|
758
|
+
"Content-Type": "application/json",
|
|
759
|
+
"User-Agent": "sylphx-cli/0.1.0"
|
|
760
|
+
},
|
|
761
|
+
body: JSON.stringify({ code })
|
|
762
|
+
});
|
|
763
|
+
if (!res.ok) {
|
|
764
|
+
throw new Error(`Failed to initialize auth code (HTTP ${res.status})`);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
var loginCommand = new import_commander5.Command("login").description("Authenticate with the Sylphx platform").option(
|
|
768
|
+
"--token <token>",
|
|
769
|
+
"Authenticate with a pre-existing API token (for CI/CD)"
|
|
770
|
+
).action(async (opts) => {
|
|
771
|
+
if (opts.token) {
|
|
772
|
+
const spinner2 = (0, import_ora5.default)("Validating token...").start();
|
|
773
|
+
try {
|
|
774
|
+
config.setToken(opts.token);
|
|
775
|
+
const me = await api.whoami();
|
|
776
|
+
config.setToken(opts.token);
|
|
777
|
+
spinner2.succeed(
|
|
778
|
+
import_chalk6.default.green(`Authenticated as ${import_chalk6.default.bold(me.user.email)}`)
|
|
779
|
+
);
|
|
780
|
+
} catch (err) {
|
|
781
|
+
config.clearToken();
|
|
782
|
+
spinner2.fail(import_chalk6.default.red("Invalid token"));
|
|
783
|
+
process.exit(1);
|
|
784
|
+
}
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const code = import_node_crypto.default.randomBytes(16).toString("hex").slice(0, 20).toUpperCase();
|
|
788
|
+
const authUrl = `${BASE_URL2}/cli-auth?code=${code}`;
|
|
789
|
+
console.log("");
|
|
790
|
+
console.log(import_chalk6.default.bold(" Sylphx CLI Authentication"));
|
|
791
|
+
console.log("");
|
|
792
|
+
console.log(" Opening browser to:");
|
|
793
|
+
console.log(` ${import_chalk6.default.cyan(authUrl)}`);
|
|
794
|
+
console.log("");
|
|
795
|
+
console.log(` One-time code: ${import_chalk6.default.yellow.bold(code)}`);
|
|
796
|
+
console.log("");
|
|
797
|
+
try {
|
|
798
|
+
await initCode(code);
|
|
799
|
+
} catch (err) {
|
|
800
|
+
console.log(
|
|
801
|
+
import_chalk6.default.yellow(
|
|
802
|
+
` Warning: Could not register code: ${err instanceof Error ? err.message : String(err)}`
|
|
803
|
+
)
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
try {
|
|
807
|
+
const { default: open } = await import("open");
|
|
808
|
+
await open(authUrl);
|
|
809
|
+
} catch {
|
|
810
|
+
console.log(
|
|
811
|
+
import_chalk6.default.dim(
|
|
812
|
+
" Could not open browser automatically. Please visit the URL above."
|
|
813
|
+
)
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
const spinner = (0, import_ora5.default)("Waiting for browser authorization...").start();
|
|
817
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
818
|
+
while (Date.now() < deadline) {
|
|
819
|
+
await sleep(POLL_INTERVAL_MS);
|
|
820
|
+
try {
|
|
821
|
+
const result = await api.pollCliAuth(code);
|
|
822
|
+
if (result.status === "authorized" && result.token) {
|
|
823
|
+
config.setToken(result.token);
|
|
824
|
+
try {
|
|
825
|
+
const me = await api.whoami();
|
|
826
|
+
spinner.succeed(
|
|
827
|
+
import_chalk6.default.green(`Authenticated as ${import_chalk6.default.bold(me.user.email)}`)
|
|
828
|
+
);
|
|
829
|
+
} catch {
|
|
830
|
+
spinner.succeed(import_chalk6.default.green("Authenticated successfully"));
|
|
831
|
+
}
|
|
832
|
+
console.log("");
|
|
833
|
+
console.log(
|
|
834
|
+
` Token stored at: ${import_chalk6.default.dim(config.getConfigPath())}`
|
|
835
|
+
);
|
|
836
|
+
console.log("");
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
} catch (err) {
|
|
840
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
841
|
+
if (msg.includes("404") || msg.includes("expired")) {
|
|
842
|
+
spinner.fail(import_chalk6.default.red("Code expired or invalid. Please try again."));
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
spinner.fail(import_chalk6.default.red("Authentication timed out. Please try again."));
|
|
848
|
+
process.exit(1);
|
|
849
|
+
});
|
|
850
|
+
function sleep(ms) {
|
|
851
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/commands/logout.ts
|
|
855
|
+
var import_chalk7 = __toESM(require("chalk"));
|
|
856
|
+
var import_commander6 = require("commander");
|
|
857
|
+
var logoutCommand = new import_commander6.Command("logout").description("Clear stored credentials").action(() => {
|
|
858
|
+
const token = config.getToken();
|
|
859
|
+
if (!token) {
|
|
860
|
+
console.log(import_chalk7.default.yellow("You are not currently logged in."));
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
config.clearToken();
|
|
864
|
+
console.log(import_chalk7.default.green("\u2713 Logged out successfully."));
|
|
865
|
+
console.log(
|
|
866
|
+
import_chalk7.default.dim(` Credentials cleared from ${config.getConfigPath()}`)
|
|
867
|
+
);
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
// src/commands/logs.ts
|
|
871
|
+
var import_chalk8 = __toESM(require("chalk"));
|
|
872
|
+
var import_commander7 = require("commander");
|
|
873
|
+
var logsCommand = new import_commander7.Command("logs").description("Stream build/runtime logs").option("--env <env>", "Environment (e.g. production, staging)").option("-f, --follow", "Follow log output (stream continuously)").action(async (opts) => {
|
|
874
|
+
const token = config.getToken();
|
|
875
|
+
if (!token) {
|
|
876
|
+
console.log(import_chalk8.default.red("Not authenticated. Run `sylphx login` first."));
|
|
877
|
+
process.exit(1);
|
|
878
|
+
}
|
|
879
|
+
const linked = config.getLinkedApp();
|
|
880
|
+
if (!linked) {
|
|
881
|
+
console.log(import_chalk8.default.red("No app linked. Run `sylphx link` first."));
|
|
882
|
+
process.exit(1);
|
|
883
|
+
}
|
|
884
|
+
const env = opts.env ?? linked.defaultEnv;
|
|
885
|
+
const envId = linked.appId;
|
|
886
|
+
console.log("");
|
|
887
|
+
console.log(
|
|
888
|
+
import_chalk8.default.bold(
|
|
889
|
+
` Logs for ${import_chalk8.default.cyan(linked.appId)} [${import_chalk8.default.white(env)}]${opts.follow ? import_chalk8.default.dim(" (following...)") : ""}`
|
|
890
|
+
)
|
|
891
|
+
);
|
|
892
|
+
console.log(import_chalk8.default.dim(` ${"\u2500".repeat(50)}`));
|
|
893
|
+
console.log("");
|
|
894
|
+
const logStream = createLogStream(envId);
|
|
895
|
+
await streamLogs(logStream.url, logStream.token, {
|
|
896
|
+
follow: opts.follow ?? false,
|
|
897
|
+
exitOnDone: false
|
|
898
|
+
});
|
|
899
|
+
if (!opts.follow) {
|
|
900
|
+
console.log("");
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// src/commands/open.ts
|
|
905
|
+
var import_chalk9 = __toESM(require("chalk"));
|
|
906
|
+
var import_commander8 = require("commander");
|
|
907
|
+
var import_ora6 = __toESM(require("ora"));
|
|
908
|
+
var openCommand = new import_commander8.Command("open").description("Open the app in your browser").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
|
|
909
|
+
const token = config.getToken();
|
|
910
|
+
if (!token) {
|
|
911
|
+
console.log(import_chalk9.default.red("Not authenticated. Run `sylphx login` first."));
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
const linked = config.getLinkedApp();
|
|
915
|
+
if (!linked) {
|
|
916
|
+
console.log(import_chalk9.default.red("No app linked. Run `sylphx link` first."));
|
|
917
|
+
process.exit(1);
|
|
918
|
+
}
|
|
919
|
+
const spinner = (0, import_ora6.default)("Fetching app URL...").start();
|
|
920
|
+
let url;
|
|
921
|
+
try {
|
|
922
|
+
const status = await api.getDeploymentStatus(linked.appId);
|
|
923
|
+
spinner.stop();
|
|
924
|
+
if (!status.url) {
|
|
925
|
+
url = `https://sylphx.com/console/apps/${linked.appId}`;
|
|
926
|
+
console.log(import_chalk9.default.yellow(" No URL found, opening console instead."));
|
|
927
|
+
} else {
|
|
928
|
+
url = status.url;
|
|
929
|
+
}
|
|
930
|
+
} catch {
|
|
931
|
+
spinner.stop();
|
|
932
|
+
url = `https://sylphx.com/console/apps/${linked.appId}`;
|
|
933
|
+
}
|
|
934
|
+
console.log(` Opening ${import_chalk9.default.cyan(url)}`);
|
|
935
|
+
try {
|
|
936
|
+
const { default: open } = await import("open");
|
|
937
|
+
await open(url);
|
|
938
|
+
} catch (err) {
|
|
939
|
+
console.log(
|
|
940
|
+
import_chalk9.default.red(
|
|
941
|
+
`Failed to open browser: ${err instanceof Error ? err.message : String(err)}`
|
|
942
|
+
)
|
|
943
|
+
);
|
|
944
|
+
console.log(import_chalk9.default.dim(` URL: ${url}`));
|
|
945
|
+
process.exit(1);
|
|
946
|
+
}
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
// src/commands/rollback.ts
|
|
950
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
951
|
+
var import_commander9 = require("commander");
|
|
952
|
+
var import_ora7 = __toESM(require("ora"));
|
|
953
|
+
var rollbackCommand = new import_commander9.Command("rollback").description("Rollback to the previous deployment").option("--env <env>", "Environment (e.g. production, staging)").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
954
|
+
const token = config.getToken();
|
|
955
|
+
if (!token) {
|
|
956
|
+
console.log(import_chalk10.default.red("Not authenticated. Run `sylphx login` first."));
|
|
957
|
+
process.exit(1);
|
|
958
|
+
}
|
|
959
|
+
const linked = config.getLinkedApp();
|
|
960
|
+
if (!linked) {
|
|
961
|
+
console.log(import_chalk10.default.red("No app linked. Run `sylphx link` first."));
|
|
962
|
+
process.exit(1);
|
|
963
|
+
}
|
|
964
|
+
const env = opts.env ?? linked.defaultEnv;
|
|
965
|
+
const envId = linked.appId;
|
|
966
|
+
if (!opts.force) {
|
|
967
|
+
const readline2 = await import("readline");
|
|
968
|
+
const rl = readline2.createInterface({
|
|
969
|
+
input: process.stdin,
|
|
970
|
+
output: process.stdout
|
|
971
|
+
});
|
|
972
|
+
await new Promise((resolve, reject) => {
|
|
973
|
+
rl.question(
|
|
974
|
+
import_chalk10.default.yellow(
|
|
975
|
+
` Rollback ${import_chalk10.default.bold(linked.appId)} [${env}]? (y/N) `
|
|
976
|
+
),
|
|
977
|
+
(answer) => {
|
|
978
|
+
rl.close();
|
|
979
|
+
if (answer.toLowerCase() !== "y") {
|
|
980
|
+
console.log(import_chalk10.default.dim(" Cancelled."));
|
|
981
|
+
process.exit(0);
|
|
982
|
+
}
|
|
983
|
+
resolve();
|
|
984
|
+
}
|
|
985
|
+
);
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
const spinner = (0, import_ora7.default)(`Rolling back ${linked.appId} [${env}]...`).start();
|
|
989
|
+
try {
|
|
990
|
+
const result = await api.rollback(envId);
|
|
991
|
+
spinner.succeed(import_chalk10.default.green(`\u2713 Rollback initiated: ${result.status}`));
|
|
992
|
+
if (result.deploymentId) {
|
|
993
|
+
console.log(import_chalk10.default.dim(` Deployment ID: ${result.deploymentId}`));
|
|
994
|
+
}
|
|
995
|
+
if (result.message) {
|
|
996
|
+
console.log(import_chalk10.default.dim(` ${result.message}`));
|
|
997
|
+
}
|
|
998
|
+
} catch (err) {
|
|
999
|
+
spinner.fail(
|
|
1000
|
+
import_chalk10.default.red(
|
|
1001
|
+
`Rollback failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1002
|
+
)
|
|
1003
|
+
);
|
|
1004
|
+
process.exit(1);
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
// src/commands/status.ts
|
|
1009
|
+
var import_chalk11 = __toESM(require("chalk"));
|
|
1010
|
+
var import_commander10 = require("commander");
|
|
1011
|
+
var import_ora8 = __toESM(require("ora"));
|
|
1012
|
+
function statusColor(status) {
|
|
1013
|
+
const s = status.toLowerCase();
|
|
1014
|
+
if (s === "running" || s === "active" || s === "healthy" || s === "success") {
|
|
1015
|
+
return import_chalk11.default.green(status);
|
|
1016
|
+
}
|
|
1017
|
+
if (s === "deploying" || s === "building" || s === "pending") {
|
|
1018
|
+
return import_chalk11.default.yellow(status);
|
|
1019
|
+
}
|
|
1020
|
+
if (s === "error" || s === "failed" || s === "stopped") {
|
|
1021
|
+
return import_chalk11.default.red(status);
|
|
1022
|
+
}
|
|
1023
|
+
return import_chalk11.default.white(status);
|
|
1024
|
+
}
|
|
1025
|
+
var statusCommand = new import_commander10.Command("status").description("Show deployment status").option("--env <env>", "Environment (e.g. production, staging)").action(async (opts) => {
|
|
1026
|
+
const token = config.getToken();
|
|
1027
|
+
if (!token) {
|
|
1028
|
+
console.log(import_chalk11.default.red("Not authenticated. Run `sylphx login` first."));
|
|
1029
|
+
process.exit(1);
|
|
1030
|
+
}
|
|
1031
|
+
const linked = config.getLinkedApp();
|
|
1032
|
+
if (!linked) {
|
|
1033
|
+
console.log(import_chalk11.default.red("No app linked. Run `sylphx link` first."));
|
|
1034
|
+
process.exit(1);
|
|
1035
|
+
}
|
|
1036
|
+
const env = opts.env ?? linked.defaultEnv;
|
|
1037
|
+
const spinner = (0, import_ora8.default)(`Fetching status [${env}]...`).start();
|
|
1038
|
+
try {
|
|
1039
|
+
const status = await api.getDeploymentStatus(linked.appId);
|
|
1040
|
+
spinner.stop();
|
|
1041
|
+
console.log("");
|
|
1042
|
+
console.log(import_chalk11.default.bold(" Deployment Status"));
|
|
1043
|
+
console.log(import_chalk11.default.dim(` ${"\u2500".repeat(40)}`));
|
|
1044
|
+
console.log("");
|
|
1045
|
+
console.log(` App: ${import_chalk11.default.cyan(linked.appId)}`);
|
|
1046
|
+
console.log(` Environment: ${import_chalk11.default.white(env)}`);
|
|
1047
|
+
console.log(` Status: ${statusColor(status.status)}`);
|
|
1048
|
+
if (status.deployedAt) {
|
|
1049
|
+
const date = new Date(status.deployedAt);
|
|
1050
|
+
console.log(` Deployed: ${import_chalk11.default.white(date.toLocaleString())}`);
|
|
1051
|
+
}
|
|
1052
|
+
if (status.url) {
|
|
1053
|
+
console.log(` URL: ${import_chalk11.default.cyan(status.url)}`);
|
|
1054
|
+
}
|
|
1055
|
+
console.log("");
|
|
1056
|
+
} catch (err) {
|
|
1057
|
+
spinner.fail(
|
|
1058
|
+
import_chalk11.default.red(
|
|
1059
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1060
|
+
)
|
|
1061
|
+
);
|
|
1062
|
+
process.exit(1);
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
// src/commands/whoami.ts
|
|
1067
|
+
var import_chalk12 = __toESM(require("chalk"));
|
|
1068
|
+
var import_commander11 = require("commander");
|
|
1069
|
+
var import_ora9 = __toESM(require("ora"));
|
|
1070
|
+
var whoamiCommand = new import_commander11.Command("whoami").description("Show current user, org, and linked app").action(async () => {
|
|
1071
|
+
const token = config.getToken();
|
|
1072
|
+
if (!token) {
|
|
1073
|
+
console.log(import_chalk12.default.red("Not authenticated. Run `sylphx login` first."));
|
|
1074
|
+
process.exit(1);
|
|
1075
|
+
}
|
|
1076
|
+
const spinner = (0, import_ora9.default)("Fetching account info...").start();
|
|
1077
|
+
try {
|
|
1078
|
+
const me = await api.whoami();
|
|
1079
|
+
spinner.stop();
|
|
1080
|
+
console.log("");
|
|
1081
|
+
console.log(import_chalk12.default.bold(" Account"));
|
|
1082
|
+
console.log(` User: ${import_chalk12.default.cyan(me.user.email)}`);
|
|
1083
|
+
console.log(` Name: ${import_chalk12.default.white(me.user.name)}`);
|
|
1084
|
+
console.log(` ID: ${import_chalk12.default.dim(me.user.id)}`);
|
|
1085
|
+
if (me.orgs.length > 0) {
|
|
1086
|
+
console.log("");
|
|
1087
|
+
console.log(import_chalk12.default.bold(" Organizations"));
|
|
1088
|
+
for (const org of me.orgs) {
|
|
1089
|
+
console.log(
|
|
1090
|
+
` ${import_chalk12.default.cyan(org.slug)} ${import_chalk12.default.dim(`(${org.name})`)}`
|
|
1091
|
+
);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
const linkedApp = config.getLinkedApp();
|
|
1095
|
+
if (linkedApp) {
|
|
1096
|
+
console.log("");
|
|
1097
|
+
console.log(import_chalk12.default.bold(" Linked App"));
|
|
1098
|
+
console.log(` App: ${import_chalk12.default.cyan(linkedApp.appId)}`);
|
|
1099
|
+
console.log(` Org: ${import_chalk12.default.white(linkedApp.orgId)}`);
|
|
1100
|
+
console.log(` Env: ${import_chalk12.default.white(linkedApp.defaultEnv)}`);
|
|
1101
|
+
} else {
|
|
1102
|
+
console.log("");
|
|
1103
|
+
console.log(
|
|
1104
|
+
import_chalk12.default.dim(" No app linked. Run `sylphx link` to link one.")
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
console.log("");
|
|
1108
|
+
} catch (err) {
|
|
1109
|
+
spinner.fail(
|
|
1110
|
+
import_chalk12.default.red(
|
|
1111
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
1112
|
+
)
|
|
1113
|
+
);
|
|
1114
|
+
process.exit(1);
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
// src/index.ts
|
|
1119
|
+
var program = new import_commander12.Command();
|
|
1120
|
+
program.name("sylphx").description(
|
|
1121
|
+
`${import_chalk13.default.bold("Sylphx Platform CLI")} \u2014 deploy and manage your applications`
|
|
1122
|
+
).version("0.1.0", "-v, --version", "Print version").helpOption("-h, --help", "Show help").addHelpText(
|
|
1123
|
+
"after",
|
|
1124
|
+
`
|
|
1125
|
+
${import_chalk13.default.bold("Examples:")}
|
|
1126
|
+
${import_chalk13.default.cyan("sylphx login")} Authenticate with Sylphx
|
|
1127
|
+
${import_chalk13.default.cyan("sylphx link")} Link current project to an app
|
|
1128
|
+
${import_chalk13.default.cyan("sylphx deploy")} Deploy to production
|
|
1129
|
+
${import_chalk13.default.cyan("sylphx deploy --env staging")} Deploy to staging
|
|
1130
|
+
${import_chalk13.default.cyan("sylphx logs -f")} Stream live logs
|
|
1131
|
+
${import_chalk13.default.cyan("sylphx env list")} List environment variables
|
|
1132
|
+
${import_chalk13.default.cyan("sylphx env set PORT=3000")} Set an env var
|
|
1133
|
+
${import_chalk13.default.cyan("sylphx status")} Check deployment status
|
|
1134
|
+
|
|
1135
|
+
${import_chalk13.default.bold("Documentation:")}
|
|
1136
|
+
${import_chalk13.default.underline("https://docs.sylphx.com/cli")}
|
|
1137
|
+
`
|
|
1138
|
+
);
|
|
1139
|
+
program.addCommand(loginCommand).addCommand(logoutCommand).addCommand(whoamiCommand).addCommand(linkCommand).addCommand(deployCommand).addCommand(logsCommand).addCommand(envCommand).addCommand(domainsCommand).addCommand(rollbackCommand).addCommand(openCommand).addCommand(statusCommand);
|
|
1140
|
+
program.on("command:*", (operands) => {
|
|
1141
|
+
console.error(
|
|
1142
|
+
import_chalk13.default.red(`
|
|
1143
|
+
Unknown command: ${import_chalk13.default.bold(operands[0] ?? "")}
|
|
1144
|
+
`)
|
|
1145
|
+
);
|
|
1146
|
+
console.log(` Run ${import_chalk13.default.cyan("sylphx --help")} for usage.
|
|
1147
|
+
`);
|
|
1148
|
+
process.exit(1);
|
|
1149
|
+
});
|
|
1150
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
1151
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1152
|
+
console.error(import_chalk13.default.red(`
|
|
1153
|
+
Error: ${msg}
|
|
1154
|
+
`));
|
|
1155
|
+
process.exit(1);
|
|
1156
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sylphx/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Sylphx Platform CLI — deploy, manage logs, env vars, and more",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sylphx": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup src/index.ts --format cjs --dts",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:coverage": "vitest run --coverage",
|
|
16
|
+
"clean": "rm -rf dist",
|
|
17
|
+
"prepublishOnly": "tsup src/index.ts --format cjs --dts"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public",
|
|
25
|
+
"registry": "https://registry.npmjs.org/"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"chalk": "^5.3.0",
|
|
29
|
+
"commander": "^12.1.0",
|
|
30
|
+
"conf": "^13.0.0",
|
|
31
|
+
"eventsource": "^2.0.2",
|
|
32
|
+
"open": "^10.1.0",
|
|
33
|
+
"ora": "^8.1.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/eventsource": "^1.1.15",
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
39
|
+
"tsup": "^8.3.0",
|
|
40
|
+
"tsx": "^4.19.0",
|
|
41
|
+
"typescript": "^5.7.0",
|
|
42
|
+
"vitest": "^2.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|