gipity 1.0.408 → 1.0.409
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/commands/add.js +1 -1
- package/dist/commands/approval.js +1 -1
- package/dist/commands/email.js +2 -1
- package/dist/commands/gmail.js +2 -1
- package/dist/commands/init.js +2 -1
- package/dist/commands/job.js +2 -1
- package/dist/commands/login.js +1 -0
- package/dist/commands/notify.js +2 -1
- package/dist/commands/page.js +1 -1
- package/dist/commands/payments.js +2 -1
- package/dist/commands/plan.js +1 -1
- package/dist/commands/remove.js +1 -1
- package/dist/commands/secrets.js +2 -1
- package/dist/commands/service.js +2 -1
- package/dist/commands/sync.js +2 -1
- package/dist/commands/text.js +2 -1
- package/dist/commands/token.js +2 -1
- package/dist/config.js +4 -4
- package/dist/helpers/output.js +42 -13
- package/dist/updater/bootstrap.js +1 -1
- package/package.json +1 -1
- package/dist/coding-guidelines.js +0 -52
- package/dist/commands/browser.js +0 -84
- package/dist/commands/checkpoint.js +0 -68
- package/dist/commands/hook-capture.js +0 -215
- package/dist/commands/scaffold.js +0 -58
- package/dist/commands/skills.js +0 -45
- package/dist/commands/start-cc.js +0 -313
- package/dist/gip3dw-guide.js +0 -18
- package/dist/platform-overview.js +0 -52
- package/dist/relay/transcripts.js +0 -119
package/dist/commands/add.js
CHANGED
|
@@ -144,7 +144,7 @@ function buildLocalPayload(rootDir) {
|
|
|
144
144
|
return { name: path.basename(rootDir), files };
|
|
145
145
|
}
|
|
146
146
|
export const addCommand = new Command('add')
|
|
147
|
-
.description('Add a template
|
|
147
|
+
.description('Add a template or kit')
|
|
148
148
|
.argument('[name]', 'Template/kit key, OR a local directory path (./, ~/, or /abs). Omit for help; use --list for just the catalog.')
|
|
149
149
|
.option('--title <title>', 'App title - templates only (defaults to project name)')
|
|
150
150
|
.option('--description <desc>', 'App description for meta tags - templates only')
|
|
@@ -21,7 +21,7 @@ function keyToIndex(key) {
|
|
|
21
21
|
return code >= 0 && code < 26 ? code : -1;
|
|
22
22
|
}
|
|
23
23
|
export const approvalCommand = new Command('approval')
|
|
24
|
-
.description('
|
|
24
|
+
.description('Manage approvals');
|
|
25
25
|
approvalCommand
|
|
26
26
|
.command('list')
|
|
27
27
|
.description('List pending approvals')
|
package/dist/commands/email.js
CHANGED
|
@@ -7,7 +7,8 @@ function collect(value, prev) {
|
|
|
7
7
|
return [...prev, value];
|
|
8
8
|
}
|
|
9
9
|
export const emailCommand = new Command('email')
|
|
10
|
-
.description('Send email
|
|
10
|
+
.description('Send email')
|
|
11
|
+
.addHelpText('after', '\nSends from gipity@gipity.ai. Omit --to to self-send.')
|
|
11
12
|
.requiredOption('--subject <subject>', 'Email subject')
|
|
12
13
|
.requiredOption('--body <body>', 'Email body (plain text)')
|
|
13
14
|
.option('--to <email>', 'Recipient (repeatable; omit for self-send)', collect, [])
|
package/dist/commands/gmail.js
CHANGED
|
@@ -3,7 +3,8 @@ import { get, post } from '../api.js';
|
|
|
3
3
|
import { run, printList, printResult } from '../helpers/index.js';
|
|
4
4
|
import { muted } from '../colors.js';
|
|
5
5
|
export const gmailCommand = new Command('gmail')
|
|
6
|
-
.description('
|
|
6
|
+
.description('Use your Gmail')
|
|
7
|
+
.addHelpText('after', '\nSend, reply, search, or read via your own Gmail (separate from `gipity email`).');
|
|
7
8
|
gmailCommand
|
|
8
9
|
.command('search <query...>')
|
|
9
10
|
.description('Search your Gmail (standard Gmail search syntax)')
|
package/dist/commands/init.js
CHANGED
|
@@ -22,7 +22,8 @@ function resolveTools(forFlag) {
|
|
|
22
22
|
return SUPPORTED_TOOLS.filter(t => requested.includes(t.key) || (!t.optIn && requested.includes('all')));
|
|
23
23
|
}
|
|
24
24
|
export const initCommand = new Command('init')
|
|
25
|
-
.description('Link this directory to a
|
|
25
|
+
.description('Link this directory to a project')
|
|
26
|
+
.addHelpText('after', '\nWrites CLAUDE.md/AGENTS.md primer files so your AI coding tool understands Gipity.')
|
|
26
27
|
.argument('[name]', 'Project name/slug (defaults to current directory name)')
|
|
27
28
|
.option('--agent <guid>', 'Agent GUID to use')
|
|
28
29
|
.option('--for <tools>', `Which AI tool primer files to write (comma-separated). Default: all except aider (opt-in - it also writes .aider.conf.yml). Choices: ${TOOL_KEYS.join(', ')}, all`)
|
package/dist/commands/job.js
CHANGED
|
@@ -4,7 +4,8 @@ import { requireConfig } from '../config.js';
|
|
|
4
4
|
import { error as clrError, bold, muted, success, warning as warn } from '../colors.js';
|
|
5
5
|
import { run, printList } from '../helpers/index.js';
|
|
6
6
|
export const jobCommand = new Command('job')
|
|
7
|
-
.description('
|
|
7
|
+
.description('Run long jobs (CPU/GPU)')
|
|
8
|
+
.addHelpText('after', '\nCPU sandbox or GPU (gpu-small=L4, gpu-medium=A10G, gpu-large=A100, gpu-huge=H100). Declared in the gipity.yaml jobs: phase; submit and poll via the subcommands below.');
|
|
8
9
|
jobCommand
|
|
9
10
|
.command('list')
|
|
10
11
|
.description('List jobs')
|
package/dist/commands/login.js
CHANGED
package/dist/commands/notify.js
CHANGED
|
@@ -4,7 +4,8 @@ import { resolveProjectContext } from '../config.js';
|
|
|
4
4
|
import { bold, muted, success, warning } from '../colors.js';
|
|
5
5
|
import { run } from '../helpers/index.js';
|
|
6
6
|
export const notifyCommand = new Command('notify')
|
|
7
|
-
.description('
|
|
7
|
+
.description('Web push notifications')
|
|
8
|
+
.addHelpText('after', '\nGipity Notify: send a test notification or inspect subscriptions.');
|
|
8
9
|
notifyCommand
|
|
9
10
|
.command('test')
|
|
10
11
|
.description('Send a test push notification to yourself, a user, or everyone')
|
package/dist/commands/page.js
CHANGED
|
@@ -10,7 +10,7 @@ import { pageFetchCommand } from './page-fetch.js';
|
|
|
10
10
|
// top-level surface lean and makes the siblings discoverable via `page --help`.
|
|
11
11
|
// `inspect` is the rendered DOM (browser); `fetch` is the raw asset (plain HTTP).
|
|
12
12
|
export const pageCommand = new Command('page')
|
|
13
|
-
.description('Inspect
|
|
13
|
+
.description('Inspect web pages')
|
|
14
14
|
.addCommand(pageInspectCommand)
|
|
15
15
|
.addCommand(pageEvalCommand)
|
|
16
16
|
.addCommand(pageScreenshotCommand)
|
|
@@ -19,7 +19,8 @@ function renderStatus(status) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
export const paymentsCommand = new Command('payments')
|
|
22
|
-
.description('
|
|
22
|
+
.description('Charge your users via Stripe')
|
|
23
|
+
.addHelpText('after', '\nAccept one-time purchases and subscriptions. Set up with `gipity payments connect`.');
|
|
23
24
|
paymentsCommand
|
|
24
25
|
.command('connect')
|
|
25
26
|
.description('Start (or resume) Stripe onboarding for this app — prints a link to finish in your browser')
|
package/dist/commands/plan.js
CHANGED
|
@@ -31,7 +31,7 @@ function renderLimits(limits, indent = '') {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
export const planCommand = new Command('plan')
|
|
34
|
-
.description('Show your
|
|
34
|
+
.description('Show your plan')
|
|
35
35
|
.option('--json', 'Output as JSON')
|
|
36
36
|
.action((opts) => run('Plan', async () => {
|
|
37
37
|
const [limitsRes, plansRes] = await Promise.all([
|
package/dist/commands/remove.js
CHANGED
|
@@ -7,7 +7,7 @@ import { run } from '../helpers/index.js';
|
|
|
7
7
|
import { confirm } from '../utils.js';
|
|
8
8
|
import { createProgressReporter, withSpinner } from '../progress.js';
|
|
9
9
|
export const removeCommand = new Command('remove')
|
|
10
|
-
.description('Remove
|
|
10
|
+
.description('Remove a kit')
|
|
11
11
|
.argument('<kit>', 'Kit key/directory under src/packages/ to remove')
|
|
12
12
|
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
13
13
|
.option('--json', 'Output as JSON')
|
package/dist/commands/secrets.js
CHANGED
|
@@ -12,7 +12,8 @@ async function scopeQuery(opts) {
|
|
|
12
12
|
return `scope=project&app_guid=${config.projectGuid}`;
|
|
13
13
|
}
|
|
14
14
|
export const secretsCommand = new Command('secrets')
|
|
15
|
-
.description('
|
|
15
|
+
.description('Manage app secrets')
|
|
16
|
+
.addHelpText('after', '\nEncrypted API keys and tokens for your app, read in functions via secrets.get("NAME").');
|
|
16
17
|
secretsCommand
|
|
17
18
|
.command('list')
|
|
18
19
|
.alias('ls')
|
package/dist/commands/service.js
CHANGED
|
@@ -20,7 +20,8 @@ const SERVICES = [
|
|
|
20
20
|
{ name: 'location/geocode', method: 'POST', desc: 'Reverse geocode ({ lat, lon })' },
|
|
21
21
|
];
|
|
22
22
|
export const serviceCommand = new Command('service')
|
|
23
|
-
.description('Call
|
|
23
|
+
.description('Call an app service')
|
|
24
|
+
.addHelpText('after', '\nCall a Gipity app service: LLM, image, music, TTS, and more.');
|
|
24
25
|
serviceCommand
|
|
25
26
|
.command('list')
|
|
26
27
|
.description('List callable app services')
|
package/dist/commands/sync.js
CHANGED
|
@@ -3,7 +3,8 @@ import { sync } from '../sync.js';
|
|
|
3
3
|
import { createProgressReporter } from '../progress.js';
|
|
4
4
|
import { error as clrError, muted } from '../colors.js';
|
|
5
5
|
export const syncCommand = new Command('sync')
|
|
6
|
-
.description('Sync files
|
|
6
|
+
.description('Sync files')
|
|
7
|
+
.addHelpText('after', '\nAdd a .gipityignore at the project root to exclude paths (gitignore-style).')
|
|
7
8
|
.option('--plan', 'Print the plan without applying any changes')
|
|
8
9
|
.option('--force', 'Bypass the bulk-deletion guard')
|
|
9
10
|
.option('--prune', 'Remove files that exist on Gipity but not locally (applies the bulk deletes the guard defers)')
|
package/dist/commands/text.js
CHANGED
|
@@ -140,7 +140,8 @@ const analyzeCommand = new Command('analyze')
|
|
|
140
140
|
// Parent namespace. One capability today (analyze); namespaced for a lean
|
|
141
141
|
// top-level surface and room for future text operations.
|
|
142
142
|
export const textCommand = new Command('text')
|
|
143
|
-
.description(
|
|
143
|
+
.description('Analyze text')
|
|
144
|
+
.addHelpText('after', `\nDeterministic char/word counts, frequency, and occurrences (${brand('text analyze')}). Local, no network.`)
|
|
144
145
|
.addCommand(analyzeCommand);
|
|
145
146
|
textCommand.action(() => {
|
|
146
147
|
textCommand.help();
|
package/dist/commands/token.js
CHANGED
|
@@ -3,7 +3,8 @@ import { get, post, del } from '../api.js';
|
|
|
3
3
|
import { bold, muted, success, warning } from '../colors.js';
|
|
4
4
|
import { run, printList } from '../helpers/index.js';
|
|
5
5
|
export const tokenCommand = new Command('token')
|
|
6
|
-
.description('Manage
|
|
6
|
+
.description('Manage API tokens')
|
|
7
|
+
.addHelpText('after', '\nLong-lived agent API tokens (gip_at_*) for headless agents and CI.');
|
|
7
8
|
const fmtDate = (d) => (d ? new Date(d).toLocaleDateString() : 'never');
|
|
8
9
|
tokenCommand
|
|
9
10
|
.command('create')
|
package/dist/config.js
CHANGED
|
@@ -134,8 +134,8 @@ export async function resolveProjectContext(opts) {
|
|
|
134
134
|
}
|
|
135
135
|
const agents = await get(`/projects/${match.short_guid}/agents`);
|
|
136
136
|
const accountSlug = await getAccountSlug();
|
|
137
|
-
console.error(dim(`→
|
|
138
|
-
console.error(
|
|
137
|
+
console.error(dim(`→ (project: ${match.slug} · no file sync)`));
|
|
138
|
+
console.error('');
|
|
139
139
|
return {
|
|
140
140
|
config: {
|
|
141
141
|
projectGuid: match.short_guid,
|
|
@@ -163,8 +163,8 @@ export async function resolveProjectContext(opts) {
|
|
|
163
163
|
console.error('Could not resolve your Home project - please contact support.');
|
|
164
164
|
process.exit(1);
|
|
165
165
|
}
|
|
166
|
-
console.error(dim(`→
|
|
167
|
-
console.error(
|
|
166
|
+
console.error(dim(`→ (project: ${res.data.projectName} · no file sync)`));
|
|
167
|
+
console.error('');
|
|
168
168
|
return {
|
|
169
169
|
config: {
|
|
170
170
|
projectGuid: res.data.projectGuid,
|
package/dist/helpers/output.js
CHANGED
|
@@ -3,16 +3,38 @@
|
|
|
3
3
|
* can close a frame even when a command ends via process.exit() and so skips
|
|
4
4
|
* the postAction hook. */
|
|
5
5
|
let frameOpen = false;
|
|
6
|
+
/** Count of newlines currently at the tail of everything written to stdout. Kept
|
|
7
|
+
* live by wrapping stdout.write so the frame can close to EXACTLY one trailing
|
|
8
|
+
* blank line instead of blindly appending one - the blind append doubled the
|
|
9
|
+
* blank whenever a command's own output already ended in a newline (e.g. an
|
|
10
|
+
* agent reply printed with a trailing '\n'). */
|
|
11
|
+
let trailingNewlines = 0;
|
|
12
|
+
function trackTrailing(chunk) {
|
|
13
|
+
const s = typeof chunk === 'string'
|
|
14
|
+
? chunk
|
|
15
|
+
: (chunk && typeof chunk.toString === 'function' ? String(chunk) : '');
|
|
16
|
+
if (!s.length)
|
|
17
|
+
return;
|
|
18
|
+
const run = /\n*$/.exec(s)?.[0].length ?? 0;
|
|
19
|
+
// A chunk that is all newlines extends the current run; otherwise it resets it
|
|
20
|
+
// to just this chunk's own trailing run.
|
|
21
|
+
trailingNewlines = run === s.length ? trailingNewlines + run : run;
|
|
22
|
+
}
|
|
6
23
|
/**
|
|
7
24
|
* Bracket every human (non-JSON) command's stdout with exactly one leading and
|
|
8
25
|
* one trailing blank line, so output is visually separated from the shell
|
|
9
26
|
* prompt and consistent across commands. JSON output is left untouched so it
|
|
10
27
|
* stays pipe/parse-clean.
|
|
11
28
|
*
|
|
29
|
+
* The trailing blank is added by TOP-UP, not blind append: one blank line means
|
|
30
|
+
* the stream ends in two newlines (the content's own line terminator plus one
|
|
31
|
+
* empty line), so we only write the newlines still missing. That keeps the
|
|
32
|
+
* boundary at exactly one blank whether the command ended its output with no
|
|
33
|
+
* newline, one, or an accidental extra.
|
|
34
|
+
*
|
|
12
35
|
* Centralizing the frame here is what lets individual commands stop printing
|
|
13
|
-
* their own leading/trailing blank lines
|
|
14
|
-
*
|
|
15
|
-
* hooks for the actual subcommand action too.
|
|
36
|
+
* their own leading/trailing blank lines. Call once on the root program;
|
|
37
|
+
* Commander runs these lifecycle hooks for the actual subcommand action too.
|
|
16
38
|
*/
|
|
17
39
|
export function installOutputFrame(program) {
|
|
18
40
|
// Only decorate a real terminal. When stdout is piped or redirected (a relay
|
|
@@ -20,6 +42,21 @@ export function installOutputFrame(program) {
|
|
|
20
42
|
// colors.ts uses for ANSI. This keeps headless `claude -p` stdout empty.
|
|
21
43
|
if (!process.stdout.isTTY)
|
|
22
44
|
return;
|
|
45
|
+
// Wrap stdout.write to keep `trailingNewlines` current for every byte the
|
|
46
|
+
// command emits (console.log, spinner, colors all route through here).
|
|
47
|
+
const origWrite = process.stdout.write.bind(process.stdout);
|
|
48
|
+
process.stdout.write = (chunk, ...rest) => {
|
|
49
|
+
trackTrailing(chunk);
|
|
50
|
+
return origWrite(chunk, ...rest);
|
|
51
|
+
};
|
|
52
|
+
const closeFrame = () => {
|
|
53
|
+
if (!frameOpen)
|
|
54
|
+
return;
|
|
55
|
+
frameOpen = false;
|
|
56
|
+
const need = Math.max(0, 2 - trailingNewlines);
|
|
57
|
+
if (need > 0)
|
|
58
|
+
origWrite('\n'.repeat(need));
|
|
59
|
+
};
|
|
23
60
|
program.hook('preAction', (_thisCommand, actionCommand) => {
|
|
24
61
|
const opts = actionCommand.optsWithGlobals
|
|
25
62
|
? actionCommand.optsWithGlobals()
|
|
@@ -29,18 +66,10 @@ export function installOutputFrame(program) {
|
|
|
29
66
|
process.stdout.write('\n');
|
|
30
67
|
frameOpen = true;
|
|
31
68
|
});
|
|
32
|
-
program.hook('postAction',
|
|
33
|
-
if (frameOpen) {
|
|
34
|
-
process.stdout.write('\n');
|
|
35
|
-
frameOpen = false;
|
|
36
|
-
}
|
|
37
|
-
});
|
|
69
|
+
program.hook('postAction', closeFrame);
|
|
38
70
|
// Commands that finish via process.exit() never reach postAction; close the
|
|
39
71
|
// frame from the exit handler instead (sync write, fires on every exit path).
|
|
40
|
-
process.on('exit',
|
|
41
|
-
if (frameOpen)
|
|
42
|
-
process.stdout.write('\n');
|
|
43
|
-
});
|
|
72
|
+
process.on('exit', closeFrame);
|
|
44
73
|
}
|
|
45
74
|
/**
|
|
46
75
|
* Print data as JSON or formatted text.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gipity",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.409",
|
|
4
4
|
"description": "The full-stack platform tuned for AI agents. Database, storage, auth, functions, deploy, and drop-in kits - all agent-tuned. Pair with Claude Code or use standalone.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gipity": "dist/updater/shim.js",
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared coding guidelines for Gipity-hosted projects.
|
|
3
|
-
* Single source of truth used by:
|
|
4
|
-
* - server/src/services/skills/web-app-basics.ts (web CLI agent)
|
|
5
|
-
* - tools/gipity-cli/src/setup.ts (CLAUDE.md template for Claude Code)
|
|
6
|
-
*
|
|
7
|
-
* The CLI build copies this file before compiling (see justfile cli-build).
|
|
8
|
-
*/
|
|
9
|
-
export const CODING_GUIDELINES = `## File Structure
|
|
10
|
-
- **Use src/ convention**: All app files live under \`src/\` — \`src/index.html\`, \`src/css/styles.css\`, \`src/js/main.js\`, \`src/images/\`
|
|
11
|
-
- **Separate files**: Split into \`index.html\`, \`styles.css\`, and \`app.js\` (or \`main.js\`). Never inline large blocks of CSS or JS in HTML.
|
|
12
|
-
- If the app grows, organize into folders: \`src/css/\`, \`src/js/\`, \`src/assets/\`, \`src/sounds/\`, \`src/images/\`, etc.
|
|
13
|
-
- **Use subfolders — don't flatten**: Reference assets from their folders (e.g. \`sounds/click.ogg\`, \`images/logo.png\`). Never copy files to the root just for convenience — deployed apps serve the full directory tree.
|
|
14
|
-
- Keep \`index.html\` clean — it should be structure/markup, not behavior or styling
|
|
15
|
-
|
|
16
|
-
## HTML
|
|
17
|
-
- Use semantic elements: \`<header>\`, \`<nav>\`, \`<main>\`, \`<section>\`, \`<footer>\`, \`<article>\`
|
|
18
|
-
- Always include \`<meta name="viewport" content="width=device-width, initial-scale=1.0">\`
|
|
19
|
-
- Add a proper \`<title>\` and favicon link
|
|
20
|
-
- Unless the user specifies a different CSS framework, include Water.css for automatic styling: \`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">\`
|
|
21
|
-
- Water.css styles semantic HTML automatically (buttons, tables, forms, nav, cards) — no classes needed. It supports dark/light themes automatically. Add custom CSS on top for app-specific tweaks.
|
|
22
|
-
|
|
23
|
-
## CSS
|
|
24
|
-
- When using Water.css, it handles base styling, resets, and typography — don't duplicate what it provides
|
|
25
|
-
- Water.css exposes CSS variables for theming — override them in \`:root\` for custom colors/fonts
|
|
26
|
-
- Use CSS custom properties (variables) for app-specific colors, spacing, and fonts
|
|
27
|
-
- Add smooth transitions on interactive elements (buttons, links, hover states)
|
|
28
|
-
|
|
29
|
-
## JavaScript
|
|
30
|
-
- Use \`const\`/\`let\`, arrow functions, template literals, and modern ES6+ syntax
|
|
31
|
-
- Wait for DOM: wrap in \`DOMContentLoaded\` or place script at end of body
|
|
32
|
-
- Keep functions small and focused
|
|
33
|
-
- Use \`addEventListener\` — never inline \`onclick\` attributes in HTML
|
|
34
|
-
|
|
35
|
-
## Code Quality
|
|
36
|
-
- **Keep files under ~400 lines** unless the content genuinely requires it (e.g. a long data table, template string, or config object). When logic grows beyond that, split into focused modules (e.g. \`utils.js\`, \`api.js\`, \`ui.js\`).
|
|
37
|
-
- **Don't duplicate code.** If the same logic appears twice, extract it into a shared function. Before writing a new helper, check if one already exists or could be extended.
|
|
38
|
-
- **One responsibility per file.** A file that handles both UI rendering and API calls should be split.
|
|
39
|
-
- **Name things clearly.** Functions, variables, and files should describe what they do — no \`temp\`, \`data2\`, \`stuff.js\`.
|
|
40
|
-
- **Prefer simple, readable code** over clever code that hides bugs. Flat over nested — use early returns, avoid deep nesting.
|
|
41
|
-
- **Centralize configuration.** App settings, API URLs, feature flags, and magic numbers should live in a dedicated config file (e.g. \`config.js\` or \`constants.js\`), not scattered across the codebase.
|
|
42
|
-
- **Write utility functions** for repeated operations (formatting, validation, API calls). Keep them in a \`utils.js\` or \`helpers.js\` file. Small, pure functions are easy to test and reuse.
|
|
43
|
-
|
|
44
|
-
## Testing
|
|
45
|
-
- **Write tests for new functions** — especially utility/helper functions. Cover the happy path and edge cases (empty input, null, boundary values).
|
|
46
|
-
- **Don't mock unless absolutely required.** Tests should exercise real code paths. Only mock external paid services (APIs that cost money per call).
|
|
47
|
-
- **E2E tests should hit real infrastructure** (real API, real DB) — just clean up test data when done.
|
|
48
|
-
- **Test file naming**: \`*.test.js\` for unit tests, \`*.e2e.test.js\` for end-to-end tests.
|
|
49
|
-
|
|
50
|
-
## Deployment
|
|
51
|
-
- **src/ detection**: If a \`src/\` directory exists, only \`src/\` is deployed. Otherwise the full project root is deployed.`;
|
|
52
|
-
//# sourceMappingURL=coding-guidelines.js.map
|
package/dist/commands/browser.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { post } from '../api.js';
|
|
3
|
-
import { resolveProjectContext } from '../config.js';
|
|
4
|
-
import { formatSize } from '../utils.js';
|
|
5
|
-
import { brand, bold, error as clrError, warning, muted, info } from '../colors.js';
|
|
6
|
-
import { run } from '../helpers/index.js';
|
|
7
|
-
function shortUrl(url) {
|
|
8
|
-
try {
|
|
9
|
-
const u = new URL(url);
|
|
10
|
-
return u.pathname + u.search;
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
return url.length > 60 ? url.slice(-60) : url;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
export const browserCommand = new Command('browser')
|
|
17
|
-
.description('Inspect a URL: console errors, performance, failed resources')
|
|
18
|
-
.argument('<url>', 'URL to inspect')
|
|
19
|
-
.option('--wait <ms>', 'Wait before capture in ms', '3000')
|
|
20
|
-
.option('--json', 'Output as JSON')
|
|
21
|
-
.action((url, opts) => run('Browser inspect', async () => {
|
|
22
|
-
const { config } = await resolveProjectContext();
|
|
23
|
-
const waitMs = parseInt(opts.wait, 10) || 3000;
|
|
24
|
-
const res = await post(`/projects/${config.projectGuid}/browser/inspect`, { url, waitMs });
|
|
25
|
-
const b = res.data;
|
|
26
|
-
if (opts.json) {
|
|
27
|
-
console.log(JSON.stringify(b));
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const timing = b.timing || { ttfb: 0, domReady: 0, load: 0 };
|
|
31
|
-
// ── Page Info ──
|
|
32
|
-
console.log(`\n${brand('Inspecting')} ${bold(b.url || url)}`);
|
|
33
|
-
console.log(` ${muted('Title:')} ${b.title || '(none)'}`);
|
|
34
|
-
console.log(` ${muted('Elements:')} ${b.elementCount || 0}`);
|
|
35
|
-
console.log(` ${muted('Page weight:')} ${info(formatSize(b.totalBytes || 0))}`);
|
|
36
|
-
// ── Timing ──
|
|
37
|
-
console.log(`\n ${bold('Timing:')}`);
|
|
38
|
-
console.log(` ${muted('TTFB:')} ${timing.ttfb}ms`);
|
|
39
|
-
console.log(` ${muted('DOM ready:')} ${timing.domReady}ms`);
|
|
40
|
-
console.log(` ${muted('Load:')} ${timing.load}ms`);
|
|
41
|
-
if (b.lcp) {
|
|
42
|
-
console.log(` LCP: ${b.lcp.time}ms (${b.lcp.element}${b.lcp.url ? ' ' + shortUrl(b.lcp.url) : ''})`);
|
|
43
|
-
}
|
|
44
|
-
// ── Console ──
|
|
45
|
-
if (b.console?.length > 0) {
|
|
46
|
-
console.log(`\n ${bold('Console')} ${muted(`(${b.console.length})`)}:`);
|
|
47
|
-
for (const line of b.console) {
|
|
48
|
-
console.log(` ${warning(line)}`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
console.log(`\n ${bold('Console:')} ${muted('(clean)')}`);
|
|
53
|
-
}
|
|
54
|
-
// ── Failed Resources ──
|
|
55
|
-
if (b.failedResources?.length > 0) {
|
|
56
|
-
console.log(`\n ${clrError(`Failed resources (${b.failedResources.length}):`)}`);
|
|
57
|
-
for (const r of b.failedResources) {
|
|
58
|
-
console.log(` ${clrError(r)}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// ── Render Blocking ──
|
|
62
|
-
if (b.renderBlocking?.length > 0) {
|
|
63
|
-
console.log(`\n ${warning(`Render-blocking (${b.renderBlocking.length}):`)}`);
|
|
64
|
-
for (const r of b.renderBlocking) {
|
|
65
|
-
console.log(` ${shortUrl(r)}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// ── Large Resources ──
|
|
69
|
-
if (b.largeResources?.length > 0) {
|
|
70
|
-
console.log(`\n ${warning(`Large resources >100KB (${b.largeResources.length}):`)}`);
|
|
71
|
-
for (const r of b.largeResources) {
|
|
72
|
-
console.log(` ${info(formatSize(r.size).padEnd(10))} ${muted(r.type.padEnd(8))} ${shortUrl(r.url)}`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
// ── Oversized Images ──
|
|
76
|
-
if (b.oversizedImages?.length > 0) {
|
|
77
|
-
console.log(`\n ${warning(`Oversized images (${b.oversizedImages.length}):`)}`);
|
|
78
|
-
for (const img of b.oversizedImages) {
|
|
79
|
-
console.log(` ${img.natural} served, ${img.displayed} displayed — ${shortUrl(img.src)}`);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
console.log('');
|
|
83
|
-
}));
|
|
84
|
-
//# sourceMappingURL=browser.js.map
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { get, post } from '../api.js';
|
|
3
|
-
import { requireConfig } from '../config.js';
|
|
4
|
-
import { syncDown } from '../sync.js';
|
|
5
|
-
import { formatAge } from '../utils.js';
|
|
6
|
-
import { error as clrError, muted, success } from '../colors.js';
|
|
7
|
-
export const checkpointCommand = new Command('checkpoint')
|
|
8
|
-
.description('Manage file checkpoints (snapshots for undo/restore)');
|
|
9
|
-
checkpointCommand
|
|
10
|
-
.command('list')
|
|
11
|
-
.description('List checkpoints')
|
|
12
|
-
.option('--limit <n>', 'Max results', '20')
|
|
13
|
-
.option('--json', 'Output as JSON')
|
|
14
|
-
.action(async (opts) => {
|
|
15
|
-
try {
|
|
16
|
-
const config = requireConfig();
|
|
17
|
-
const limit = parseInt(opts.limit, 10) || 20;
|
|
18
|
-
const res = await get(`/projects/${config.projectGuid}/checkpoints?limit=${limit}`);
|
|
19
|
-
if (opts.json) {
|
|
20
|
-
console.log(JSON.stringify(res.data));
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
if (res.data.length === 0) {
|
|
24
|
-
console.log('No checkpoints.');
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
for (const cp of res.data) {
|
|
28
|
-
const age = formatAge(cp.created_at);
|
|
29
|
-
const branch = cp.branched ? ' (branched)' : '';
|
|
30
|
-
console.log(` ${muted(cp.guid)} ${cp.label || muted('(auto)')} ${cp.fileCount} files ${muted(age)}${branch}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
catch (err) {
|
|
36
|
-
console.error(clrError(`List failed: ${err.message}`));
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
checkpointCommand
|
|
41
|
-
.command('restore <guid>')
|
|
42
|
-
.description('Restore files to a checkpoint (creates a backup checkpoint first)')
|
|
43
|
-
.option('--json', 'Output as JSON')
|
|
44
|
-
.action(async (guid, opts) => {
|
|
45
|
-
try {
|
|
46
|
-
const config = requireConfig();
|
|
47
|
-
const res = await post(`/projects/${config.projectGuid}/checkpoints/restore`, {
|
|
48
|
-
checkpoint_guid: guid,
|
|
49
|
-
});
|
|
50
|
-
// Sync down restored files (confirm deletions — restore may remove files)
|
|
51
|
-
const syncResult = await syncDown({ confirmDeletions: true });
|
|
52
|
-
if (opts.json) {
|
|
53
|
-
console.log(JSON.stringify({ ...res.data, synced: syncResult.pulled }));
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
console.log(success(`Restored to ${res.data.restoredTo}`));
|
|
57
|
-
console.log(`Backup created: ${res.data.backupCheckpoint}`);
|
|
58
|
-
if (syncResult.pulled > 0) {
|
|
59
|
-
console.log(`Pulled ${syncResult.pulled} files to local.`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch (err) {
|
|
64
|
-
console.error(clrError(`Restore failed: ${err.message}`));
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
//# sourceMappingURL=checkpoint.js.map
|