gipity 1.0.365 → 1.0.374
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/banner.js +3 -1
- package/dist/commands/add.js +2 -2
- package/dist/commands/agent.js +3 -5
- package/dist/commands/approval.js +3 -3
- package/dist/commands/audit.js +2 -2
- package/dist/commands/chat.js +4 -4
- package/dist/commands/claude.js +8 -9
- package/dist/commands/credits.js +1 -1
- package/dist/commands/db.js +7 -6
- package/dist/commands/deploy.js +5 -8
- package/dist/commands/doctor.js +11 -13
- package/dist/commands/domain.js +18 -15
- package/dist/commands/email.js +0 -4
- package/dist/commands/fn.js +2 -2
- package/dist/commands/generate.js +57 -5
- package/dist/commands/job.js +6 -6
- package/dist/commands/location.js +7 -7
- package/dist/commands/login.js +2 -16
- package/dist/commands/logout.js +2 -3
- package/dist/commands/logs.js +1 -1
- package/dist/commands/page-eval.js +6 -4
- package/dist/commands/page-fetch.js +136 -0
- package/dist/commands/page-inspect.js +29 -27
- package/dist/commands/page-screenshot.js +17 -18
- package/dist/commands/page-test.js +8 -8
- package/dist/commands/page.js +6 -3
- package/dist/commands/plan.js +4 -4
- package/dist/commands/push.js +2 -6
- package/dist/commands/realtime.js +7 -9
- package/dist/commands/relay-install.js +18 -21
- package/dist/commands/relay.js +29 -31
- package/dist/commands/sandbox.js +16 -3
- package/dist/commands/service.js +1 -3
- package/dist/commands/status.js +2 -2
- package/dist/commands/sync.js +4 -1
- package/dist/commands/test.js +7 -13
- package/dist/commands/text.js +10 -10
- package/dist/commands/uninstall.js +20 -42
- package/dist/commands/update.js +0 -2
- package/dist/commands/upload.js +4 -4
- package/dist/commands/workflow.js +1 -2
- package/dist/helpers/output.js +45 -7
- package/dist/index.js +4 -0
- package/dist/knowledge.js +19 -4
- package/dist/progress.js +60 -0
- package/dist/project-setup.js +5 -1
- package/dist/provider-docs.js +7 -7
- package/dist/setup.js +20 -7
- package/dist/sync.js +16 -6
- package/dist/updater/shim.js +18 -4
- package/dist/upload.js +6 -0
- package/package.json +5 -4
package/dist/commands/login.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { saveAuth, getAuth } from '../auth.js';
|
|
3
3
|
import { publicPost } from '../api.js';
|
|
4
4
|
import { prompt, decodeJwtExp } from '../utils.js';
|
|
5
|
-
import { success, error as clrError } from '../colors.js';
|
|
5
|
+
import { success, error as clrError, muted } from '../colors.js';
|
|
6
6
|
export const loginCommand = new Command('login')
|
|
7
7
|
.description('Log in or sign up')
|
|
8
8
|
.option('--email <email>', 'Email address')
|
|
@@ -19,37 +19,27 @@ export const loginCommand = new Command('login')
|
|
|
19
19
|
// Email only → send code and exit (non-interactive step 1)
|
|
20
20
|
if (email && !code) {
|
|
21
21
|
await publicPost('/auth/login', { email });
|
|
22
|
-
console.log('');
|
|
23
22
|
console.log('Check your email for a 6-digit code.');
|
|
24
|
-
console.log(`Then run: gipity login --email ${email} --code <code>`);
|
|
25
|
-
console.log('');
|
|
23
|
+
console.log(muted(`Then run: gipity login --email ${email} --code <code>`));
|
|
26
24
|
return;
|
|
27
25
|
}
|
|
28
26
|
// Fully interactive flow
|
|
29
|
-
console.log('');
|
|
30
27
|
console.log('Enter your email to log in or create an account.');
|
|
31
|
-
console.log('');
|
|
32
28
|
const existing = getAuth();
|
|
33
29
|
email = await prompt(existing ? `Email [${existing.email}]: ` : 'Email: ');
|
|
34
30
|
if (!email && existing)
|
|
35
31
|
email = existing.email;
|
|
36
32
|
if (!email) {
|
|
37
|
-
console.log('');
|
|
38
33
|
console.error(clrError('Email required.'));
|
|
39
|
-
console.log('');
|
|
40
34
|
process.exit(1);
|
|
41
35
|
}
|
|
42
36
|
await publicPost('/auth/login', { email });
|
|
43
|
-
console.log('');
|
|
44
37
|
console.log('Check your email for a 6-digit code.');
|
|
45
|
-
console.log('');
|
|
46
38
|
code = await prompt('Code: ');
|
|
47
39
|
await verify(email, code);
|
|
48
40
|
}
|
|
49
41
|
catch (err) {
|
|
50
|
-
console.log('');
|
|
51
42
|
console.error(clrError(`Login failed: ${err.message}`));
|
|
52
|
-
console.log('');
|
|
53
43
|
process.exit(1);
|
|
54
44
|
}
|
|
55
45
|
});
|
|
@@ -57,9 +47,7 @@ async function verify(email, code) {
|
|
|
57
47
|
const res = await publicPost('/auth/verify', { email, code });
|
|
58
48
|
const exp = decodeJwtExp(res.accessToken);
|
|
59
49
|
if (!exp) {
|
|
60
|
-
console.log('');
|
|
61
50
|
console.error(clrError('Invalid token received.'));
|
|
62
|
-
console.log('');
|
|
63
51
|
process.exit(1);
|
|
64
52
|
}
|
|
65
53
|
const expiresAt = new Date(exp * 1000).toISOString();
|
|
@@ -69,8 +57,6 @@ async function verify(email, code) {
|
|
|
69
57
|
email,
|
|
70
58
|
expiresAt,
|
|
71
59
|
});
|
|
72
|
-
console.log('');
|
|
73
60
|
console.log(success(`Logged in (${email}).`));
|
|
74
|
-
console.log('');
|
|
75
61
|
}
|
|
76
62
|
//# sourceMappingURL=login.js.map
|
package/dist/commands/logout.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { getAuth, clearAuth } from '../auth.js';
|
|
3
|
+
import { success } from '../colors.js';
|
|
3
4
|
export const logoutCommand = new Command('logout')
|
|
4
5
|
.description('Log out')
|
|
5
6
|
.action(() => {
|
|
6
7
|
const auth = getAuth();
|
|
7
|
-
console.log('');
|
|
8
8
|
if (!auth) {
|
|
9
9
|
console.log('Not logged in.');
|
|
10
10
|
}
|
|
11
11
|
else {
|
|
12
12
|
clearAuth();
|
|
13
|
-
console.log(`Logged out (${auth.email}).`);
|
|
13
|
+
console.log(success(`Logged out (${auth.email}).`));
|
|
14
14
|
}
|
|
15
|
-
console.log('');
|
|
16
15
|
});
|
|
17
16
|
//# sourceMappingURL=logout.js.map
|
package/dist/commands/logs.js
CHANGED
|
@@ -30,7 +30,7 @@ logsCommand
|
|
|
30
30
|
const status = statusColor(log.status.padEnd(8));
|
|
31
31
|
const trigger = muted(log.trigger_type.padEnd(8));
|
|
32
32
|
const err = log.error_message ? ` ${clrError(`"${log.error_message}"`)}` : '';
|
|
33
|
-
console.log(
|
|
33
|
+
console.log(`${muted(time)} ${status} ${dur} ${trigger}${err}`);
|
|
34
34
|
}
|
|
35
35
|
}));
|
|
36
36
|
logsCommand
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { post, get, ApiError } from '../api.js';
|
|
3
|
-
import { brand, bold, muted } from '../colors.js';
|
|
3
|
+
import { brand, bold, muted, warning } from '../colors.js';
|
|
4
4
|
import { run } from '../helpers/index.js';
|
|
5
5
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6
6
|
/** Poll the async eval job until it finishes. Eval runs server-side as a
|
|
@@ -60,11 +60,13 @@ export const pageEvalCommand = new Command('eval')
|
|
|
60
60
|
console.log(JSON.stringify(d));
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
-
console.log(
|
|
64
|
-
|
|
63
|
+
console.log(`${brand('Eval')} ${bold(d.url || url)}`);
|
|
64
|
+
if (d.navigationIncomplete) {
|
|
65
|
+
console.log(`${warning('⚠ Navigation incomplete:')} ${d.note || 'page did not reach full load'}`);
|
|
66
|
+
}
|
|
67
|
+
console.log(`${muted('Expression:')} ${expr}`);
|
|
65
68
|
console.log(`\n${d.result || muted('(empty result)')}`);
|
|
66
69
|
if (d.truncated)
|
|
67
70
|
console.log(muted('\n(result truncated to fit context - narrow the expression for the full value)'));
|
|
68
|
-
console.log('');
|
|
69
71
|
}));
|
|
70
72
|
//# sourceMappingURL=page-eval.js.map
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createHash } from 'crypto';
|
|
3
|
+
import { brand, bold, muted, success, warning, error as clrError } from '../colors.js';
|
|
4
|
+
import { formatSize } from '../utils.js';
|
|
5
|
+
import { run } from '../helpers/index.js';
|
|
6
|
+
function expectedType(path) {
|
|
7
|
+
const p = path.toLowerCase().split('?')[0].split('#')[0];
|
|
8
|
+
if (p.endsWith('.json'))
|
|
9
|
+
return 'json';
|
|
10
|
+
if (p.endsWith('.xml'))
|
|
11
|
+
return 'xml';
|
|
12
|
+
if (p.endsWith('.html') || p.endsWith('.htm'))
|
|
13
|
+
return 'html';
|
|
14
|
+
if (p.endsWith('.txt') || p.endsWith('.md') || p.endsWith('.markdown'))
|
|
15
|
+
return 'text';
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function contentTypeMatches(expect, ct) {
|
|
19
|
+
if (!expect || !ct)
|
|
20
|
+
return true; // no expectation, or host sent no type → don't flag
|
|
21
|
+
const c = ct.toLowerCase();
|
|
22
|
+
switch (expect) {
|
|
23
|
+
case 'json': return c.includes('application/json');
|
|
24
|
+
case 'xml': return c.includes('xml');
|
|
25
|
+
case 'html': return c.includes('text/html');
|
|
26
|
+
case 'text': return c.includes('text/plain') || c.includes('text/markdown');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function looksLikeHtml(body) {
|
|
30
|
+
const head = body.slice(0, 300).toLowerCase();
|
|
31
|
+
return /<!doctype html|<html[\s>]/.test(head);
|
|
32
|
+
}
|
|
33
|
+
function sha256(s) {
|
|
34
|
+
return createHash('sha256').update(s).digest('hex');
|
|
35
|
+
}
|
|
36
|
+
function baseWithSlash(url) {
|
|
37
|
+
return url.endsWith('/') ? url : url + '/';
|
|
38
|
+
}
|
|
39
|
+
/** Resolve a file path against the app base, keeping the app's subpath. */
|
|
40
|
+
function resolveUrl(base, path) {
|
|
41
|
+
return new URL(path.replace(/^\/+/, ''), baseWithSlash(base)).toString();
|
|
42
|
+
}
|
|
43
|
+
async function fetchRaw(url) {
|
|
44
|
+
try {
|
|
45
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
46
|
+
const body = await res.text();
|
|
47
|
+
return { status: res.status, contentType: res.headers.get('content-type'), body };
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null; // DNS failure, connection refused, etc.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Hit a guaranteed-absent path to learn whether the host serves a catch-all shell. */
|
|
54
|
+
async function probeShell(base) {
|
|
55
|
+
const rand = Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
56
|
+
const r = await fetchRaw(resolveUrl(base, `__gipity_probe_${rand}.notreal`));
|
|
57
|
+
if (!r)
|
|
58
|
+
return { served: false, status: 0, sha256: null, isHtml: false };
|
|
59
|
+
return {
|
|
60
|
+
served: r.status >= 200 && r.status < 300,
|
|
61
|
+
status: r.status,
|
|
62
|
+
sha256: sha256(r.body),
|
|
63
|
+
isHtml: looksLikeHtml(r.body),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function classify(path, r, shell) {
|
|
67
|
+
if (!r)
|
|
68
|
+
return { status: null, contentType: null, bytes: 0, verdict: 'MISSING', detail: 'fetch failed (host unreachable)' };
|
|
69
|
+
const bytes = Buffer.byteLength(r.body);
|
|
70
|
+
const base = { status: r.status, contentType: r.contentType, bytes };
|
|
71
|
+
// Honest 404/5xx - the file simply isn't there.
|
|
72
|
+
if (r.status >= 400)
|
|
73
|
+
return { ...base, verdict: 'MISSING', detail: `HTTP ${r.status}` };
|
|
74
|
+
const expect = expectedType(path);
|
|
75
|
+
// 1) Served the catch-all shell verbatim → 200, but the file isn't deployed.
|
|
76
|
+
if (shell.served && shell.sha256 && sha256(r.body) === shell.sha256) {
|
|
77
|
+
return { ...base, verdict: 'MISSING', detail: `HTTP ${r.status} but served the SPA shell (file not deployed)` };
|
|
78
|
+
}
|
|
79
|
+
// 2) Asked for a non-HTML asset but got an HTML body → a per-path shell variant.
|
|
80
|
+
if (expect && expect !== 'html' && looksLikeHtml(r.body)) {
|
|
81
|
+
return { ...base, verdict: 'MISSING', detail: `HTTP ${r.status} but got HTML where ${expect} expected (SPA shell)` };
|
|
82
|
+
}
|
|
83
|
+
// 3) Present and real, but the content-type header disagrees with the extension.
|
|
84
|
+
if (expect && !contentTypeMatches(expect, r.contentType)) {
|
|
85
|
+
return { ...base, verdict: 'WRONG-TYPE', detail: `expected ${expect}, served as ${r.contentType ?? '(none)'}` };
|
|
86
|
+
}
|
|
87
|
+
return { ...base, verdict: 'OK', detail: r.contentType ?? '' };
|
|
88
|
+
}
|
|
89
|
+
const TAG = {
|
|
90
|
+
'OK': success('OK '),
|
|
91
|
+
'MISSING': clrError('MISSING '),
|
|
92
|
+
'WRONG-TYPE': warning('WRONG-TYPE'),
|
|
93
|
+
};
|
|
94
|
+
export const pageFetchCommand = new Command('fetch')
|
|
95
|
+
.description('Verify deployed non-rendered files (llms.txt, AGENTS.md, robots.txt, served JSON…) really exist - catches the static-host trap where a missing file returns 200 with the SPA shell instead of a 404')
|
|
96
|
+
.argument('<url>', 'Deployed app base URL; file paths resolve relative to it')
|
|
97
|
+
.argument('<paths...>', 'One or more file paths to verify, e.g. llms.txt AGENTS.md robots.txt')
|
|
98
|
+
.option('--json', 'Output as JSON')
|
|
99
|
+
.action((url, paths, opts) => run('Page fetch', async () => {
|
|
100
|
+
const shell = await probeShell(url);
|
|
101
|
+
const results = [];
|
|
102
|
+
for (const p of paths) {
|
|
103
|
+
const target = resolveUrl(url, p);
|
|
104
|
+
const c = classify(p, await fetchRaw(target), shell);
|
|
105
|
+
results.push({ path: p, url: target, ...c });
|
|
106
|
+
}
|
|
107
|
+
const failed = results.filter((r) => r.verdict !== 'OK');
|
|
108
|
+
if (opts.json) {
|
|
109
|
+
console.log(JSON.stringify({
|
|
110
|
+
base: baseWithSlash(url),
|
|
111
|
+
shellFallback: shell.served,
|
|
112
|
+
ok: failed.length === 0,
|
|
113
|
+
failed: failed.length,
|
|
114
|
+
files: results,
|
|
115
|
+
}));
|
|
116
|
+
if (failed.length)
|
|
117
|
+
process.exitCode = 1;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
console.log(`${brand('Page fetch')} ${bold(baseWithSlash(url))}`);
|
|
121
|
+
if (shell.served) {
|
|
122
|
+
console.log(`${muted('Host serves a catch-all shell for unknown paths - a bare 200 is not proof; verifying bodies.')}`);
|
|
123
|
+
}
|
|
124
|
+
const pad = Math.min(48, Math.max(...results.map((r) => r.path.length)));
|
|
125
|
+
for (const r of results) {
|
|
126
|
+
const size = r.verdict === 'OK' ? muted(formatSize(r.bytes).padStart(9)) : ' '.repeat(9);
|
|
127
|
+
const detail = r.detail && r.verdict !== 'OK' ? muted(` ${r.detail}`) : '';
|
|
128
|
+
console.log(`${TAG[r.verdict]} ${r.path.padEnd(pad)} ${size}${detail}`);
|
|
129
|
+
}
|
|
130
|
+
console.log(failed.length === 0
|
|
131
|
+
? `\n${success(`✓ all ${results.length} file(s) present with the right content-type`)}`
|
|
132
|
+
: `\n${clrError(`✗ ${failed.length} of ${results.length} file(s) missing or wrong-type`)}`);
|
|
133
|
+
if (failed.length)
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
}));
|
|
136
|
+
//# sourceMappingURL=page-fetch.js.map
|
|
@@ -58,27 +58,30 @@ export const pageInspectCommand = new Command('inspect')
|
|
|
58
58
|
}
|
|
59
59
|
const timing = b.timing || { ttfb: 0, domReady: 0, load: 0 };
|
|
60
60
|
// ── Page Info ──
|
|
61
|
-
console.log(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
console.log(`${brand('Inspecting')} ${bold(b.url || url)}`);
|
|
62
|
+
if (b.navigationIncomplete) {
|
|
63
|
+
console.log(`${warning('⚠ Navigation incomplete:')} ${b.note || 'page did not reach full load'}`);
|
|
64
|
+
}
|
|
65
|
+
console.log(`${muted('Title:')} ${b.title || '(none)'}`);
|
|
66
|
+
console.log(`${muted('Elements:')} ${b.elementCount || 0}`);
|
|
67
|
+
console.log(`${muted('Page weight:')} ${info(formatSize(b.totalBytes || 0))}`);
|
|
65
68
|
// ── Timing ──
|
|
66
|
-
console.log(`\n
|
|
67
|
-
console.log(
|
|
68
|
-
console.log(
|
|
69
|
-
console.log(
|
|
69
|
+
console.log(`\n${bold('Timing:')}`);
|
|
70
|
+
console.log(`${muted('TTFB:')} ${timing.ttfb}ms`);
|
|
71
|
+
console.log(`${muted('DOM ready:')} ${timing.domReady}ms`);
|
|
72
|
+
console.log(`${muted('Load:')} ${timing.load}ms`);
|
|
70
73
|
if (showAll && b.lcp) {
|
|
71
|
-
console.log(`
|
|
74
|
+
console.log(`LCP: ${b.lcp.time}ms (${b.lcp.element}${b.lcp.url ? ' ' + shortUrl(b.lcp.url, truncate) : ''})`);
|
|
72
75
|
}
|
|
73
76
|
// ── Console ──
|
|
74
77
|
if (b.console?.length > 0) {
|
|
75
|
-
console.log(`\n
|
|
78
|
+
console.log(`\n${bold('Console')} ${muted(`(${b.console.length})`)}:`);
|
|
76
79
|
for (const line of b.console) {
|
|
77
|
-
console.log(
|
|
80
|
+
console.log(`${warning(line)}`);
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
else {
|
|
81
|
-
console.log(`\n
|
|
84
|
+
console.log(`\n${bold('Console:')} ${muted('(clean)')}`);
|
|
82
85
|
}
|
|
83
86
|
// ── Failed Resources ──
|
|
84
87
|
// Browsers auto-request /favicon.ico at the site root for every page, so a
|
|
@@ -97,57 +100,56 @@ export const pageInspectCommand = new Command('inspect')
|
|
|
97
100
|
const failed = (b.failedResources || []).filter((r) => !isImplicitFavicon(r));
|
|
98
101
|
const rootFaviconMissing = (b.failedResources || []).some(isImplicitFavicon);
|
|
99
102
|
if (failed.length > 0) {
|
|
100
|
-
console.log(`\n
|
|
103
|
+
console.log(`\n${clrError(`Failed resources (${failed.length}):`)}`);
|
|
101
104
|
for (const r of failed) {
|
|
102
|
-
console.log(
|
|
105
|
+
console.log(`${clrError(r)}`);
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
108
|
if (rootFaviconMissing) {
|
|
106
|
-
console.log(`\n
|
|
109
|
+
console.log(`\n${muted('No root /favicon.ico (browsers request this automatically; harmless for app pages served under a subpath)')}`);
|
|
107
110
|
}
|
|
108
111
|
// ── Layout (horizontal overflow) ──
|
|
109
112
|
if (b.overflow) {
|
|
110
113
|
if (b.overflow.overflowX) {
|
|
111
|
-
console.log(`\n
|
|
114
|
+
console.log(`\n${clrError(`Horizontal overflow: +${b.overflow.amount}px`)} ${muted(`(content ${b.overflow.scrollWidth}px vs viewport ${b.overflow.clientWidth}px)`)}`);
|
|
112
115
|
if (showAll && b.overflow.culprits.length > 0) {
|
|
113
|
-
console.log(
|
|
116
|
+
console.log(`${muted('Overflowing elements:')}`);
|
|
114
117
|
for (const c of b.overflow.culprits) {
|
|
115
118
|
const sel = c.cls ? `${c.tag}.${c.cls.split(/\s+/)[0]}` : c.tag;
|
|
116
|
-
console.log(
|
|
119
|
+
console.log(`${sel} ${muted(`(right ${c.right}px, width ${c.width}px)`)}`);
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
122
|
else if (b.overflow.culprits.length > 0) {
|
|
120
|
-
console.log(
|
|
123
|
+
console.log(`${muted(`${b.overflow.culprits.length} overflowing element(s) - use --all to list`)}`);
|
|
121
124
|
}
|
|
122
125
|
}
|
|
123
126
|
else {
|
|
124
|
-
console.log(`\n
|
|
127
|
+
console.log(`\n${bold('Layout:')} ${muted('no horizontal overflow')}`);
|
|
125
128
|
}
|
|
126
129
|
}
|
|
127
130
|
if (showAll) {
|
|
128
131
|
// ── Render Blocking ──
|
|
129
132
|
if (b.renderBlocking?.length > 0) {
|
|
130
|
-
console.log(`\n
|
|
133
|
+
console.log(`\n${warning(`Render-blocking (${b.renderBlocking.length}):`)}`);
|
|
131
134
|
for (const r of b.renderBlocking) {
|
|
132
|
-
console.log(
|
|
135
|
+
console.log(`${shortUrl(r, truncate)}`);
|
|
133
136
|
}
|
|
134
137
|
}
|
|
135
138
|
// ── Large Resources ──
|
|
136
139
|
if (b.largeResources?.length > 0) {
|
|
137
|
-
console.log(`\n
|
|
140
|
+
console.log(`\n${warning(`Large resources >100KB (${b.largeResources.length}):`)}`);
|
|
138
141
|
for (const r of b.largeResources) {
|
|
139
|
-
console.log(
|
|
142
|
+
console.log(`${info(formatSize(r.size).padEnd(10))} ${muted(r.type.padEnd(8))} ${shortUrl(r.url, truncate)}`);
|
|
140
143
|
}
|
|
141
144
|
}
|
|
142
145
|
// ── Oversized Images ──
|
|
143
146
|
if (b.oversizedImages?.length > 0) {
|
|
144
|
-
console.log(`\n
|
|
147
|
+
console.log(`\n${warning(`Oversized images (${b.oversizedImages.length}):`)}`);
|
|
145
148
|
for (const img of b.oversizedImages) {
|
|
146
|
-
console.log(
|
|
149
|
+
console.log(`${img.natural} served, ${img.displayed} displayed - ${shortUrl(img.src, truncate)}`);
|
|
147
150
|
}
|
|
148
151
|
}
|
|
149
152
|
}
|
|
150
|
-
console.log('');
|
|
151
153
|
});
|
|
152
154
|
});
|
|
153
155
|
//# sourceMappingURL=page-inspect.js.map
|
|
@@ -183,41 +183,40 @@ export const pageScreenshotCommand = new Command('screenshot')
|
|
|
183
183
|
}
|
|
184
184
|
if (meta.screenshots.length === 1) {
|
|
185
185
|
const s = meta.screenshots[0];
|
|
186
|
-
console.log(
|
|
186
|
+
console.log(`${brand('Screenshot')} ${bold(url)}`);
|
|
187
187
|
if (meta.title)
|
|
188
|
-
console.log(
|
|
188
|
+
console.log(`${label('Web page title')} ${meta.title}`);
|
|
189
189
|
if (meta.finalUrl)
|
|
190
|
-
console.log(
|
|
190
|
+
console.log(`${label('Web page URL')} ${meta.finalUrl}`);
|
|
191
191
|
if (meta.status != null)
|
|
192
|
-
console.log(
|
|
192
|
+
console.log(`${label('Web page status')} ${meta.status}`);
|
|
193
193
|
if (meta.performance)
|
|
194
|
-
console.log(
|
|
194
|
+
console.log(`${label('Web page perf')} ${fmtPerformance(meta.performance)}`);
|
|
195
195
|
const sizePart = formatSize(s.screenshotSizeBytes) + (meta.full ? ' (full page)' : '');
|
|
196
|
-
console.log(
|
|
196
|
+
console.log(`${label('Screenshot size')} ${sizePart}`);
|
|
197
197
|
if (s.width && s.height)
|
|
198
|
-
console.log(
|
|
199
|
-
console.log(
|
|
198
|
+
console.log(`${label('Screenshot dims')} ${s.width} × ${s.height}`);
|
|
199
|
+
console.log(`${label('Screenshot file')} ${success(savedFiles[0])}`);
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
202
|
-
console.log(
|
|
202
|
+
console.log(`${brand('Loading')} ${bold(url)} ${muted(`once → ${meta.screenshots.length} viewports`)}`);
|
|
203
203
|
if (meta.title)
|
|
204
|
-
console.log(
|
|
204
|
+
console.log(`${label('Web page title')} ${meta.title}`);
|
|
205
205
|
if (meta.finalUrl)
|
|
206
|
-
console.log(
|
|
206
|
+
console.log(`${label('Web page URL')} ${meta.finalUrl}`);
|
|
207
207
|
if (meta.status != null)
|
|
208
|
-
console.log(
|
|
208
|
+
console.log(`${label('Web page status')} ${meta.status}`);
|
|
209
209
|
if (meta.performance)
|
|
210
|
-
console.log(
|
|
210
|
+
console.log(`${label('Web page perf')} ${fmtPerformance(meta.performance)}`);
|
|
211
211
|
for (let i = 0; i < meta.screenshots.length; i++) {
|
|
212
212
|
const s = meta.screenshots[i];
|
|
213
213
|
const dims = `${s.viewport.width}×${s.viewport.height}${s.viewport.deviceScaleFactor > 1 ? ` @${s.viewport.deviceScaleFactor}x` : ''}`;
|
|
214
|
-
console.log(`\n
|
|
214
|
+
console.log(`\n${brand('@ ' + dims)}`);
|
|
215
215
|
const sizePart = formatSize(s.screenshotSizeBytes) + (meta.full ? ' (full page)' : '');
|
|
216
|
-
console.log(
|
|
216
|
+
console.log(`${label('Screenshot size')} ${sizePart}`);
|
|
217
217
|
if (s.width && s.height)
|
|
218
|
-
console.log(
|
|
219
|
-
console.log(
|
|
218
|
+
console.log(`${label('Screenshot dims')} ${s.width} × ${s.height}`);
|
|
219
|
+
console.log(`${label('Screenshot file')} ${success(savedFiles[i])}`);
|
|
220
220
|
}
|
|
221
|
-
console.log('');
|
|
222
221
|
}));
|
|
223
222
|
//# sourceMappingURL=page-screenshot.js.map
|
|
@@ -36,15 +36,15 @@ export const pageTestCommand = new Command('test')
|
|
|
36
36
|
const stagger = Math.max(0, parseInt(opts.stagger, 10) || 0);
|
|
37
37
|
const wait = Math.min(30000, Math.max(2000, parseInt(opts.wait, 10) || 24000));
|
|
38
38
|
if (!opts.json) {
|
|
39
|
-
console.log(
|
|
40
|
-
console.log(
|
|
39
|
+
console.log(`${brand('Page test')} ${bold(url)}`);
|
|
40
|
+
console.log(`${muted(`${clients} client(s), stagger ${stagger}s, ${wait}ms open each`)}`);
|
|
41
41
|
}
|
|
42
42
|
const runs = [];
|
|
43
43
|
for (let i = 0; i < clients; i++) {
|
|
44
44
|
runs.push((async () => {
|
|
45
45
|
await sleep(i * stagger * 1000);
|
|
46
46
|
if (!opts.json)
|
|
47
|
-
console.log(
|
|
47
|
+
console.log(`${muted(`client ${i}${i === 0 ? ' (first)' : ''} starting`)}`);
|
|
48
48
|
return inspectClient(url, wait, i);
|
|
49
49
|
})());
|
|
50
50
|
}
|
|
@@ -65,21 +65,21 @@ export const pageTestCommand = new Command('test')
|
|
|
65
65
|
for (const r of results) {
|
|
66
66
|
console.log(`\n${bold(`=== client ${r.i}${r.i === 0 ? ' (first)' : ''} ===`)}`);
|
|
67
67
|
if (r.error) {
|
|
68
|
-
console.log(
|
|
68
|
+
console.log(`${clrError(`page inspect failed: ${r.error}`)}`);
|
|
69
69
|
continue;
|
|
70
70
|
}
|
|
71
71
|
if (r.lines.length === 0) {
|
|
72
|
-
console.log(
|
|
72
|
+
console.log(`${muted('(no console output)')}`);
|
|
73
73
|
continue;
|
|
74
74
|
}
|
|
75
75
|
for (const line of r.lines) {
|
|
76
76
|
const bad = BAD.test(line);
|
|
77
|
-
console.log(
|
|
77
|
+
console.log(`${bad ? warning('⚠ ' + line) : ' ' + line}`);
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
console.log(problems === 0
|
|
81
|
-
? `\n${success('✓ no error/crash lines across all clients')}
|
|
82
|
-
: `\n${clrError(`⚠ ${problems} error/crash line(s) flagged above`)}
|
|
81
|
+
? `\n${success('✓ no error/crash lines across all clients')}`
|
|
82
|
+
: `\n${clrError(`⚠ ${problems} error/crash line(s) flagged above`)}`);
|
|
83
83
|
if (problems > 0)
|
|
84
84
|
process.exitCode = 1;
|
|
85
85
|
}));
|
package/dist/commands/page.js
CHANGED
|
@@ -3,16 +3,19 @@ import { pageInspectCommand } from './page-inspect.js';
|
|
|
3
3
|
import { pageScreenshotCommand } from './page-screenshot.js';
|
|
4
4
|
import { pageEvalCommand } from './page-eval.js';
|
|
5
5
|
import { pageTestCommand } from './page-test.js';
|
|
6
|
+
import { pageFetchCommand } from './page-fetch.js';
|
|
6
7
|
// Parent namespace grouping the page/browser diagnostics under one command:
|
|
7
|
-
// gipity page inspect | eval | screenshot | test
|
|
8
|
+
// gipity page inspect | eval | screenshot | test | fetch
|
|
8
9
|
// Each subcommand is canonical for its capability; the namespace keeps the
|
|
9
10
|
// top-level surface lean and makes the siblings discoverable via `page --help`.
|
|
11
|
+
// `inspect` is the rendered DOM (browser); `fetch` is the raw asset (plain HTTP).
|
|
10
12
|
export const pageCommand = new Command('page')
|
|
11
|
-
.description('Inspect, evaluate, screenshot,
|
|
13
|
+
.description('Inspect, evaluate, screenshot, multi-client test, and verify raw files of web pages (page inspect | eval | screenshot | test | fetch)')
|
|
12
14
|
.addCommand(pageInspectCommand)
|
|
13
15
|
.addCommand(pageEvalCommand)
|
|
14
16
|
.addCommand(pageScreenshotCommand)
|
|
15
|
-
.addCommand(pageTestCommand)
|
|
17
|
+
.addCommand(pageTestCommand)
|
|
18
|
+
.addCommand(pageFetchCommand);
|
|
16
19
|
// No subcommand → show help instead of commander's terse error.
|
|
17
20
|
pageCommand.action(() => {
|
|
18
21
|
pageCommand.help();
|
package/dist/commands/plan.js
CHANGED
|
@@ -22,7 +22,7 @@ function formatLimit(key, value) {
|
|
|
22
22
|
}
|
|
23
23
|
return value.toLocaleString();
|
|
24
24
|
}
|
|
25
|
-
function renderLimits(limits, indent = '
|
|
25
|
+
function renderLimits(limits, indent = '') {
|
|
26
26
|
for (const key of Object.keys(LIMIT_LABELS)) {
|
|
27
27
|
const value = limits[key];
|
|
28
28
|
if (value !== undefined) {
|
|
@@ -48,14 +48,14 @@ export const planCommand = new Command('plan')
|
|
|
48
48
|
const price = plan.monthlyPriceUsd > 0 ? ` $${plan.monthlyPriceUsd}/mo` : '';
|
|
49
49
|
console.log(`Plan: ${brand(plan.displayName)} (${plan.tier})${price}`);
|
|
50
50
|
if (plan.monthlyCredits > 0) {
|
|
51
|
-
console.log(
|
|
51
|
+
console.log(`${plan.monthlyCredits.toLocaleString()} credits/mo, ${plan.creditExpiryDays}-day expiry`);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
else {
|
|
55
55
|
console.log(`Plan: ${tier} (no matching plan row)`);
|
|
56
56
|
}
|
|
57
57
|
if (planAppliedAt) {
|
|
58
|
-
console.log(
|
|
58
|
+
console.log(`${dim('Applied: ' + new Date(planAppliedAt).toLocaleDateString())}`);
|
|
59
59
|
}
|
|
60
60
|
console.log('\nLimits:');
|
|
61
61
|
renderLimits(limits);
|
|
@@ -85,7 +85,7 @@ planCommand
|
|
|
85
85
|
? ` - ${plan.monthlyCredits.toLocaleString()} credits/mo (${plan.creditExpiryDays}-day expiry)`
|
|
86
86
|
: '';
|
|
87
87
|
console.log(`${marker}${plan.displayName} (${plan.tier})${price}${credits}`);
|
|
88
|
-
renderLimits(plan.limits
|
|
88
|
+
renderLimits(plan.limits);
|
|
89
89
|
console.log('');
|
|
90
90
|
}
|
|
91
91
|
console.log(dim('(* = your current plan)'));
|
package/dist/commands/push.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { resolve } from 'path';
|
|
3
3
|
import { pushFile } from '../sync.js';
|
|
4
|
-
import { error as clrError } from '../colors.js';
|
|
4
|
+
import { error as clrError, success } from '../colors.js';
|
|
5
5
|
export const pushCommand = new Command('push')
|
|
6
6
|
.description('Push a file')
|
|
7
7
|
.argument('<file>', 'File path to push')
|
|
@@ -22,16 +22,12 @@ export const pushCommand = new Command('push')
|
|
|
22
22
|
}
|
|
23
23
|
await pushFile(fullPath);
|
|
24
24
|
if (!opts.quiet) {
|
|
25
|
-
console.log(
|
|
26
|
-
console.log(`Pushed ${file}`);
|
|
27
|
-
console.log('');
|
|
25
|
+
console.log(success(`Pushed ${file}`));
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
28
|
catch (err) {
|
|
31
29
|
if (!opts.quiet) {
|
|
32
|
-
console.log('');
|
|
33
30
|
console.error(clrError(`Push failed: ${err.message}`));
|
|
34
|
-
console.log('');
|
|
35
31
|
}
|
|
36
32
|
process.exit(1);
|
|
37
33
|
}
|
|
@@ -32,7 +32,7 @@ const roomCommand = new Command('room')
|
|
|
32
32
|
switch (sub) {
|
|
33
33
|
case 'list': {
|
|
34
34
|
const res = await get(base);
|
|
35
|
-
printList(res.data, opts, 'No realtime rooms. Create one: gipity realtime room create <name>', r =>
|
|
35
|
+
printList(res.data, opts, 'No realtime rooms. Create one: gipity realtime room create <name>', r => `${bold(r.name)} ${muted(`${r.room_type} · ${r.auth_level} · max ${r.max_clients}`)}`);
|
|
36
36
|
break;
|
|
37
37
|
}
|
|
38
38
|
case 'create': {
|
|
@@ -74,7 +74,7 @@ const roomCommand = new Command('room')
|
|
|
74
74
|
console.log(JSON.stringify({ success: true }));
|
|
75
75
|
}
|
|
76
76
|
else {
|
|
77
|
-
console.log(`Deleted room '${name}'
|
|
77
|
+
console.log(success(`Deleted room '${name}'.`) + ' Active instances drain as clients disconnect.');
|
|
78
78
|
}
|
|
79
79
|
break;
|
|
80
80
|
}
|
|
@@ -89,13 +89,11 @@ const roomCommand = new Command('room')
|
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
91
|
const { room, live } = res.data;
|
|
92
|
-
console.log(
|
|
93
|
-
console.log(`
|
|
94
|
-
console.log(`
|
|
95
|
-
console.log(`
|
|
96
|
-
console.log(`
|
|
97
|
-
console.log(` Live: ${live ? `${live.instances} instance(s), ${live.clients} client(s)` : muted('Colyseus server unreachable')}`);
|
|
98
|
-
console.log('');
|
|
92
|
+
console.log(`${bold(room.name)}`);
|
|
93
|
+
console.log(`Type: ${room.room_type}`);
|
|
94
|
+
console.log(`Auth: ${room.auth_level}`);
|
|
95
|
+
console.log(`Max clients: ${room.max_clients}`);
|
|
96
|
+
console.log(`Live: ${live ? `${live.instances} instance(s), ${live.clients} client(s)` : muted('Colyseus server unreachable')}`);
|
|
99
97
|
}
|
|
100
98
|
break;
|
|
101
99
|
}
|