@3minapi/cli 0.0.0-stage → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -2
  3. package/dist/bin.js +54 -0
  4. package/dist/cli.js +241 -0
  5. package/dist/commands/auth.js +573 -0
  6. package/dist/commands/common.js +48 -0
  7. package/dist/commands/dashboard.js +298 -0
  8. package/dist/commands/delete.js +167 -0
  9. package/dist/commands/doctor.js +326 -0
  10. package/dist/commands/guide.js +84 -0
  11. package/dist/commands/index.js +28 -0
  12. package/dist/commands/init.js +248 -0
  13. package/dist/commands/logs.js +154 -0
  14. package/dist/commands/new.js +109 -0
  15. package/dist/commands/paths.js +64 -0
  16. package/dist/commands/reload.js +32 -0
  17. package/dist/commands/results.js +445 -0
  18. package/dist/commands/start.js +443 -0
  19. package/dist/commands/stop.js +68 -0
  20. package/dist/commands/tasks.js +93 -0
  21. package/dist/commands/worker.js +178 -0
  22. package/dist/core/api.js +132 -0
  23. package/dist/core/config.js +188 -0
  24. package/dist/core/control-db.js +462 -0
  25. package/dist/core/credentials.js +75 -0
  26. package/dist/core/domain/constants.js +121 -0
  27. package/dist/core/domain/contract.js +102 -0
  28. package/dist/core/domain/errors.js +57 -0
  29. package/dist/core/domain/index.js +3 -0
  30. package/dist/core/domain/types.js +1 -0
  31. package/dist/core/error-report.js +103 -0
  32. package/dist/core/host/detach.js +49 -0
  33. package/dist/core/host/live-connection.js +270 -0
  34. package/dist/core/host/live-receiver.js +264 -0
  35. package/dist/core/host/module.js +1 -0
  36. package/dist/core/host/poller.js +113 -0
  37. package/dist/core/host/pull-step.js +54 -0
  38. package/dist/core/host/receiver.js +1 -0
  39. package/dist/core/host/reload.js +34 -0
  40. package/dist/core/host/runner-lock.js +245 -0
  41. package/dist/core/host/session.js +84 -0
  42. package/dist/core/host/usage.js +20 -0
  43. package/dist/core/log-read.js +59 -0
  44. package/dist/core/logger.js +206 -0
  45. package/dist/core/names.js +27 -0
  46. package/dist/core/network.js +10 -0
  47. package/dist/core/open-browser.js +26 -0
  48. package/dist/core/output.js +68 -0
  49. package/dist/core/paths.js +26 -0
  50. package/dist/core/project.js +85 -0
  51. package/dist/core/prompt.js +140 -0
  52. package/dist/core/redact.js +45 -0
  53. package/dist/core/secure-fs.js +50 -0
  54. package/dist/core/text-file.js +12 -0
  55. package/dist/core/text.js +27 -0
  56. package/dist/core/time.js +84 -0
  57. package/dist/core/update-notice.js +142 -0
  58. package/dist/core/version.js +16 -0
  59. package/dist/core/warnings.js +10 -0
  60. package/dist/help.js +144 -0
  61. package/dist/modules/dashboard/assets.js +411 -0
  62. package/dist/modules/dashboard/data.js +565 -0
  63. package/dist/modules/dashboard/guard.js +75 -0
  64. package/dist/modules/dashboard/lock.js +93 -0
  65. package/dist/modules/dashboard/server.js +295 -0
  66. package/dist/modules/dashboard/views/html.js +114 -0
  67. package/dist/modules/dashboard/views/layout.js +59 -0
  68. package/dist/modules/dashboard/views/logs.js +122 -0
  69. package/dist/modules/dashboard/views/overview.js +316 -0
  70. package/dist/modules/dashboard/views/runs.js +486 -0
  71. package/dist/modules/dashboard/views/workers.js +157 -0
  72. package/dist/modules/workers/builtin/host-identity.js +59 -0
  73. package/dist/modules/workers/builtin/results-worker.js +37 -0
  74. package/dist/modules/workers/local-server.js +244 -0
  75. package/dist/modules/workers/module.js +98 -0
  76. package/dist/modules/workers/reload-result.js +16 -0
  77. package/dist/modules/workers/repeat-scheduler.js +165 -0
  78. package/dist/modules/workers/results/missing-paths.js +66 -0
  79. package/dist/modules/workers/results/query.js +175 -0
  80. package/dist/modules/workers/results/reader-input.js +134 -0
  81. package/dist/modules/workers/results/reader.js +430 -0
  82. package/dist/modules/workers/results/results-child.js +64 -0
  83. package/dist/modules/workers/results/shape.js +212 -0
  84. package/dist/modules/workers/results/store.js +336 -0
  85. package/dist/modules/workers/results/substitute.js +30 -0
  86. package/dist/modules/workers/runtime/env.js +43 -0
  87. package/dist/modules/workers/runtime/error-chain.js +109 -0
  88. package/dist/modules/workers/runtime/executor.js +289 -0
  89. package/dist/modules/workers/runtime/inspect.js +53 -0
  90. package/dist/modules/workers/runtime/protocol.js +1 -0
  91. package/dist/modules/workers/runtime/resolve.js +30 -0
  92. package/dist/modules/workers/runtime/schema.js +85 -0
  93. package/dist/modules/workers/runtime/thread.js +117 -0
  94. package/dist/modules/workers/start.js +265 -0
  95. package/dist/modules/workers/sync.js +241 -0
  96. package/dist/modules/workers/task-runner.js +892 -0
  97. package/dist/modules/workers/template.js +100 -0
  98. package/dist/modules/workers/worker-set.js +122 -0
  99. package/dist/modules/workers/workers.js +66 -0
  100. package/package.json +48 -5
