gipity 1.0.356 → 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/capture/sources/claude-code.js +76 -11
- package/dist/commands/add.js +45 -28
- 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 +73 -18
- 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 +48 -7
- package/dist/commands/page-fetch.js +136 -0
- package/dist/commands/page-inspect.js +59 -29
- package/dist/commands/page-screenshot.js +51 -41
- package/dist/commands/page-test.js +86 -0
- package/dist/commands/page.js +8 -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 +54 -0
- package/dist/commands/skill.js +2 -1
- 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 +148 -0
- 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 +11 -16
- package/dist/config.js +8 -1
- package/dist/help-skills.js +1 -0
- package/dist/helpers/output.js +52 -8
- package/dist/helpers/text-analysis.js +200 -0
- package/dist/hooks/capture-runner.js +32 -8
- package/dist/index.js +35 -2
- package/dist/knowledge.js +32 -7
- package/dist/progress.js +60 -0
- package/dist/project-setup.js +5 -1
- package/dist/provider-docs.js +7 -7
- package/dist/relay/daemon.js +11 -1
- package/dist/relay/stream-json.js +45 -8
- package/dist/setup.js +38 -11
- package/dist/sync.js +30 -8
- package/dist/updater/shim.js +18 -4
- package/dist/upload.js +6 -0
- package/package.json +5 -4
package/dist/commands/domain.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { get, post, del } from '../api.js';
|
|
3
3
|
import { getConfig, requireConfig } from '../config.js';
|
|
4
|
-
import { error as clrError, success, muted } from '../colors.js';
|
|
4
|
+
import { error as clrError, success, muted, brand } from '../colors.js';
|
|
5
5
|
import { run, printList } from '../helpers/index.js';
|
|
6
6
|
export const domainCommand = new Command('domain')
|
|
7
7
|
.description('Manage custom domains')
|
|
@@ -21,7 +21,7 @@ export const domainCommand = new Command('domain')
|
|
|
21
21
|
console.log(JSON.stringify(res.data));
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
console.log(
|
|
24
|
+
console.log(`${muted('Domains:')} ${brand(`${count}/${limit}`)}\n`);
|
|
25
25
|
if (domains.length === 0) {
|
|
26
26
|
console.log('No custom domains.');
|
|
27
27
|
return;
|
|
@@ -34,18 +34,21 @@ export const domainCommand = new Command('domain')
|
|
|
34
34
|
grouped.set(key, []);
|
|
35
35
|
grouped.get(key).push(d);
|
|
36
36
|
}
|
|
37
|
+
let first = true;
|
|
37
38
|
for (const [label, doms] of grouped) {
|
|
38
|
-
|
|
39
|
+
if (!first)
|
|
40
|
+
console.log('');
|
|
41
|
+
first = false;
|
|
42
|
+
console.log(brand(label));
|
|
39
43
|
for (const d of doms) {
|
|
40
|
-
console.log(
|
|
44
|
+
console.log(`${d.domain} ${muted(d.status)} ${muted(`[${d.shortGuid}]`)}`);
|
|
41
45
|
}
|
|
42
|
-
console.log();
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
else {
|
|
46
49
|
const config = requireConfig();
|
|
47
50
|
const res = await get(`/projects/${config.projectGuid}/domains`);
|
|
48
|
-
printList(res.data, opts, 'No custom domains configured.', d =>
|
|
51
|
+
printList(res.data, opts, 'No custom domains configured.', d => `${d.domain} ${muted(d.status)} ${muted(`[${d.short_guid}]`)}`);
|
|
49
52
|
}
|
|
50
53
|
break;
|
|
51
54
|
}
|
|
@@ -64,15 +67,15 @@ export const domainCommand = new Command('domain')
|
|
|
64
67
|
console.log(success(`Domain "${data.domain.domain}" added.`));
|
|
65
68
|
console.log('');
|
|
66
69
|
console.log('Add this DNS record:');
|
|
67
|
-
console.log(
|
|
68
|
-
console.log(
|
|
69
|
-
console.log(
|
|
70
|
+
console.log(`${muted('Type:'.padEnd(8))}${data.instructions.type}`);
|
|
71
|
+
console.log(`${muted('Name:'.padEnd(8))}${data.instructions.name}`);
|
|
72
|
+
console.log(`${muted('Target:'.padEnd(8))}${data.instructions.target}`);
|
|
70
73
|
if (data.instructions.note) {
|
|
71
74
|
console.log('');
|
|
72
75
|
console.log(data.instructions.note);
|
|
73
76
|
}
|
|
74
77
|
console.log('');
|
|
75
|
-
console.log(`Then run: gipity domain verify ${data.domain.short_guid}`);
|
|
78
|
+
console.log(muted(`Then run: gipity domain verify ${data.domain.short_guid}`));
|
|
76
79
|
}
|
|
77
80
|
break;
|
|
78
81
|
}
|
|
@@ -121,11 +124,11 @@ export const domainCommand = new Command('domain')
|
|
|
121
124
|
default:
|
|
122
125
|
console.log('Usage: gipity domain [list|add|verify|remove]');
|
|
123
126
|
console.log('');
|
|
124
|
-
console.log('
|
|
125
|
-
console.log('
|
|
126
|
-
console.log('
|
|
127
|
-
console.log('
|
|
128
|
-
console.log('
|
|
127
|
+
console.log(`${brand('gipity domain list')} ${muted('List project domains')}`);
|
|
128
|
+
console.log(`${brand('gipity domain list --all')} ${muted('List all domains across projects')}`);
|
|
129
|
+
console.log(`${brand('gipity domain add <domain.com>')} ${muted('Add a custom domain (requires project)')}`);
|
|
130
|
+
console.log(`${brand('gipity domain verify <guid>')} ${muted('Verify DNS and activate (requires project)')}`);
|
|
131
|
+
console.log(`${brand('gipity domain remove <guid>')} ${muted('Remove a custom domain')}`);
|
|
129
132
|
}
|
|
130
133
|
}));
|
|
131
134
|
//# sourceMappingURL=domain.js.map
|
package/dist/commands/email.js
CHANGED
|
@@ -41,15 +41,11 @@ export const emailCommand = new Command('email')
|
|
|
41
41
|
const recap = res.data.to.join(', ')
|
|
42
42
|
+ (res.data.cc.length ? `, cc: ${res.data.cc.join(', ')}` : '')
|
|
43
43
|
+ (res.data.bcc.length ? `, bcc: ${res.data.bcc.length}` : '');
|
|
44
|
-
console.log('');
|
|
45
44
|
console.log(success(`Email sent to ${recap}: ${res.data.subject}`));
|
|
46
|
-
console.log('');
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
47
|
catch (err) {
|
|
50
|
-
console.log('');
|
|
51
48
|
console.error(clrError(`Email failed: ${err.message}`));
|
|
52
|
-
console.log('');
|
|
53
49
|
process.exit(1);
|
|
54
50
|
}
|
|
55
51
|
});
|
package/dist/commands/fn.js
CHANGED
|
@@ -14,7 +14,7 @@ fnCommand
|
|
|
14
14
|
const res = await get(`/projects/${config.projectGuid}/functions`);
|
|
15
15
|
printList(res.data, opts, 'No functions defined.', f => {
|
|
16
16
|
const line = `${bold(f.name)} ${muted(`v${f.version}`)} ${muted(f.auth_level)} ${muted(`timeout=${f.timeout_ms}ms`)}`;
|
|
17
|
-
return f.description ? `${line}\n
|
|
17
|
+
return f.description ? `${line}\n${muted(f.description)}` : line;
|
|
18
18
|
});
|
|
19
19
|
}));
|
|
20
20
|
fnCommand
|
|
@@ -30,7 +30,7 @@ fnCommand
|
|
|
30
30
|
const ts = new Date(log.created_at).toLocaleString();
|
|
31
31
|
const statusColor = log.status === 'success' ? success : log.status === 'error' ? clrError : muted;
|
|
32
32
|
const line = `${statusColor(log.status)} ${dur} ${muted(log.trigger_type || 'http')} ${muted(ts)}`;
|
|
33
|
-
return log.error_message ? `${line}\n
|
|
33
|
+
return log.error_message ? `${line}\n${clrError(`error: ${log.error_message}`)}` : line;
|
|
34
34
|
});
|
|
35
35
|
}));
|
|
36
36
|
fnCommand
|
|
@@ -2,15 +2,18 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { post } from '../api.js';
|
|
3
3
|
import { resolveProjectContext } from '../config.js';
|
|
4
4
|
import { writeFileSync } from 'fs';
|
|
5
|
+
import { resolve as resolvePath } from 'path';
|
|
5
6
|
import { error as clrError, success, muted, info } from '../colors.js';
|
|
6
7
|
import { IMAGE_MODELS_DOC, IMAGE_GEMINI_ASPECT_RATIOS, IMAGE_GEMINI_SIZES, VIDEO_MODELS_DOC, TTS_PROVIDER_DESCRIPTIONS } from '../provider-docs.js';
|
|
7
|
-
/** Download a URL and save to a local file
|
|
8
|
+
/** Download a URL and save to a local file. Returns the absolute path written,
|
|
9
|
+
* so callers can report exactly where the file landed. */
|
|
8
10
|
async function downloadFile(url, filename) {
|
|
9
11
|
const res = await fetch(url);
|
|
10
12
|
if (!res.ok)
|
|
11
13
|
throw new Error(`Download failed: ${res.status}`);
|
|
12
14
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
13
15
|
writeFileSync(filename, buffer);
|
|
16
|
+
return resolvePath(filename);
|
|
14
17
|
}
|
|
15
18
|
// ── IMAGE ──────────────────────────────────────────────────────────────
|
|
16
19
|
const imageCommand = new Command('image')
|
|
@@ -25,16 +28,16 @@ Gemini-specific options:
|
|
|
25
28
|
Examples:
|
|
26
29
|
gipity generate image "a cat wearing a top hat"
|
|
27
30
|
gipity generate image "landscape sunset" --provider gemini --aspect-ratio 16:9 --image-size 2K
|
|
28
|
-
gipity generate image "product photo" --provider openai --model gpt-image-
|
|
31
|
+
gipity generate image "product photo" --provider openai --model gpt-image-2 --size 1536x1024 --quality high
|
|
29
32
|
gipity generate image "abstract art" --provider bfl --model flux-2-pro -o art.png`)
|
|
30
33
|
.argument('<prompt>', 'Text description of the image to generate')
|
|
31
34
|
.option('--provider <provider>', 'Image provider: openai, bfl, or gemini (default: bfl)')
|
|
32
35
|
.option('--model <model>', 'Model ID (see provider list above)')
|
|
33
36
|
.option('--size <size>', 'Dimensions as WxH, e.g. "1024x1024" (OpenAI/BFL)')
|
|
34
|
-
.option('--quality <quality>', 'Quality: low|medium|high|auto (gpt-image-
|
|
37
|
+
.option('--quality <quality>', 'Quality: low|medium|high|auto (gpt-image-2)')
|
|
35
38
|
.option('--aspect-ratio <ratio>', 'Aspect ratio (Gemini only): 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 4:5, 5:4, 21:9')
|
|
36
39
|
.option('--image-size <size>', 'Output resolution (Gemini only): 512, 1K, 2K, 4K')
|
|
37
|
-
.option('-o, --output <file>', 'Output
|
|
40
|
+
.option('-o, --output <file>', 'Output path (default ./generated.png). For an image your app ships, write it into the source tree so it deploys, e.g. -o src/assets/images/hero.png; the cwd default is fine for one-off generation.')
|
|
38
41
|
.option('--json', 'Output as JSON')
|
|
39
42
|
.action(async (prompt, opts) => {
|
|
40
43
|
try {
|
|
@@ -50,14 +53,14 @@ Examples:
|
|
|
50
53
|
});
|
|
51
54
|
const ext = result.content_type.includes('png') ? 'png' : 'jpg';
|
|
52
55
|
const filename = opts.output || `generated.${ext}`;
|
|
53
|
-
await downloadFile(result.url, filename);
|
|
56
|
+
const savedPath = await downloadFile(result.url, filename);
|
|
54
57
|
if (opts.json) {
|
|
55
|
-
console.log(JSON.stringify({ ...result, saved:
|
|
58
|
+
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
56
59
|
}
|
|
57
60
|
else {
|
|
58
61
|
const sizeKb = Math.round(result.size_bytes / 1024);
|
|
59
62
|
console.log(`${muted(`Generated with ${result.provider}/${result.model} (${sizeKb}KB)`)}`);
|
|
60
|
-
console.log(success(`Saved to ${
|
|
63
|
+
console.log(success(`Saved to ${savedPath}`));
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
catch (err) {
|
|
@@ -88,12 +91,13 @@ Examples:
|
|
|
88
91
|
.option('--model <model>', 'Veo model: veo-3.1-generate-preview (quality), veo-3.1-fast-generate-preview (speed), veo-3.1-lite-generate-preview (budget)')
|
|
89
92
|
.option('--aspect <ratio>', 'Aspect ratio: 16:9 (landscape), 9:16 (portrait), 1:1 (square)')
|
|
90
93
|
.option('--resolution <res>', 'Video resolution: 720p, 1080p, 4k')
|
|
91
|
-
.option('-o, --output <file>', 'Output
|
|
94
|
+
.option('-o, --output <file>', 'Output path (default ./generated.mp4). For a clip your app ships, write it into the source tree so it deploys, e.g. -o src/assets/video/clip.mp4; the cwd default is fine for one-off generation.')
|
|
92
95
|
.option('--json', 'Output as JSON')
|
|
93
96
|
.action(async (prompt, opts) => {
|
|
94
97
|
try {
|
|
95
98
|
const { config } = await resolveProjectContext();
|
|
96
|
-
|
|
99
|
+
if (!opts.json)
|
|
100
|
+
console.log(info('Generating video (this may take 30-120 seconds)...')); // keep --json stdout pure JSON
|
|
97
101
|
const result = await post(`/projects/${config.projectGuid}/generate/video`, {
|
|
98
102
|
prompt,
|
|
99
103
|
model: opts.model,
|
|
@@ -101,14 +105,14 @@ Examples:
|
|
|
101
105
|
resolution: opts.resolution,
|
|
102
106
|
});
|
|
103
107
|
const filename = opts.output || 'generated.mp4';
|
|
104
|
-
await downloadFile(result.url, filename);
|
|
108
|
+
const savedPath = await downloadFile(result.url, filename);
|
|
105
109
|
if (opts.json) {
|
|
106
|
-
console.log(JSON.stringify({ ...result, saved:
|
|
110
|
+
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
107
111
|
}
|
|
108
112
|
else {
|
|
109
113
|
const sizeKb = Math.round(result.size_bytes / 1024);
|
|
110
114
|
console.log(`${muted(`Generated with ${result.provider}/${result.model} (${sizeKb}KB)`)}`);
|
|
111
|
-
console.log(success(`Saved to ${
|
|
115
|
+
console.log(success(`Saved to ${savedPath}`));
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
catch (err) {
|
|
@@ -137,7 +141,7 @@ Examples:
|
|
|
137
141
|
.option('--voice <voice>', 'Voice ID or name (provider-specific)')
|
|
138
142
|
.option('--language <code>', 'BCP-47 language code, e.g. ja-JP, es-ES (Gemini only, 60+ languages)')
|
|
139
143
|
.option('--speakers <json>', 'Multi-speaker config as JSON array (Gemini only, up to 2 speakers)')
|
|
140
|
-
.option('-o, --output <file>', 'Output
|
|
144
|
+
.option('-o, --output <file>', 'Output path (default ./speech.mp3). For audio your app ships, write it into the source tree so it deploys, e.g. -o src/assets/sounds/intro.mp3; the cwd default is fine for one-off generation.')
|
|
141
145
|
.option('--json', 'Output as JSON')
|
|
142
146
|
.action(async (text, opts) => {
|
|
143
147
|
try {
|
|
@@ -160,14 +164,14 @@ Examples:
|
|
|
160
164
|
speakers,
|
|
161
165
|
});
|
|
162
166
|
const filename = opts.output || 'speech.mp3';
|
|
163
|
-
await downloadFile(result.url, filename);
|
|
167
|
+
const savedPath = await downloadFile(result.url, filename);
|
|
164
168
|
if (opts.json) {
|
|
165
|
-
console.log(JSON.stringify({ ...result, saved:
|
|
169
|
+
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
166
170
|
}
|
|
167
171
|
else {
|
|
168
172
|
const sizeKb = Math.round(result.size_bytes / 1024);
|
|
169
173
|
console.log(`${muted(`Generated with ${result.provider} (${sizeKb}KB)`)}`);
|
|
170
|
-
console.log(success(`Saved to ${
|
|
174
|
+
console.log(success(`Saved to ${savedPath}`));
|
|
171
175
|
}
|
|
172
176
|
}
|
|
173
177
|
catch (err) {
|
|
@@ -175,10 +179,61 @@ Examples:
|
|
|
175
179
|
process.exit(1);
|
|
176
180
|
}
|
|
177
181
|
});
|
|
182
|
+
// ── MUSIC ──────────────────────────────────────────────────────────────
|
|
183
|
+
const musicCommand = new Command('music')
|
|
184
|
+
.description(`Generate music from a text prompt using AI.
|
|
185
|
+
|
|
186
|
+
The model is chosen from the platform's music catalog. Omit --model to use the
|
|
187
|
+
default; pass --model <id> only if you want a specific one (see the catalog with
|
|
188
|
+
\`gipity service call music/models --get\`).
|
|
189
|
+
|
|
190
|
+
Tips:
|
|
191
|
+
- Describe genre, mood, instruments, and tempo (e.g. "upbeat lo-fi hip hop with mellow piano")
|
|
192
|
+
- Music is instrumental by default; pass --vocals to allow singing
|
|
193
|
+
- Longer clips cost more; the max length depends on the model
|
|
194
|
+
|
|
195
|
+
Examples:
|
|
196
|
+
gipity generate music "chill lo-fi beat for studying"
|
|
197
|
+
gipity generate music "epic orchestral battle theme" --duration 60 -o src/assets/audio/theme.mp3
|
|
198
|
+
gipity generate music "indie pop chorus" --vocals --duration 20`)
|
|
199
|
+
.argument('<prompt>', 'Text description of the music to generate')
|
|
200
|
+
.option('--duration <seconds>', 'Clip length in seconds (default 30; max depends on the model)', (v) => parseInt(v, 10))
|
|
201
|
+
.option('--model <model>', 'Music model id (default: platform default)')
|
|
202
|
+
.option('--vocals', 'Allow vocals (default: instrumental only)')
|
|
203
|
+
.option('-o, --output <file>', 'Output path (default ./music.mp3). For audio your app ships, write it into the source tree so it deploys, e.g. -o src/assets/audio/theme.mp3; the cwd default is fine for one-off generation.')
|
|
204
|
+
.option('--json', 'Output as JSON')
|
|
205
|
+
.action(async (prompt, opts) => {
|
|
206
|
+
try {
|
|
207
|
+
const { config } = await resolveProjectContext();
|
|
208
|
+
if (!opts.json)
|
|
209
|
+
console.log(info('Generating music...')); // keep --json stdout pure JSON
|
|
210
|
+
const result = await post(`/projects/${config.projectGuid}/generate/music`, {
|
|
211
|
+
prompt,
|
|
212
|
+
duration_seconds: opts.duration,
|
|
213
|
+
model: opts.model,
|
|
214
|
+
instrumental: !opts.vocals,
|
|
215
|
+
});
|
|
216
|
+
const filename = opts.output || 'music.mp3';
|
|
217
|
+
const savedPath = await downloadFile(result.url, filename);
|
|
218
|
+
if (opts.json) {
|
|
219
|
+
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const sizeKb = Math.round(result.size_bytes / 1024);
|
|
223
|
+
console.log(`${muted(`Generated with ${result.model} (${sizeKb}KB)`)}`);
|
|
224
|
+
console.log(success(`Saved to ${savedPath}`));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
console.error(clrError(`Music generation failed: ${err.message}`));
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
178
232
|
// ── PARENT COMMAND ─────────────────────────────────────────────────────
|
|
179
233
|
export const generateCommand = new Command('generate')
|
|
180
|
-
.description('Generate images, video, or
|
|
234
|
+
.description('Generate images, video, speech, or music')
|
|
181
235
|
.addCommand(imageCommand)
|
|
182
236
|
.addCommand(videoCommand)
|
|
183
|
-
.addCommand(speechCommand)
|
|
237
|
+
.addCommand(speechCommand)
|
|
238
|
+
.addCommand(musicCommand);
|
|
184
239
|
//# sourceMappingURL=generate.js.map
|
package/dist/commands/job.js
CHANGED
|
@@ -17,7 +17,7 @@ jobCommand
|
|
|
17
17
|
if (j.on_complete)
|
|
18
18
|
tags.push(muted(`→ ${j.on_complete}`));
|
|
19
19
|
const line = `${bold(j.name)} ${tags.join(' ')}`;
|
|
20
|
-
return j.description ? `${line}\n
|
|
20
|
+
return j.description ? `${line}\n${muted(j.description)}` : line;
|
|
21
21
|
});
|
|
22
22
|
}));
|
|
23
23
|
jobCommand
|
|
@@ -57,13 +57,13 @@ jobCommand
|
|
|
57
57
|
const statusColor = r.status === 'success' ? success : r.status === 'failed' ? clrError : muted;
|
|
58
58
|
console.log(`${statusColor(r.status)} ${muted(r.guid)}`);
|
|
59
59
|
if (r.progress_pct != null)
|
|
60
|
-
console.log(`
|
|
60
|
+
console.log(`progress: ${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ''}`);
|
|
61
61
|
if (r.duration_ms != null)
|
|
62
|
-
console.log(`
|
|
62
|
+
console.log(`duration: ${r.duration_ms}ms`);
|
|
63
63
|
if (r.error)
|
|
64
|
-
console.log(
|
|
64
|
+
console.log(`${clrError('error:')} ${r.error}`);
|
|
65
65
|
if (r.output)
|
|
66
|
-
console.log(`
|
|
66
|
+
console.log(`output: ${JSON.stringify(r.output)}`);
|
|
67
67
|
}));
|
|
68
68
|
jobCommand
|
|
69
69
|
.command('runs <name>')
|
|
@@ -78,7 +78,7 @@ jobCommand
|
|
|
78
78
|
const dur = r.duration_ms != null ? `${r.duration_ms}ms` : '?';
|
|
79
79
|
const ts = new Date(r.created_at).toLocaleString();
|
|
80
80
|
const line = `${statusColor(r.status)} ${dur} ${muted(r.trigger_type)} ${muted(r.guid)} ${muted(ts)}`;
|
|
81
|
-
return r.error ? `${line}\n
|
|
81
|
+
return r.error ? `${line}\n${clrError(`error: ${r.error}`)}` : line;
|
|
82
82
|
});
|
|
83
83
|
}));
|
|
84
84
|
jobCommand
|
|
@@ -12,16 +12,16 @@ function formatLocation(r) {
|
|
|
12
12
|
const lines = [];
|
|
13
13
|
const place = [r.city, r.region, r.country].filter(Boolean).join(', ');
|
|
14
14
|
if (place)
|
|
15
|
-
lines.push(
|
|
15
|
+
lines.push(place);
|
|
16
16
|
if (r.lat != null && r.lon != null)
|
|
17
|
-
lines.push(`
|
|
17
|
+
lines.push(`Coordinates: ${r.lat}, ${r.lon}`);
|
|
18
18
|
if (r.ip)
|
|
19
|
-
lines.push(`
|
|
19
|
+
lines.push(`IP: ${r.ip}`);
|
|
20
20
|
if (r.timezone)
|
|
21
|
-
lines.push(`
|
|
21
|
+
lines.push(`Timezone: ${r.timezone}`);
|
|
22
22
|
if (r.accuracy != null)
|
|
23
|
-
lines.push(`
|
|
24
|
-
lines.push(`
|
|
23
|
+
lines.push(`Accuracy: ${Math.round(r.accuracy)}m`);
|
|
24
|
+
lines.push(`Source: ${r.source}`);
|
|
25
25
|
return lines.join('\n');
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
@@ -71,7 +71,7 @@ export const locationCommand = new Command('location')
|
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
if (!res.data) {
|
|
74
|
-
console.log('
|
|
74
|
+
console.log('No location data.');
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
console.log(formatLocation(res.data));
|
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,7 +1,38 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { post } from '../api.js';
|
|
3
|
-
import { brand, bold, muted } from '../colors.js';
|
|
2
|
+
import { post, get, ApiError } from '../api.js';
|
|
3
|
+
import { brand, bold, muted, warning } from '../colors.js';
|
|
4
4
|
import { run } from '../helpers/index.js';
|
|
5
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6
|
+
/** Poll the async eval job until it finishes. Eval runs server-side as a
|
|
7
|
+
* short-lived job (so a long --wait can't trip the gateway idle timeout);
|
|
8
|
+
* we submit, then poll the result out of the job store. */
|
|
9
|
+
async function pollEvalResult(evalJobId, waitMs) {
|
|
10
|
+
// Generous client budget: the server work is bounded by --wait plus browser
|
|
11
|
+
// open/settle overhead; give it that plus headroom before giving up.
|
|
12
|
+
const deadline = Date.now() + waitMs + 60_000;
|
|
13
|
+
let missCount = 0;
|
|
14
|
+
while (Date.now() < deadline) {
|
|
15
|
+
let rec;
|
|
16
|
+
try {
|
|
17
|
+
rec = (await get(`/tools/browser/eval/${evalJobId}`)).data;
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
// A 404 right after submit can happen if the record hasn't landed yet;
|
|
21
|
+
// tolerate a few, then treat a persistent 404 as the job being gone.
|
|
22
|
+
if (err instanceof ApiError && err.statusCode === 404 && missCount++ < 3) {
|
|
23
|
+
await sleep(500);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
28
|
+
if (rec.status === 'done')
|
|
29
|
+
return rec;
|
|
30
|
+
if (rec.status === 'error')
|
|
31
|
+
throw new ApiError(rec.httpStatus, rec.code, rec.reason);
|
|
32
|
+
await sleep(1000);
|
|
33
|
+
}
|
|
34
|
+
throw new ApiError(504, 'EVAL_TIMEOUT', 'Eval did not finish in time; narrow the expression or lower --wait');
|
|
35
|
+
}
|
|
5
36
|
// The long-tail escape hatch alongside `page inspect`'s fixed bundle: when the
|
|
6
37
|
// curated metrics don't cover what you need (computed styles, element rects,
|
|
7
38
|
// visibility, z-index stacks), eval an expression in page context and get the
|
|
@@ -11,21 +42,31 @@ export const pageEvalCommand = new Command('eval')
|
|
|
11
42
|
.argument('<url>', 'URL to load')
|
|
12
43
|
.argument('<expr>', 'JavaScript expression to evaluate in page context (result is JSON-serialized)')
|
|
13
44
|
.option('--wait <ms>', 'Sleep this many ms after DOMContentLoaded before evaluating (lets late async work settle)', '500')
|
|
45
|
+
.option('--wait-for <selector>', 'Wait until this CSS selector appears before evaluating (deterministic; replaces --wait)')
|
|
46
|
+
.option('--wait-timeout <ms>', 'Max ms to wait for --wait-for before giving up', '5000')
|
|
14
47
|
.option('--json', 'Output as JSON')
|
|
15
48
|
.action((url, expr, opts) => run('Page eval', async () => {
|
|
16
49
|
const parsedWait = parseInt(opts.wait, 10);
|
|
17
50
|
const waitMs = Number.isFinite(parsedWait) && parsedWait >= 0 ? parsedWait : 500;
|
|
18
|
-
const
|
|
19
|
-
const
|
|
51
|
+
const parsedTimeout = parseInt(opts.waitTimeout, 10);
|
|
52
|
+
const waitForTimeoutMs = Number.isFinite(parsedTimeout) && parsedTimeout >= 0 ? parsedTimeout : 5000;
|
|
53
|
+
const kickoff = await post('/tools/browser/eval', {
|
|
54
|
+
url, expr, waitMs,
|
|
55
|
+
waitForSelector: opts.waitFor || undefined,
|
|
56
|
+
waitForTimeoutMs: opts.waitFor ? waitForTimeoutMs : undefined,
|
|
57
|
+
});
|
|
58
|
+
const d = await pollEvalResult(kickoff.data.evalJobId, waitMs);
|
|
20
59
|
if (opts.json) {
|
|
21
60
|
console.log(JSON.stringify(d));
|
|
22
61
|
return;
|
|
23
62
|
}
|
|
24
|
-
console.log(
|
|
25
|
-
|
|
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}`);
|
|
26
68
|
console.log(`\n${d.result || muted('(empty result)')}`);
|
|
27
69
|
if (d.truncated)
|
|
28
70
|
console.log(muted('\n(result truncated to fit context - narrow the expression for the full value)'));
|
|
29
|
-
console.log('');
|
|
30
71
|
}));
|
|
31
72
|
//# sourceMappingURL=page-eval.js.map
|