@irtio/cli 0.5.2 → 0.7.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/api-keys-UTLYMZYN.js +222 -0
- package/dist/api.d.ts +52 -0
- package/dist/api.js +15 -0
- package/dist/bundle.js +1 -1
- package/dist/{chunk-GBNHBWES.js → chunk-BQBOBFBO.js} +10 -6
- package/dist/chunk-IDF46P7R.js +98 -0
- package/dist/{chunk-32QTPKVT.js → chunk-JL235KIE.js} +1 -1
- package/dist/chunk-OCVALOGK.js +31 -0
- package/dist/{chunk-KRQUAEN2.js → chunk-OTSFRVJN.js} +12 -6
- package/dist/chunk-UPHQM6NZ.js +72 -0
- package/dist/chunk-WFMRNGO5.js +481 -0
- package/dist/chunk-ZK5JLUD4.js +94 -0
- package/dist/credentials.d.ts +61 -0
- package/dist/credentials.js +20 -0
- package/dist/delete-project-MXUYNGAO.js +118 -0
- package/dist/deploy.d.ts +151 -0
- package/dist/{deploy-3SABPL3T.js → deploy.js} +324 -44
- package/dist/{dev-QJOGXLKM.js → dev-AUZ4OLA3.js} +3066 -211
- package/dist/index.js +121 -25
- package/dist/init.d.ts +1 -1
- package/dist/init.js +20 -4
- package/dist/{keys-XBORZAPI.js → keys-NXRIBJZP.js} +8 -4
- package/dist/leaderboard-ZBJBKETM.js +406 -0
- package/dist/{login-3EXB4CGX.js → login-3RVN5PPN.js} +12 -5
- package/dist/{logs-2EOXLNWF.js → logs-AT7G6YRH.js} +9 -5
- package/dist/{migrate-WUO2GBMX.js → migrate-FMXTRVUV.js} +12 -8
- package/dist/ratings-XBLX2MUW.js +297 -0
- package/dist/{rollback-GA6UY772.js → rollback-LI4TDLQA.js} +9 -5
- package/dist/rooms-52Q5KBUS.js +411 -0
- package/dist/simulate.d.ts +254 -6
- package/dist/simulate.js +925 -64
- package/dist/{static-deploy-5TBH4VNA.js → static-deploy-7UCYINJB.js} +6 -4
- package/dist/status-JZGKH2P6.js +219 -0
- package/dist/usage-7S447INI.js +213 -0
- package/dist/{whoami-CI5D5RCC.js → whoami-UFSWPWK6.js} +8 -4
- package/package.json +23 -7
- package/dist/chunk-BPE452KF.js +0 -180
- package/dist/chunk-TV66QHFP.js +0 -167
- package/dist/rooms-B66LQIIF.js +0 -226
|
@@ -4,10 +4,12 @@ import {
|
|
|
4
4
|
runStaticDeploy,
|
|
5
5
|
staticDeploy,
|
|
6
6
|
walkStaticDir
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-BQBOBFBO.js";
|
|
8
|
+
import "./chunk-WFMRNGO5.js";
|
|
9
|
+
import "./chunk-JL235KIE.js";
|
|
10
|
+
import "./chunk-OCVALOGK.js";
|
|
11
|
+
import "./chunk-IDF46P7R.js";
|
|
12
|
+
import "./chunk-UPHQM6NZ.js";
|
|
11
13
|
export {
|
|
12
14
|
USAGE,
|
|
13
15
|
parseStaticDeployArgs,
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readProjectConfig
|
|
3
|
+
} from "./chunk-JL235KIE.js";
|
|
4
|
+
import {
|
|
5
|
+
HelpRequested,
|
|
6
|
+
helpFor,
|
|
7
|
+
helpRequested
|
|
8
|
+
} from "./chunk-OCVALOGK.js";
|
|
9
|
+
import {
|
|
10
|
+
createApiClient,
|
|
11
|
+
isLoginRequired
|
|
12
|
+
} from "./chunk-IDF46P7R.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveControlUrlForUser
|
|
15
|
+
} from "./chunk-UPHQM6NZ.js";
|
|
16
|
+
|
|
17
|
+
// src/status.ts
|
|
18
|
+
import pc from "picocolors";
|
|
19
|
+
var USAGE = `usage: irtio status [options]
|
|
20
|
+
|
|
21
|
+
Shows a project's live metrics.
|
|
22
|
+
|
|
23
|
+
With no --room: the latest value of every tenant-wide metric, then a per-room table of
|
|
24
|
+
connections and tick health, built from a 30s-per-tenant sample rollup.
|
|
25
|
+
|
|
26
|
+
With --room <id>: that one room's recent history instead, newest first.
|
|
27
|
+
|
|
28
|
+
options:
|
|
29
|
+
--room <id> show one room's recent history instead of the project-wide summary
|
|
30
|
+
--project <id> project id (default: the project file)
|
|
31
|
+
-c, --config <f> the project file to read (default irtio.json)
|
|
32
|
+
--url <control> control plane (default: your stored login)
|
|
33
|
+
-h, --help print this
|
|
34
|
+
`;
|
|
35
|
+
function parseStatusArgs(args) {
|
|
36
|
+
if (helpRequested(args)) throw helpFor(USAGE);
|
|
37
|
+
const parsed = {};
|
|
38
|
+
for (let i = 0; i < args.length; i++) {
|
|
39
|
+
const arg = args[i];
|
|
40
|
+
switch (arg) {
|
|
41
|
+
case "--room": {
|
|
42
|
+
const value = args[++i];
|
|
43
|
+
if (value === void 0) throw new Error("irtio status: --room needs a value");
|
|
44
|
+
parsed.room = value;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
case "--project": {
|
|
48
|
+
const value = args[++i];
|
|
49
|
+
if (value === void 0) throw new Error("irtio status: --project needs a value");
|
|
50
|
+
parsed.project = value;
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case "-c":
|
|
54
|
+
case "--config": {
|
|
55
|
+
const value = args[++i];
|
|
56
|
+
if (value === void 0 || value === "")
|
|
57
|
+
throw new Error("irtio status: --config needs a value");
|
|
58
|
+
parsed.config = value;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "--url": {
|
|
62
|
+
const value = args[++i];
|
|
63
|
+
if (value === void 0) throw new Error("irtio status: --url needs a value");
|
|
64
|
+
parsed.url = value;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
default:
|
|
68
|
+
throw new Error(`irtio status: unknown option ${JSON.stringify(arg)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return parsed;
|
|
72
|
+
}
|
|
73
|
+
function formatTimestamp(iso) {
|
|
74
|
+
return iso.replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
75
|
+
}
|
|
76
|
+
function formatSampleAge(iso, now) {
|
|
77
|
+
const ms = now - Date.parse(iso);
|
|
78
|
+
if (!Number.isFinite(ms) || ms < 0) return "-";
|
|
79
|
+
const secs = Math.floor(ms / 1e3);
|
|
80
|
+
if (secs < 60) return `${secs}s ago`;
|
|
81
|
+
const mins = Math.floor(secs / 60);
|
|
82
|
+
if (mins < 60) return `${mins}m ago`;
|
|
83
|
+
const hours = Math.floor(mins / 60);
|
|
84
|
+
if (hours < 24) return `${hours}h ago`;
|
|
85
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
86
|
+
}
|
|
87
|
+
function formatAggregateMetrics(rows) {
|
|
88
|
+
if (rows.length === 0) return [pc.dim("no metrics reported yet")];
|
|
89
|
+
const newest = /* @__PURE__ */ new Map();
|
|
90
|
+
for (const row of rows) if (!newest.has(row.name)) newest.set(row.name, row);
|
|
91
|
+
const names = [...newest.keys()].sort();
|
|
92
|
+
const nameWidth = Math.max(6, ...names.map((n) => n.length));
|
|
93
|
+
const out = [pc.dim(`${"METRIC".padEnd(nameWidth)} VALUE AT`)];
|
|
94
|
+
for (const name of names) {
|
|
95
|
+
const row = newest.get(name);
|
|
96
|
+
out.push(
|
|
97
|
+
`${name.padEnd(nameWidth)} ${String(row.value).padEnd(10)} ${pc.dim(formatTimestamp(row.at))}`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
function formatRoomsSnapshot(rows, now) {
|
|
103
|
+
if (rows.length === 0) {
|
|
104
|
+
return [
|
|
105
|
+
pc.dim("no per-room samples yet"),
|
|
106
|
+
pc.dim("an old guest rootfs, or a project with no rooms, both look like this")
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
const idWidth = Math.max(4, ...rows.map((r) => r.roomId.length));
|
|
110
|
+
const field = (metrics, name) => metrics[name] === void 0 ? "-" : String(metrics[name]);
|
|
111
|
+
const bytes = (metrics, name) => {
|
|
112
|
+
const value = metrics[name];
|
|
113
|
+
if (value === void 0) return "-";
|
|
114
|
+
if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
115
|
+
if (value >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
|
|
116
|
+
return String(value);
|
|
117
|
+
};
|
|
118
|
+
const out = [
|
|
119
|
+
pc.dim(
|
|
120
|
+
`${"ROOM".padEnd(idWidth)} CONN TICKS MAXTICKMS OVERRUNS ELU IN OUT SAMPLE AGE`
|
|
121
|
+
)
|
|
122
|
+
];
|
|
123
|
+
for (const r of rows) {
|
|
124
|
+
out.push(
|
|
125
|
+
`${r.roomId.padEnd(idWidth)} ${field(r.metrics, "connections").padEnd(4)} ${field(r.metrics, "ticks").padEnd(10)} ${field(r.metrics, "maxTickMs").padEnd(10)} ${field(r.metrics, "overruns").padEnd(8)} ${field(r.metrics, "workerElu").padEnd(6)} ${bytes(r.metrics, "ingressBytes").padEnd(8)} ${bytes(r.metrics, "egressBytes").padEnd(8)} ${pc.dim(formatSampleAge(r.at, now))}`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
var ROOM_HISTORY_NAMES = ["ticks", "maxTickMs", "overruns", "connections"];
|
|
131
|
+
function formatRoomHistory(rows) {
|
|
132
|
+
if (rows.length === 0) {
|
|
133
|
+
return [pc.dim("no samples for this room in the 7-day retention window")];
|
|
134
|
+
}
|
|
135
|
+
const order = [];
|
|
136
|
+
const samples = /* @__PURE__ */ new Map();
|
|
137
|
+
for (const row of rows) {
|
|
138
|
+
let sample = samples.get(row.at);
|
|
139
|
+
if (sample === void 0) {
|
|
140
|
+
sample = {};
|
|
141
|
+
samples.set(row.at, sample);
|
|
142
|
+
order.push(row.at);
|
|
143
|
+
}
|
|
144
|
+
sample[row.name] = row.value;
|
|
145
|
+
}
|
|
146
|
+
const out = [];
|
|
147
|
+
for (const at of order.slice(0, 10)) {
|
|
148
|
+
const sample = samples.get(at);
|
|
149
|
+
const parts = ROOM_HISTORY_NAMES.filter((name) => sample[name] !== void 0).map(
|
|
150
|
+
(name) => `${name}=${sample[name]}`
|
|
151
|
+
);
|
|
152
|
+
out.push(`${pc.dim(formatTimestamp(at))} ${parts.join(" ")}`);
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
async function runStatus(options) {
|
|
157
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
158
|
+
const controlUrl = await resolveControlUrlForUser(options.controlUrl);
|
|
159
|
+
const client = options.client ?? await createApiClient(controlUrl);
|
|
160
|
+
if (options.room !== void 0) {
|
|
161
|
+
const rows = await client.get(
|
|
162
|
+
`/v1/projects/${options.project}/metrics`,
|
|
163
|
+
{ room: options.room }
|
|
164
|
+
);
|
|
165
|
+
for (const line of formatRoomHistory(rows)) log(line);
|
|
166
|
+
return { kind: "room", rows };
|
|
167
|
+
}
|
|
168
|
+
const [project, metrics, rooms] = await Promise.all([
|
|
169
|
+
client.get(`/v1/projects/${options.project}`),
|
|
170
|
+
client.get(`/v1/projects/${options.project}/metrics`),
|
|
171
|
+
client.get(`/v1/projects/${options.project}/metrics/rooms`)
|
|
172
|
+
]);
|
|
173
|
+
log(`shards: ${typeof project.shards === "number" ? project.shards : 1}`);
|
|
174
|
+
log("");
|
|
175
|
+
for (const line of formatAggregateMetrics(metrics)) log(line);
|
|
176
|
+
log("");
|
|
177
|
+
for (const line of formatRoomsSnapshot(rooms, options.now ?? Date.now())) log(line);
|
|
178
|
+
return { kind: "project", metrics, rooms };
|
|
179
|
+
}
|
|
180
|
+
async function status(args, deps = {}) {
|
|
181
|
+
const log = deps.log ?? ((line) => console.log(line));
|
|
182
|
+
const errorLog = deps.errorLog ?? ((line) => console.error(line));
|
|
183
|
+
try {
|
|
184
|
+
const parsed = parseStatusArgs(args);
|
|
185
|
+
const config = await readProjectConfig(process.cwd(), "irtio status", parsed.config);
|
|
186
|
+
const project = parsed.project ?? config.project;
|
|
187
|
+
if (project === void 0) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
"irtio status: no project id. Pass --project or run this from a project with irtio.json"
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
await runStatus({
|
|
193
|
+
project,
|
|
194
|
+
log,
|
|
195
|
+
...parsed.room !== void 0 ? { room: parsed.room } : {},
|
|
196
|
+
...parsed.url !== void 0 ? { controlUrl: parsed.url } : {},
|
|
197
|
+
...deps.client !== void 0 ? { client: deps.client } : {}
|
|
198
|
+
});
|
|
199
|
+
} catch (err) {
|
|
200
|
+
if (err instanceof HelpRequested) {
|
|
201
|
+
log(err.usage);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (isLoginRequired(err)) {
|
|
205
|
+
errorLog(pc.red("not logged in"));
|
|
206
|
+
errorLog("run: irtio login");
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
errorLog(pc.red(err instanceof Error ? err.message : String(err)));
|
|
211
|
+
process.exitCode = 1;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
export {
|
|
215
|
+
USAGE,
|
|
216
|
+
parseStatusArgs,
|
|
217
|
+
runStatus,
|
|
218
|
+
status
|
|
219
|
+
};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readProjectConfig
|
|
3
|
+
} from "./chunk-JL235KIE.js";
|
|
4
|
+
import {
|
|
5
|
+
HelpRequested,
|
|
6
|
+
helpFor,
|
|
7
|
+
helpRequested
|
|
8
|
+
} from "./chunk-OCVALOGK.js";
|
|
9
|
+
import {
|
|
10
|
+
createApiClient,
|
|
11
|
+
isLoginRequired
|
|
12
|
+
} from "./chunk-IDF46P7R.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveControlUrlForUser
|
|
15
|
+
} from "./chunk-UPHQM6NZ.js";
|
|
16
|
+
|
|
17
|
+
// src/usage.ts
|
|
18
|
+
import pc from "picocolors";
|
|
19
|
+
var USAGE = `usage: irtio usage [options]
|
|
20
|
+
|
|
21
|
+
Shows what a project used this billing period, per meter.
|
|
22
|
+
|
|
23
|
+
The period is the calendar month in UTC. Meters nothing has reported print "not reported"
|
|
24
|
+
rather than zero, because those are different facts.
|
|
25
|
+
|
|
26
|
+
On the card plan each meter shows its included allowance, the overage rate, and an estimated
|
|
27
|
+
overage cost where usage is past the bundle. The free plan shows no dollar figures: it stops
|
|
28
|
+
at its caps instead of billing.
|
|
29
|
+
|
|
30
|
+
options:
|
|
31
|
+
--windows also print the hourly windows behind the totals
|
|
32
|
+
--project <id> project id (default: the project file)
|
|
33
|
+
-c, --config <f> the project file to read (default irtio.json)
|
|
34
|
+
--url <control> control plane (default: your stored login)
|
|
35
|
+
-h, --help print this
|
|
36
|
+
`;
|
|
37
|
+
function parseUsageArgs(args) {
|
|
38
|
+
if (helpRequested(args)) throw helpFor(USAGE);
|
|
39
|
+
const parsed = {};
|
|
40
|
+
for (let i = 0; i < args.length; i++) {
|
|
41
|
+
const arg = args[i];
|
|
42
|
+
switch (arg) {
|
|
43
|
+
case "--windows":
|
|
44
|
+
parsed.windows = true;
|
|
45
|
+
break;
|
|
46
|
+
case "--project": {
|
|
47
|
+
const value = args[++i];
|
|
48
|
+
if (value === void 0) throw new Error("irtio usage: --project needs a value");
|
|
49
|
+
parsed.project = value;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case "-c":
|
|
53
|
+
case "--config": {
|
|
54
|
+
const value = args[++i];
|
|
55
|
+
if (value === void 0 || value === "")
|
|
56
|
+
throw new Error("irtio usage: --config needs a value");
|
|
57
|
+
parsed.config = value;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "--url": {
|
|
61
|
+
const value = args[++i];
|
|
62
|
+
if (value === void 0) throw new Error("irtio usage: --url needs a value");
|
|
63
|
+
parsed.url = value;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
default:
|
|
67
|
+
throw new Error(`irtio usage: unknown option ${JSON.stringify(arg)}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
72
|
+
function formatBytes(bytes) {
|
|
73
|
+
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
74
|
+
let value = bytes;
|
|
75
|
+
let unit = 0;
|
|
76
|
+
while (value >= 1e3 && unit < units.length - 1) {
|
|
77
|
+
value /= 1e3;
|
|
78
|
+
unit++;
|
|
79
|
+
}
|
|
80
|
+
return `${unit === 0 ? String(Math.round(value)) : value.toFixed(2)} ${units[unit]}`;
|
|
81
|
+
}
|
|
82
|
+
function formatMeterValue(meter) {
|
|
83
|
+
if (meter.unit === "bytes") return formatBytes(meter.used);
|
|
84
|
+
if (meter.unit === "hours") return `${meter.used.toFixed(3)} hours`;
|
|
85
|
+
return `${meter.used.toFixed(2)} ${meter.unit}`;
|
|
86
|
+
}
|
|
87
|
+
function formatQuantity(value, unit) {
|
|
88
|
+
if (unit === "bytes") return formatBytes(value);
|
|
89
|
+
if (unit === "hours") return `${value} hours`;
|
|
90
|
+
return `${value} ${unit}`;
|
|
91
|
+
}
|
|
92
|
+
function estimatedOverage(meter) {
|
|
93
|
+
const p = meter.pricing;
|
|
94
|
+
if (p === void 0 || !meter.present) return void 0;
|
|
95
|
+
const used = meter.walledUsed ?? meter.used;
|
|
96
|
+
const over = used - (p.cardIncluded ?? 0);
|
|
97
|
+
if (!Number.isFinite(over) || over <= 0) return void 0;
|
|
98
|
+
return over / p.per * p.rate;
|
|
99
|
+
}
|
|
100
|
+
var CARD_BASE_FEE_USD = 9;
|
|
101
|
+
function formatUsage(body, showWindows) {
|
|
102
|
+
const out = [pc.dim(`period ${body.period.label} \xB7 project ${body.project}`), ""];
|
|
103
|
+
const width = Math.max(6, ...body.meters.map((m) => m.label.length));
|
|
104
|
+
const card = body.plan === "card";
|
|
105
|
+
let overageTotal = 0;
|
|
106
|
+
for (const meter of body.meters) {
|
|
107
|
+
if (!meter.present) {
|
|
108
|
+
out.push(`${meter.label.padEnd(width)} ${pc.dim("not reported")}`);
|
|
109
|
+
if (meter.note !== void 0) out.push(`${" ".repeat(width)} ${pc.dim(meter.note)}`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const gauge = meter.gauge ? pc.dim(" (high-water this period)") : "";
|
|
113
|
+
out.push(`${meter.label.padEnd(width)} ${formatMeterValue(meter)}${gauge}`);
|
|
114
|
+
if (meter.byEngine !== void 0) {
|
|
115
|
+
const parts = Object.entries(meter.byEngine).sort(([a], [b]) => a.localeCompare(b)).map(([engine, hours]) => `${engine}=${hours.toFixed(3)}`);
|
|
116
|
+
if (parts.length > 0)
|
|
117
|
+
out.push(`${" ".repeat(width)} ${pc.dim(`by engine: ${parts.join(" ")}`)}`);
|
|
118
|
+
}
|
|
119
|
+
if (card && meter.pricing !== void 0) {
|
|
120
|
+
const p = meter.pricing;
|
|
121
|
+
const bundle = p.cardIncluded !== void 0 ? `included ${formatQuantity(p.cardIncluded, meter.unit)}, then ${p.rateLabel}` : `no included bundle, ${p.rateLabel}`;
|
|
122
|
+
out.push(`${" ".repeat(width)} ${pc.dim(bundle)}`);
|
|
123
|
+
const over = estimatedOverage(meter);
|
|
124
|
+
if (over !== void 0) {
|
|
125
|
+
overageTotal += over;
|
|
126
|
+
out.push(`${" ".repeat(width)} estimated overage: $${over.toFixed(2)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
out.push("");
|
|
131
|
+
if (card) {
|
|
132
|
+
out.push(
|
|
133
|
+
overageTotal > 0 ? `estimated overage this period: $${overageTotal.toFixed(2)}, ` + pc.dim(
|
|
134
|
+
`plus the $${CARD_BASE_FEE_USD}/mo base fee. Estimates, not an invoice. This total is what counts against your monthly overage spend cap; at the cap every meter stops.`
|
|
135
|
+
) : pc.dim(
|
|
136
|
+
`within the included bundle: no overage so far this period (the $${CARD_BASE_FEE_USD}/mo base fee still applies)`
|
|
137
|
+
)
|
|
138
|
+
);
|
|
139
|
+
} else if (body.plan === "free") {
|
|
140
|
+
out.push(
|
|
141
|
+
pc.dim("free plan: usage stops at its caps and nothing is billed; add a card for overage")
|
|
142
|
+
);
|
|
143
|
+
} else {
|
|
144
|
+
out.push(pc.dim("these are measurements, not charges"));
|
|
145
|
+
}
|
|
146
|
+
if (showWindows) {
|
|
147
|
+
out.push("");
|
|
148
|
+
if (body.windows.length === 0) {
|
|
149
|
+
out.push(pc.dim("no windows in this period"));
|
|
150
|
+
} else {
|
|
151
|
+
out.push(pc.dim("WINDOW METER SOURCE ENGINE VALUE"));
|
|
152
|
+
for (const w of body.windows) {
|
|
153
|
+
out.push(
|
|
154
|
+
`${w.windowStart.replace("T", " ").replace(/:\d\d\.\d+Z$/, "Z").padEnd(20)} ${w.meter.padEnd(13)} ${(w.source || "-").padEnd(6)} ${(w.engine || "-").padEnd(8)} ${w.value}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
async function runUsage(options) {
|
|
162
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
163
|
+
const controlUrl = await resolveControlUrlForUser(options.controlUrl);
|
|
164
|
+
const client = options.client ?? await createApiClient(controlUrl);
|
|
165
|
+
const body = await client.get(`/v1/projects/${options.project}/usage`);
|
|
166
|
+
for (const line of formatUsage(body, options.windows === true)) log(line);
|
|
167
|
+
return body;
|
|
168
|
+
}
|
|
169
|
+
async function usage(args, deps = {}) {
|
|
170
|
+
const log = deps.log ?? ((line) => console.log(line));
|
|
171
|
+
const errorLog = deps.errorLog ?? ((line) => console.error(line));
|
|
172
|
+
try {
|
|
173
|
+
const parsed = parseUsageArgs(args);
|
|
174
|
+
const config = await readProjectConfig(process.cwd(), "irtio usage", parsed.config);
|
|
175
|
+
const project = parsed.project ?? config.project;
|
|
176
|
+
if (project === void 0) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
"irtio usage: no project id. Pass --project or run this from a project with irtio.json"
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
await runUsage({
|
|
182
|
+
project,
|
|
183
|
+
log,
|
|
184
|
+
...parsed.windows === true ? { windows: true } : {},
|
|
185
|
+
...parsed.url !== void 0 ? { controlUrl: parsed.url } : {},
|
|
186
|
+
...deps.client !== void 0 ? { client: deps.client } : {}
|
|
187
|
+
});
|
|
188
|
+
} catch (err) {
|
|
189
|
+
if (err instanceof HelpRequested) {
|
|
190
|
+
log(err.usage);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (isLoginRequired(err)) {
|
|
194
|
+
errorLog(pc.red("not logged in"));
|
|
195
|
+
errorLog("run: irtio login");
|
|
196
|
+
process.exitCode = 1;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
errorLog(pc.red(err instanceof Error ? err.message : String(err)));
|
|
200
|
+
process.exitCode = 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
export {
|
|
204
|
+
CARD_BASE_FEE_USD,
|
|
205
|
+
USAGE,
|
|
206
|
+
estimatedOverage,
|
|
207
|
+
formatBytes,
|
|
208
|
+
formatMeterValue,
|
|
209
|
+
formatUsage,
|
|
210
|
+
parseUsageArgs,
|
|
211
|
+
runUsage,
|
|
212
|
+
usage
|
|
213
|
+
};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
HelpRequested,
|
|
3
|
-
createApiClient,
|
|
4
3
|
helpFor,
|
|
5
|
-
helpRequested
|
|
6
|
-
|
|
4
|
+
helpRequested
|
|
5
|
+
} from "./chunk-OCVALOGK.js";
|
|
6
|
+
import {
|
|
7
|
+
createApiClient,
|
|
8
|
+
isLoginRequired
|
|
9
|
+
} from "./chunk-IDF46P7R.js";
|
|
10
|
+
import {
|
|
7
11
|
resolveControlUrlForUser
|
|
8
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-UPHQM6NZ.js";
|
|
9
13
|
|
|
10
14
|
// src/whoami.ts
|
|
11
15
|
import pc from "picocolors";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irtio/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "irtio CLI: local dev server, room scaffolding, bot simulation, deploy, logs and rooms",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -17,6 +17,22 @@
|
|
|
17
17
|
"./bundle": {
|
|
18
18
|
"types": "./dist/bundle.d.ts",
|
|
19
19
|
"import": "./dist/bundle.js"
|
|
20
|
+
},
|
|
21
|
+
"./api": {
|
|
22
|
+
"types": "./dist/api.d.ts",
|
|
23
|
+
"import": "./dist/api.js"
|
|
24
|
+
},
|
|
25
|
+
"./credentials": {
|
|
26
|
+
"types": "./dist/credentials.d.ts",
|
|
27
|
+
"import": "./dist/credentials.js"
|
|
28
|
+
},
|
|
29
|
+
"./simulate": {
|
|
30
|
+
"types": "./dist/simulate.d.ts",
|
|
31
|
+
"import": "./dist/simulate.js"
|
|
32
|
+
},
|
|
33
|
+
"./deploy": {
|
|
34
|
+
"types": "./dist/deploy.d.ts",
|
|
35
|
+
"import": "./dist/deploy.js"
|
|
20
36
|
}
|
|
21
37
|
},
|
|
22
38
|
"bin": {
|
|
@@ -29,12 +45,12 @@
|
|
|
29
45
|
"esbuild": "^0.27.3",
|
|
30
46
|
"picocolors": "^1.1.1",
|
|
31
47
|
"ws": "^8.18.0",
|
|
32
|
-
"@irtio/bots": "0.
|
|
33
|
-
"@irtio/client": "0.
|
|
34
|
-
"@irtio/protocol": "0.
|
|
35
|
-
"@irtio/runtime": "0.
|
|
36
|
-
"@irtio/schema": "0.
|
|
37
|
-
"@irtio/server": "0.
|
|
48
|
+
"@irtio/bots": "0.7.0",
|
|
49
|
+
"@irtio/client": "0.7.0",
|
|
50
|
+
"@irtio/protocol": "0.7.0",
|
|
51
|
+
"@irtio/runtime": "0.7.0",
|
|
52
|
+
"@irtio/schema": "0.7.0",
|
|
53
|
+
"@irtio/server": "0.7.0"
|
|
38
54
|
},
|
|
39
55
|
"devDependencies": {
|
|
40
56
|
"@types/ws": "^8.5.13",
|
package/dist/chunk-BPE452KF.js
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
// ../store/dist/index.js
|
|
2
|
-
import { mkdir, readFile, readdir, rename, rm, writeFile } from "fs/promises";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
var KV_LIMITS = {
|
|
5
|
-
/** Max UTF-8 bytes in one value. */
|
|
6
|
-
valueBytes: 16 * 1024,
|
|
7
|
-
/** Max UTF-8 bytes in one key. */
|
|
8
|
-
keyBytes: 256,
|
|
9
|
-
/** Max UTF-8 bytes in one player id. */
|
|
10
|
-
playerIdBytes: 256,
|
|
11
|
-
/** Max distinct keys one player may hold within one project. */
|
|
12
|
-
keysPerPlayer: 128,
|
|
13
|
-
/** Max rows one project may hold across all its players. */
|
|
14
|
-
rowsPerProject: 1e6
|
|
15
|
-
};
|
|
16
|
-
var PLAYER_ISSUER_RE = /^[a-z0-9._-]{1,64}$/;
|
|
17
|
-
var KV_ERRORS = {
|
|
18
|
-
badKey: "E_KV_BAD_KEY",
|
|
19
|
-
badPlayer: "E_KV_BAD_PLAYER",
|
|
20
|
-
valueTooLarge: "E_KV_VALUE_TOO_LARGE",
|
|
21
|
-
tooManyKeys: "E_KV_TOO_MANY_KEYS",
|
|
22
|
-
projectFull: "E_KV_PROJECT_FULL",
|
|
23
|
-
forbidden: "E_KV_FORBIDDEN",
|
|
24
|
-
unavailable: "E_KV_UNAVAILABLE"
|
|
25
|
-
};
|
|
26
|
-
var SAVES_SEGMENT = "/saves/";
|
|
27
|
-
var SAVE_ID_TIME_DIGITS = 17;
|
|
28
|
-
var DEFAULT_SAVE_RETAIN = 10;
|
|
29
|
-
var PRE_MIGRATION_SAVE_ID = "premigrate";
|
|
30
|
-
function mintSaveId(nowMs, rand) {
|
|
31
|
-
const t = Math.max(0, Math.floor(nowMs));
|
|
32
|
-
const suffix = (Math.floor(Math.abs(rand)) & 65535).toString(16).padStart(4, "0");
|
|
33
|
-
return `${String(t).padStart(SAVE_ID_TIME_DIGITS, "0")}-${suffix}`;
|
|
34
|
-
}
|
|
35
|
-
function saveIdCreatedAt(saveId) {
|
|
36
|
-
if (saveId.length !== SAVE_ID_TIME_DIGITS + 5) return void 0;
|
|
37
|
-
if (saveId[SAVE_ID_TIME_DIGITS] !== "-") return void 0;
|
|
38
|
-
const time = saveId.slice(0, SAVE_ID_TIME_DIGITS);
|
|
39
|
-
if (!/^[0-9]+$/.test(time)) return void 0;
|
|
40
|
-
if (!/^[0-9a-f]{4}$/.test(saveId.slice(SAVE_ID_TIME_DIGITS + 1))) return void 0;
|
|
41
|
-
return Number(time);
|
|
42
|
-
}
|
|
43
|
-
function savesPrefix(liveKey) {
|
|
44
|
-
return `${liveKey}${SAVES_SEGMENT}`;
|
|
45
|
-
}
|
|
46
|
-
function saveKey(liveKey, saveId) {
|
|
47
|
-
return `${savesPrefix(liveKey)}${saveId}`;
|
|
48
|
-
}
|
|
49
|
-
function saveIdOf(liveKey, key) {
|
|
50
|
-
const prefix = savesPrefix(liveKey);
|
|
51
|
-
if (!key.startsWith(prefix)) return void 0;
|
|
52
|
-
const rest = key.slice(prefix.length);
|
|
53
|
-
if (rest === "" || rest.includes("/")) return void 0;
|
|
54
|
-
return rest;
|
|
55
|
-
}
|
|
56
|
-
async function listSaves(store, liveKey) {
|
|
57
|
-
const keys = await store.list(savesPrefix(liveKey));
|
|
58
|
-
const out = [];
|
|
59
|
-
for (const key of keys) {
|
|
60
|
-
const saveId = saveIdOf(liveKey, key);
|
|
61
|
-
if (saveId === void 0) continue;
|
|
62
|
-
const createdAt = saveIdCreatedAt(saveId);
|
|
63
|
-
if (createdAt === void 0) continue;
|
|
64
|
-
out.push({ saveId, key, createdAt });
|
|
65
|
-
}
|
|
66
|
-
out.sort((a, b) => a.saveId < b.saveId ? 1 : a.saveId > b.saveId ? -1 : 0);
|
|
67
|
-
return out;
|
|
68
|
-
}
|
|
69
|
-
function selectForPruning(saves, retain) {
|
|
70
|
-
const keep = Number.isFinite(retain) ? Math.max(1, Math.floor(retain)) : 1;
|
|
71
|
-
return saves.length <= keep ? [] : saves.slice(keep);
|
|
72
|
-
}
|
|
73
|
-
async function pruneSaves(store, saves, retain, log) {
|
|
74
|
-
const doomed = selectForPruning(saves, retain);
|
|
75
|
-
let deleted = 0;
|
|
76
|
-
for (const save of doomed) {
|
|
77
|
-
try {
|
|
78
|
-
await store.delete(save.key);
|
|
79
|
-
deleted++;
|
|
80
|
-
} catch (err) {
|
|
81
|
-
log("warn", `save retention: could not delete ${save.key}`, err);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return deleted;
|
|
85
|
-
}
|
|
86
|
-
var STATIC_LIMITS = {
|
|
87
|
-
/** Bytes per file. Big enough for a wasm build or a texture atlas; a video does not belong. */
|
|
88
|
-
maxFileBytes: 32 * 1024 * 1024,
|
|
89
|
-
/** Files per deploy. */
|
|
90
|
-
maxFiles: 2e3,
|
|
91
|
-
/** Total bytes per deploy. */
|
|
92
|
-
maxTotalBytes: 256 * 1024 * 1024
|
|
93
|
-
};
|
|
94
|
-
function staticPathProblem(path2) {
|
|
95
|
-
if (path2.length === 0 || path2.length > 512) return "path must be 1-512 characters";
|
|
96
|
-
if (path2.startsWith("/")) return "path must be relative (no leading slash)";
|
|
97
|
-
if (path2.includes("\\")) return "path must use forward slashes";
|
|
98
|
-
if (/[\x00-\x1f\x7f]/.test(path2)) return "path must not contain control characters";
|
|
99
|
-
for (const segment of path2.split("/")) {
|
|
100
|
-
if (segment === "" || segment === "." || segment === "..") {
|
|
101
|
-
return "path segments must be non-empty and not . or ..";
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return void 0;
|
|
105
|
-
}
|
|
106
|
-
var DiskStore = class {
|
|
107
|
-
constructor(dir) {
|
|
108
|
-
this.dir = dir;
|
|
109
|
-
}
|
|
110
|
-
dir;
|
|
111
|
-
fileFor(key) {
|
|
112
|
-
if (!/^[A-Za-z0-9_.@/-]+$/.test(key) || key.includes("..")) {
|
|
113
|
-
throw new Error(`invalid object store key ${JSON.stringify(key)}`);
|
|
114
|
-
}
|
|
115
|
-
return path.join(this.dir, `${key}.snap`);
|
|
116
|
-
}
|
|
117
|
-
async put(key, bytes) {
|
|
118
|
-
const file = this.fileFor(key);
|
|
119
|
-
await mkdir(path.dirname(file), { recursive: true });
|
|
120
|
-
const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
121
|
-
await writeFile(tmp, bytes);
|
|
122
|
-
for (let attempt = 0; ; attempt++) {
|
|
123
|
-
try {
|
|
124
|
-
await rename(tmp, file);
|
|
125
|
-
return;
|
|
126
|
-
} catch (err) {
|
|
127
|
-
const code = err.code;
|
|
128
|
-
if ((code === "EPERM" || code === "EBUSY") && attempt < 10) {
|
|
129
|
-
await new Promise((resolve) => setTimeout(resolve, 5 * (attempt + 1)));
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
throw err;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
async get(key) {
|
|
137
|
-
try {
|
|
138
|
-
return new Uint8Array(await readFile(this.fileFor(key)));
|
|
139
|
-
} catch (err) {
|
|
140
|
-
if (err.code === "ENOENT") return void 0;
|
|
141
|
-
throw err;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
async delete(key) {
|
|
145
|
-
await rm(this.fileFor(key), { force: true });
|
|
146
|
-
}
|
|
147
|
-
async list(prefix) {
|
|
148
|
-
const out = [];
|
|
149
|
-
const walk = async (dir, rel) => {
|
|
150
|
-
let entries;
|
|
151
|
-
try {
|
|
152
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
153
|
-
} catch (err) {
|
|
154
|
-
if (err.code === "ENOENT") return;
|
|
155
|
-
throw err;
|
|
156
|
-
}
|
|
157
|
-
for (const e of entries) {
|
|
158
|
-
const r = rel ? `${rel}/${e.name}` : e.name;
|
|
159
|
-
if (e.isDirectory()) await walk(path.join(dir, e.name), r);
|
|
160
|
-
else if (e.name.endsWith(".snap")) out.push(r.slice(0, -".snap".length));
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
await walk(this.dir, "");
|
|
164
|
-
return out.filter((k) => k.startsWith(prefix)).sort();
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
export {
|
|
169
|
-
PLAYER_ISSUER_RE,
|
|
170
|
-
KV_ERRORS,
|
|
171
|
-
DEFAULT_SAVE_RETAIN,
|
|
172
|
-
PRE_MIGRATION_SAVE_ID,
|
|
173
|
-
mintSaveId,
|
|
174
|
-
saveKey,
|
|
175
|
-
listSaves,
|
|
176
|
-
pruneSaves,
|
|
177
|
-
STATIC_LIMITS,
|
|
178
|
-
staticPathProblem,
|
|
179
|
-
DiskStore
|
|
180
|
-
};
|