@@ -0,0 +1,316 @@
1
+ import { BIN } from "../../../core/domain/index.js";
2
+ import { stopHint } from "../../../core/host/session.js";
3
+ import { formatBytes, formatCount } from "../../../core/text.js";
4
+ import { daysToMs, formatInterval, formatLocal, relative } from "../../../core/time.js";
5
+ import { RANGES } from "../data.js";
6
+ import { badge, command, html, qs, time, workerName } from "./html.js";
7
+ const RANGE_LABEL = {
8
+ all: 'All',
9
+ today: 'Today',
10
+ '7d': '7 days',
11
+ '30d': '30 days'
12
+ };
13
+ export function overviewPage(o, now, ctx) {
14
+ return html `
15
+ <div class="head">
16
+ <h1>Overview</h1>
17
+ ${rangeTabs('/', o.range)}
18
+ </div>
19
+ ${o.hasControlDb
20
+ ? ''
21
+ : html `<p class="note">
22
+ No task history on host ${ctx.host} yet — run <code>${BIN} start</code> to receive tasks.
23
+ </p>`}
24
+ <section class="cards" aria-label="Totals">
25
+ ${card('Runs', o.totals.total, `/runs${qs({ range: o.range })}`)}
26
+ ${card('Completed', o.totals.completed, `/runs${qs({ range: o.range, state: 'completed' })}`)}
27
+ ${card('Failed', o.totals.failed, `/runs${qs({ range: o.range, state: 'failed' })}`, o.totals.failed > 0)}
28
+ ${card('Cancelled', o.totals.cancelled, `/runs${qs({ range: o.range, state: 'cancelled' })}`)}
29
+ ${card('Running', o.totals.running, `/runs${qs({ range: o.range, state: 'running' })}`)}
30
+ </section>
31
+
32
+ <section class="panel">
33
+ <h2>Trend <span class="muted">${o.trend.unit === 'hour' ? 'by hour' : 'by day'}</span></h2>
34
+ ${trendChart(o.trend.buckets, o.trend.unit)}
35
+ <p class="legend">
36
+ <span class="key key-ok"></span>completed <span class="key key-fail"></span>failed
37
+ <span class="key key-other"></span>other
38
+ </p>
39
+ </section>
40
+
41
+ <div class="grid">
42
+ <section class="panel">
43
+ <h2>By worker</h2>
44
+ ${o.byWorker.length === 0
45
+ ? html `<p class="muted">No runs in this period.</p>`
46
+ : html `<table class="table">
47
+ <thead>
48
+ <tr>
49
+ <th>Worker</th>
50
+ <th class="num">Runs</th>
51
+ <th class="num">Failed</th>
52
+ <th class="bar-col"></th>
53
+ </tr>
54
+ </thead>
55
+ <tbody>
56
+ ${o.byWorker.map((w) => html `<tr>
57
+ <td>
58
+ <a href="/runs${qs({ range: o.range, worker: w.worker })}"
59
+ >${workerName(w.worker)}</a
60
+ >
61
+ </td>
62
+ <td class="num">${formatCount(w.total)}</td>
63
+ <td class="num">
64
+ ${w.failed > 0
65
+ ? html `<a
66
+ href="/runs${qs({
67
+ range: o.range,
68
+ worker: w.worker,
69
+ state: 'failed'
70
+ })}"
71
+ >${formatCount(w.failed)}</a
72
+ >`
73
+ : '0'}
74
+ </td>
75
+ <td class="bar-col">${miniBar(w, o.byWorker[0].total)}</td>
76
+ </tr>`)}
77
+ </tbody>
78
+ </table>`}
79
+ </section>
80
+
81
+ <section class="panel">
82
+ <h2>Recent failures</h2>
83
+ ${o.recentFailures.length === 0
84
+ ? html `<p class="muted">No failures in this period.</p>`
85
+ : html `<ul class="list">
86
+ ${o.recentFailures.map((t) => html `<li>
87
+ <div class="row">
88
+ <a href="/runs/${encodeURIComponent(t.task_id)}"><code>${t.task_id}</code></a>
89
+ ${badge('failed')}
90
+ </div>
91
+ <div class="row muted">
92
+ ${workerName(t.worker)} · ${time(t.finished_at ?? t.received_at)}
93
+ </div>
94
+ ${t.last_error ? html `<div class="error">${t.last_error}</div>` : ''}
95
+ </li>`)}
96
+ </ul>`}
97
+ </section>
98
+ </div>
99
+
100
+ <section class="now" aria-labelledby="now-title">
101
+ <h2 id="now-title" class="section-title">
102
+ Host ${ctx.host} <span class="muted">— now, not limited to the period above</span>
103
+ </h2>
104
+ <div class="grid">${runnerPanel(o, now, ctx)} ${diskPanel(o, now, ctx)}</div>
105
+ </section>
106
+ `;
107
+ }
108
+ export function rangeTabs(base, current, extra = {}, ranges = RANGES) {
109
+ return html `<nav class="tabs" aria-label="Period">
110
+ ${ranges.map((r) => html `<a
111
+ href="${base}${qs({ ...extra, range: r })}"
112
+ ${r === current ? html ` aria-current="page"` : ''}
113
+ >${RANGE_LABEL[r]}</a
114
+ >`)}
115
+ </nav>`;
116
+ }
117
+ function card(label, value, href, alert = false) {
118
+ return html `<a class="card${alert ? ' card-alert' : ''}" href="${href}"
119
+ ><span class="card-label">${label}</span
120
+ ><span class="card-value">${formatCount(value)}</span></a
121
+ >`;
122
+ }
123
+ const COUNT_CHAR_WIDTH = 6.5;
124
+ function trendChart(buckets, unit) {
125
+ const width = 720;
126
+ const height = 160;
127
+ const pad = { top: 18, bottom: 22, left: 4, right: 4 };
128
+ const max = Math.max(1, ...buckets.map((b) => b.total));
129
+ const innerW = width - pad.left - pad.right;
130
+ const innerH = height - pad.top - pad.bottom;
131
+ const step = innerW / Math.max(1, buckets.length);
132
+ const barW = Math.max(2, Math.min(28, step * 0.7));
133
+ const y = (n) => (n / max) * innerH;
134
+ const labelEvery = Math.ceil(buckets.length / 8);
135
+ const label = (b) => (unit === 'hour' ? `${b.slice(11)}h` : b.slice(5));
136
+ return html `<svg
137
+ class="chart"
138
+ viewBox="0 0 ${width} ${height}"
139
+ role="img"
140
+ aria-label="Runs per ${unit}"
141
+ >
142
+ <line
143
+ class="axis"
144
+ x1="${pad.left}"
145
+ y1="${pad.top + innerH}"
146
+ x2="${width - pad.right}"
147
+ y2="${pad.top + innerH}"
148
+ />
149
+ ${buckets.map((b, i) => {
150
+ const x = pad.left + i * step + (step - barW) / 2;
151
+ const other = b.total - b.completed - b.failed;
152
+ let base = pad.top + innerH;
153
+ const seg = (n, cls) => {
154
+ if (n === 0)
155
+ return '';
156
+ const h = Math.max(1, y(n));
157
+ base -= h;
158
+ return html `<rect
159
+ class="${cls}"
160
+ x="${x.toFixed(1)}"
161
+ y="${base.toFixed(1)}"
162
+ width="${barW.toFixed(1)}"
163
+ height="${h.toFixed(1)}"
164
+ />`;
165
+ };
166
+ const count = formatCount(b.total);
167
+ const fits = count.length * COUNT_CHAR_WIDTH <= step;
168
+ return html `<g
169
+ ><title>${b.bucket}: ${b.total} runs, ${b.completed} completed, ${b.failed} failed</title>
170
+ ${seg(b.completed, 'bar-ok')}${seg(other, 'bar-other')}${seg(b.failed, 'bar-fail')}
171
+ ${b.total > 0 && fits
172
+ ? html `<text
173
+ class="count"
174
+ x="${(x + barW / 2).toFixed(1)}"
175
+ y="${(base - 4).toFixed(1)}"
176
+ text-anchor="middle"
177
+ >${count}</text
178
+ >`
179
+ : ''}
180
+ ${i % labelEvery === 0
181
+ ? html `<text
182
+ class="tick"
183
+ x="${(x + barW / 2).toFixed(1)}"
184
+ y="${height - 6}"
185
+ text-anchor="middle"
186
+ >${label(b.bucket)}</text
187
+ >`
188
+ : ''}</g
189
+ >`;
190
+ })}
191
+ </svg>`;
192
+ }
193
+ function miniBar(c, max) {
194
+ const w = 120;
195
+ const scale = (n) => (max === 0 ? 0 : (n / max) * w);
196
+ const ok = scale(c.completed);
197
+ const fail = scale(c.failed);
198
+ const other = scale(c.total - c.completed - c.failed);
199
+ return html `<svg class="mini" viewBox="0 0 ${w} 10" aria-hidden="true">
200
+ <rect class="bar-ok" x="0" y="1" width="${ok.toFixed(1)}" height="8" />
201
+ <rect class="bar-other" x="${ok.toFixed(1)}" y="1" width="${other.toFixed(1)}" height="8" />
202
+ <rect
203
+ class="bar-fail"
204
+ x="${(ok + other).toFixed(1)}"
205
+ y="1"
206
+ width="${fail.toFixed(1)}"
207
+ height="8"
208
+ />
209
+ </svg>`;
210
+ }
211
+ function runnerPanel(o, now, ctx) {
212
+ const r = o.runner;
213
+ const rows = [];
214
+ if (r.state === 'running') {
215
+ const info = r.info;
216
+ rows.push([
217
+ 'State',
218
+ html `${badge('running')} pid ${info.pid}${info.detached ? ' · detached' : ''}`
219
+ ]);
220
+ rows.push([
221
+ 'Started',
222
+ html `${time(info.startedAt)} <span class="muted">(${relative(info.startedAt, now)})</span>`
223
+ ]);
224
+ rows.push(info.intervalMs === null
225
+ ? ['Receive mode', 'live (pulls when 3Min API signals new tasks)']
226
+ : ['Poll interval', `${formatInterval(info.intervalMs)} (or the server minimum, if larger)`]);
227
+ if (info.port !== null)
228
+ rows.push([
229
+ 'Local server',
230
+ html `127.0.0.1:${info.port}${info.public ? ' (public)' : ''}${ctx.remote
231
+ ? html ` <span class="muted">— reachable only on host ${ctx.host}</span>`
232
+ : ''}`
233
+ ]);
234
+ if (info.workersDir)
235
+ rows.push(['Workers folder', html `<code>${info.workersDir}</code>`]);
236
+ }
237
+ else if (r.state === 'starting') {
238
+ rows.push(['State', html `${badge('running')} starting`]);
239
+ }
240
+ else {
241
+ rows.push([
242
+ 'State',
243
+ html `${badge('stopped')} <span class="muted">not running</span> ${command(`${BIN} start`, ctx)}`
244
+ ]);
245
+ }
246
+ if (o.host.name)
247
+ rows.push(['Host', o.host.name]);
248
+ if (o.lastResponse) {
249
+ const q = o.lastResponse.quota;
250
+ rows.push([
251
+ 'Last server response',
252
+ html `${time(o.lastResponse.at)}
253
+ <span class="muted">(${relative(o.lastResponse.at, now)})</span>`
254
+ ]);
255
+ rows.push([
256
+ 'Quota',
257
+ html `${formatCount(q.used)} / ${formatCount(q.limit)} calls · resets
258
+ ${time(Date.parse(q.reset_at))}`
259
+ ]);
260
+ }
261
+ if (o.stop) {
262
+ rows.push([
263
+ 'Last stop',
264
+ html `${badge('failed')} <code>${o.stop.reason}</code> ${time(o.stop.stoppedAt)}
265
+ <div class="muted">${stopHint(o.stop.reason)}</div>`
266
+ ]);
267
+ }
268
+ if (o.end) {
269
+ rows.push([
270
+ 'Last end',
271
+ html `<code>${o.end.reason}</code> ${time(o.end.endedAt)}${o.end.deletedWorkers.length > 0
272
+ ? html `<div class="muted">Deleted on the server: ${o.end.deletedWorkers.join(', ')}</div>`
273
+ : ''}`
274
+ ]);
275
+ }
276
+ return html `<section class="panel">
277
+ <h2>Runner</h2>
278
+ <dl class="kv">
279
+ ${rows.map(([k, v]) => html `<dt>${k}</dt>
280
+ <dd>${v}</dd>`)}
281
+ </dl>
282
+ </section>`;
283
+ }
284
+ function diskPanel(o, now, ctx) {
285
+ const { controlDb, dataDb, logs } = o.disk;
286
+ const before = formatLocal(now - daysToMs(30)).slice(0, 10);
287
+ return html `<section class="panel">
288
+ <h2>Disk</h2>
289
+ <dl class="kv">
290
+ <dt>Task history</dt>
291
+ <dd>
292
+ ${controlDb
293
+ ? html `${formatCount(controlDb.tasks)} task(s) · ${formatBytes(controlDb.bytes)}
294
+ <div class="path"><code>${controlDb.file}</code></div>
295
+ ${command(`${BIN} tasks prune --before ${before}`, ctx)}`
296
+ : html `<span class="muted">none</span>`}
297
+ </dd>
298
+ <dt>Saved results</dt>
299
+ <dd>
300
+ ${dataDb
301
+ ? html `${formatCount(dataDb.results)} result(s)
302
+ <span class="muted">(a repeating task saves each run)</span> ·
303
+ ${formatBytes(dataDb.bytes)}
304
+ <div class="path"><code>${dataDb.file}</code></div>
305
+ ${command(`${BIN} worker results prune --before ${before}`, ctx)}`
306
+ : html `<span class="muted">none</span>`}
307
+ </dd>
308
+ <dt>Logs</dt>
309
+ <dd>
310
+ ${formatCount(logs.files)} file(s) · ${formatBytes(logs.bytes)}
311
+ <div class="path"><code>${logs.dir}</code></div>
312
+ <span class="muted">Deleted automatically (logRetentionDays, logMaxBytes)</span>
313
+ </dd>
314
+ </dl>
315
+ </section>`;
316
+ }