gipity 1.1.4 → 1.1.6
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/adopt-cwd.js +1 -1
- package/dist/agents/agy.js +52 -0
- package/dist/agents/index.js +2 -0
- package/dist/api.js +19 -3
- package/dist/banner.js +3 -5
- package/dist/capture/sources/agy.js +88 -0
- package/dist/client-context.js +9 -0
- package/dist/commands/add.js +1 -1
- package/dist/commands/brand.js +125 -0
- package/dist/commands/build.js +13 -12
- package/dist/commands/chat.js +1 -1
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/fn.js +2 -8
- package/dist/commands/generate.js +5 -5
- package/dist/commands/gmail.js +1 -1
- package/dist/commands/init.js +2 -2
- package/dist/commands/job.js +10 -5
- package/dist/commands/key.js +91 -0
- package/dist/commands/load.js +2 -2
- package/dist/commands/login.js +7 -2
- package/dist/commands/page-eval.js +47 -8
- package/dist/commands/page-fetch.js +14 -10
- package/dist/commands/page-inspect.js +2 -2
- package/dist/commands/page-screenshot.js +5 -5
- package/dist/commands/page-test.js +1 -1
- package/dist/commands/records.js +57 -19
- package/dist/commands/remove.js +1 -1
- package/dist/commands/sandbox.js +1 -1
- package/dist/commands/save.js +1 -1
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/service.js +1 -1
- package/dist/commands/setup.js +4 -6
- package/dist/commands/status.js +41 -12
- package/dist/commands/storage.js +2 -2
- package/dist/commands/sync.js +15 -0
- package/dist/commands/test.js +1 -1
- package/dist/commands/token.js +6 -1
- package/dist/commands/uninstall.js +14 -1
- package/dist/commands/upload.js +1 -1
- package/dist/commands/workflow.js +1 -1
- package/dist/hooks/capture-runner.js +20 -8
- package/dist/index.js +1046 -518
- package/dist/knowledge.js +4 -1
- package/dist/login-flow.js +44 -2
- package/dist/project-setup.js +2 -0
- package/dist/relay/diagnostics.js +4 -2
- package/dist/relay/onboarding.js +26 -44
- package/dist/setup.js +198 -16
- package/dist/sync.js +6 -6
- package/dist/template-vars.js +74 -0
- package/dist/utils.js +60 -3
- package/package.json +2 -2
package/dist/adopt-cwd.js
CHANGED
|
@@ -167,7 +167,7 @@ export function formatCwdLabel(cwd) {
|
|
|
167
167
|
const tail = norm.slice(home.length + 1).split(sep);
|
|
168
168
|
if (tail.length <= 2)
|
|
169
169
|
return '~/' + tail.join('/');
|
|
170
|
-
return '
|
|
170
|
+
return '~/.../' + tail.slice(-2).join('/');
|
|
171
171
|
}
|
|
172
172
|
// Outside home: show parent/this for non-root paths.
|
|
173
173
|
const parts = norm.split(sep).filter(Boolean);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** LLM_MODELS canonical id -> agy's own spaced --model display string, for the
|
|
2
|
+
* subset of the Gemini catalog agy actually offers. Anything not listed here
|
|
3
|
+
* passes through translateModel() unchanged - either it's already agy's own
|
|
4
|
+
* format (a human typed it directly), or it's a Gemini id agy doesn't have an
|
|
5
|
+
* equivalent for, in which case agy's own `--model` validation is the right
|
|
6
|
+
* place to reject it, not a guess made here. */
|
|
7
|
+
const MODEL_ID_TO_AGY_DISPLAY = {
|
|
8
|
+
'gemini-3.5-flash': 'Gemini 3.5 Flash (Medium)',
|
|
9
|
+
'gemini-3.1-pro-preview': 'Gemini 3.1 Pro (High)',
|
|
10
|
+
};
|
|
11
|
+
function translateModel(model) {
|
|
12
|
+
return MODEL_ID_TO_AGY_DISPLAY[model] ?? model;
|
|
13
|
+
}
|
|
14
|
+
function projectArgs(resume) {
|
|
15
|
+
return resume ? ['--conversation', resume] : ['--new-project'];
|
|
16
|
+
}
|
|
17
|
+
export const agyAdapter = {
|
|
18
|
+
key: 'agy',
|
|
19
|
+
source: 'agy',
|
|
20
|
+
displayName: 'Antigravity',
|
|
21
|
+
providerName: 'Google',
|
|
22
|
+
binary: 'agy',
|
|
23
|
+
buildInteractiveArgs({ resume, model }) {
|
|
24
|
+
const args = [...projectArgs(resume)];
|
|
25
|
+
if (model)
|
|
26
|
+
args.push('--model', translateModel(model));
|
|
27
|
+
return args;
|
|
28
|
+
},
|
|
29
|
+
buildHeadlessArgs({ message, resume, model, bypassApprovals }) {
|
|
30
|
+
const args = ['-p', message, ...projectArgs(resume)];
|
|
31
|
+
if (model)
|
|
32
|
+
args.push('--model', translateModel(model));
|
|
33
|
+
if (bypassApprovals)
|
|
34
|
+
args.push('--dangerously-skip-permissions');
|
|
35
|
+
return args;
|
|
36
|
+
},
|
|
37
|
+
sessionIdFromStreamEvent() {
|
|
38
|
+
// agy has no --json/--output-format flag (confirmed: `agy --help` lists
|
|
39
|
+
// none, and `-p` stdout is plain prose) - there is no stream to read a
|
|
40
|
+
// session id off. Capture relies entirely on hooks (see class comment).
|
|
41
|
+
return null;
|
|
42
|
+
},
|
|
43
|
+
hooksSupportedOnPlatform(platform) {
|
|
44
|
+
return platform !== 'win32'; // agy's hooks.json wiring is POSIX-only, like Codex's
|
|
45
|
+
},
|
|
46
|
+
// No stream-json ingest mapper for agy: hook capture carries the full
|
|
47
|
+
// conversationId on every event (confirmed live, including headless -p),
|
|
48
|
+
// so the daemon doesn't need to parse agy's stdout at all.
|
|
49
|
+
daemonStreamCapture: false,
|
|
50
|
+
installHint: 'see https://antigravity.google (Google account sign-in required)',
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=agy.js.map
|
package/dist/agents/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { claudeCodeAdapter } from './claude-code.js';
|
|
2
2
|
import { codexAdapter } from './codex.js';
|
|
3
3
|
import { grokAdapter } from './grok.js';
|
|
4
|
+
import { agyAdapter } from './agy.js';
|
|
4
5
|
/** Picker/help order: Claude first (the default), then the others. */
|
|
5
6
|
export const AGENT_ADAPTERS = [
|
|
6
7
|
claudeCodeAdapter,
|
|
7
8
|
codexAdapter,
|
|
8
9
|
grokAdapter,
|
|
10
|
+
agyAdapter,
|
|
9
11
|
];
|
|
10
12
|
export const AGENT_KEYS = AGENT_ADAPTERS.map(a => a.key);
|
|
11
13
|
export function getAdapter(key) {
|
package/dist/api.js
CHANGED
|
@@ -366,12 +366,12 @@ export async function getAccountSlug() {
|
|
|
366
366
|
/** Unauthenticated request (for login/verify, and anonymous-visitor calls like
|
|
367
367
|
* `fn call --anon`). extraHeaders lets callers attach non-auth headers such as
|
|
368
368
|
* X-App-Token; no Authorization header is ever sent. */
|
|
369
|
-
export async function
|
|
369
|
+
export async function publicRequest(method, path, body, extraHeaders) {
|
|
370
370
|
const url = `${baseUrl()}${path}`;
|
|
371
371
|
const res = await fetch(url, {
|
|
372
|
-
method
|
|
372
|
+
method,
|
|
373
373
|
headers: { ...clientHeaders(), 'Content-Type': 'application/json', ...extraHeaders },
|
|
374
|
-
body: JSON.stringify(body),
|
|
374
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
375
375
|
});
|
|
376
376
|
if (!res.ok) {
|
|
377
377
|
const json = await res.json().catch(() => ({ error: { code: 'UNKNOWN', message: res.statusText } }));
|
|
@@ -380,4 +380,20 @@ export async function publicPost(path, body, extraHeaders) {
|
|
|
380
380
|
}
|
|
381
381
|
return res.json();
|
|
382
382
|
}
|
|
383
|
+
export function publicPost(path, body, extraHeaders) {
|
|
384
|
+
return publicRequest('POST', path, body, extraHeaders);
|
|
385
|
+
}
|
|
386
|
+
/** Mint the short-lived public app token the browser SDK uses, so a CLI call can
|
|
387
|
+
* exercise the exact path a signed-out visitor hits. Best-effort: fully public
|
|
388
|
+
* surfaces work with no token at all, and an auth-gated one should 401 with the
|
|
389
|
+
* server's real reason rather than a CLI-invented one. */
|
|
390
|
+
export async function mintAppToken(projectGuid) {
|
|
391
|
+
try {
|
|
392
|
+
const minted = await publicPost('/api/token', { app: projectGuid });
|
|
393
|
+
return { 'X-App-Token': minted.data.token };
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
383
399
|
//# sourceMappingURL=api.js.map
|
package/dist/banner.js
CHANGED
|
@@ -115,15 +115,13 @@ function padR(s, width) {
|
|
|
115
115
|
const gap = width - visLen(s);
|
|
116
116
|
return gap > 0 ? s + ' '.repeat(gap) : s;
|
|
117
117
|
}
|
|
118
|
-
/** Truncate from the left with a leading
|
|
118
|
+
/** Truncate from the left with a leading "..." if longer than maxW (plain text). */
|
|
119
119
|
function leadingEllipsify(s, maxW) {
|
|
120
120
|
if (s.length <= maxW)
|
|
121
121
|
return s;
|
|
122
|
-
if (maxW <=
|
|
122
|
+
if (maxW <= 4)
|
|
123
123
|
return s.slice(-maxW);
|
|
124
|
-
|
|
125
|
-
return '…'.padStart(maxW);
|
|
126
|
-
return '…' + s.slice(-(maxW - 1));
|
|
124
|
+
return '...' + s.slice(-(maxW - 3));
|
|
127
125
|
}
|
|
128
126
|
function center(s, width) {
|
|
129
127
|
const gap = width - visLen(s);
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const USER_REQUEST_RE = /<USER_REQUEST>\s*([\s\S]*?)\s*<\/USER_REQUEST>/;
|
|
2
|
+
function extractUserRequest(content) {
|
|
3
|
+
const m = USER_REQUEST_RE.exec(content);
|
|
4
|
+
return m ? m[1].trim() : content.trim();
|
|
5
|
+
}
|
|
6
|
+
function positional(conversationId, idx) {
|
|
7
|
+
return `${conversationId}#${idx}`;
|
|
8
|
+
}
|
|
9
|
+
function watermarkIndex(afterUuid, conversationId) {
|
|
10
|
+
if (!afterUuid || !conversationId)
|
|
11
|
+
return null;
|
|
12
|
+
const prefix = `${conversationId}#`;
|
|
13
|
+
if (!afterUuid.startsWith(prefix))
|
|
14
|
+
return null;
|
|
15
|
+
const idx = parseInt(afterUuid.slice(prefix.length), 10);
|
|
16
|
+
return Number.isInteger(idx) && idx >= 0 ? idx : null;
|
|
17
|
+
}
|
|
18
|
+
function lineToEntries(parsed, conversationId, idx) {
|
|
19
|
+
const ts = typeof parsed?.created_at === 'string' ? parsed.created_at : undefined;
|
|
20
|
+
const type = parsed?.type;
|
|
21
|
+
const content = parsed?.content;
|
|
22
|
+
const uuid = positional(conversationId, idx);
|
|
23
|
+
if (type === 'USER_INPUT' && typeof content === 'string' && content) {
|
|
24
|
+
const prompt = extractUserRequest(content);
|
|
25
|
+
if (!prompt)
|
|
26
|
+
return [];
|
|
27
|
+
return [{ kind: 'prompt', prompt, source_uuid: uuid, ...(ts ? { ts } : {}) }];
|
|
28
|
+
}
|
|
29
|
+
if (type === 'PLANNER_RESPONSE') {
|
|
30
|
+
if (typeof content !== 'string' || !content)
|
|
31
|
+
return []; // thinking-only turn, nothing user-visible
|
|
32
|
+
return [{
|
|
33
|
+
kind: 'assistant',
|
|
34
|
+
text: content,
|
|
35
|
+
blocks: [{ type: 'text', text: content }],
|
|
36
|
+
source_uuid: uuid,
|
|
37
|
+
...(ts ? { ts } : {}),
|
|
38
|
+
}];
|
|
39
|
+
}
|
|
40
|
+
if (type === 'CODE_ACTION') {
|
|
41
|
+
if (typeof content !== 'string' || !content)
|
|
42
|
+
return [];
|
|
43
|
+
return [{ kind: 'system', content, source_uuid: uuid, ...(ts ? { ts } : {}) }];
|
|
44
|
+
}
|
|
45
|
+
return []; // CONVERSATION_HISTORY, EPHEMERAL_MESSAGE, CHECKPOINT, SYSTEM_MESSAGE, unknown
|
|
46
|
+
}
|
|
47
|
+
/** Same contract as the other source parsers: entries after `afterUuid`, the
|
|
48
|
+
* new watermark, and whether the old watermark was still valid for this
|
|
49
|
+
* file. `conversationId` comes from the hook payload (capture-runner passes
|
|
50
|
+
* it as `hook.session_id`), not from the file content - agy's transcript
|
|
51
|
+
* lines carry no conversation/session id field of their own. */
|
|
52
|
+
export function parseTranscript(content, afterUuid, opts = {}) {
|
|
53
|
+
const conversationId = opts.conversationId ?? '';
|
|
54
|
+
const lines = content.split('\n');
|
|
55
|
+
// A watermark past EOF means the file was replaced by a shorter one under
|
|
56
|
+
// the same conversation id (mirrors the Codex parser's resume-rotation
|
|
57
|
+
// guard) - treat as not-found so the caller replays from the top instead of
|
|
58
|
+
// wedging forever.
|
|
59
|
+
let startAfter = watermarkIndex(afterUuid, conversationId);
|
|
60
|
+
if (startAfter !== null && startAfter >= lines.length)
|
|
61
|
+
startAfter = null;
|
|
62
|
+
const foundWatermark = afterUuid === null || startAfter !== null;
|
|
63
|
+
const out = [];
|
|
64
|
+
let lastIdx = startAfter;
|
|
65
|
+
for (let i = 0; i < lines.length; i++) {
|
|
66
|
+
const line = lines[i].trim();
|
|
67
|
+
if (!line)
|
|
68
|
+
continue;
|
|
69
|
+
if (startAfter !== null && i <= startAfter)
|
|
70
|
+
continue;
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(line);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
for (const e of lineToEntries(parsed, conversationId, i))
|
|
79
|
+
out.push(e);
|
|
80
|
+
lastIdx = i;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
entries: out,
|
|
84
|
+
lastUuid: lastIdx === null ? afterUuid : positional(conversationId, lastIdx),
|
|
85
|
+
foundWatermark,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=agy.js.map
|
package/dist/client-context.js
CHANGED
|
@@ -29,6 +29,15 @@ export function detectHarness() {
|
|
|
29
29
|
return { harness: 'codex' };
|
|
30
30
|
if (hasEnvPrefix('GROK_'))
|
|
31
31
|
return { harness: 'grok', harnessSession: process.env.GROK_SESSION_ID };
|
|
32
|
+
// Antigravity (agy) injects ANTIGRAVITY_CONVERSATION_ID into every hook/tool
|
|
33
|
+
// subprocess it spawns (confirmed live). Check this exact var, not an
|
|
34
|
+
// 'ANTIGRAVITY_' prefix scan - the Antigravity IDE's own integrated terminal
|
|
35
|
+
// ambiently sets other ANTIGRAVITY_*-prefixed vars (e.g. ANTIGRAVITY_CLI_ALIAS)
|
|
36
|
+
// in every shell it spawns, which would false-positive for any user with the
|
|
37
|
+
// IDE open, even outside a real agy session/hook.
|
|
38
|
+
if (process.env.ANTIGRAVITY_CONVERSATION_ID) {
|
|
39
|
+
return { harness: 'agy', harnessSession: process.env.ANTIGRAVITY_CONVERSATION_ID };
|
|
40
|
+
}
|
|
32
41
|
if (process.env.CURSOR_TRACE_ID || hasEnvPrefix('CURSOR_') || (process.env.TERM_PROGRAM ?? '').toLowerCase().includes('cursor')) {
|
|
33
42
|
return { harness: 'cursor' };
|
|
34
43
|
}
|
package/dist/commands/add.js
CHANGED
|
@@ -207,7 +207,7 @@ export const addCommand = new Command('add')
|
|
|
207
207
|
const doAdd = () => post(`/projects/${config.projectGuid}/add`, body);
|
|
208
208
|
const res = opts.json
|
|
209
209
|
? await doAdd()
|
|
210
|
-
: await withSpinner('Installing
|
|
210
|
+
: await withSpinner('Installing...', doAdd, { done: null });
|
|
211
211
|
// Pull the created/installed files down to local.
|
|
212
212
|
const syncResult = await sync({ interactive: false, progress: opts.json ? undefined : createProgressReporter() });
|
|
213
213
|
const data = res.data;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gipity brand` - view or change the app's generated visual identity:
|
|
3
|
+
* favicons, the iOS Home Screen icon, the PWA manifest, and the 1200x630
|
|
4
|
+
* social share card (og-image) that X/iMessage/Slack show when the app's
|
|
5
|
+
* URL is shared.
|
|
6
|
+
*
|
|
7
|
+
* The default identity is deterministic (accent color from the project guid,
|
|
8
|
+
* glyph from the title's first character, small Gipity egg badge). `brand set`
|
|
9
|
+
* overrides pieces of it - most usefully the glyph (an emoji reads far more
|
|
10
|
+
* "this is my app" than a letter) and the accent color - then regenerates
|
|
11
|
+
* every asset server-side in one shot. Deploy afterwards to publish.
|
|
12
|
+
*/
|
|
13
|
+
import { Command } from 'commander';
|
|
14
|
+
import { get, post } from '../api.js';
|
|
15
|
+
import { resolveProjectContext } from '../config.js';
|
|
16
|
+
import { bold, muted, success } from '../colors.js';
|
|
17
|
+
import { run } from '../helpers/index.js';
|
|
18
|
+
function printBrand(d) {
|
|
19
|
+
console.log(`${bold('Glyph:')} ${d.resolved.glyph}${d.resolved.isEmoji ? muted(' (emoji)') : ''}`);
|
|
20
|
+
console.log(`${bold('Accent:')} ${d.resolved.accent}${d.branding.primary_color ? '' : muted(' (derived from project)')}`);
|
|
21
|
+
if (d.branding.app_name)
|
|
22
|
+
console.log(`${bold('Name:')} ${d.branding.app_name}`);
|
|
23
|
+
if (d.branding.tagline)
|
|
24
|
+
console.log(`${bold('Tagline:')} ${d.branding.tagline}`);
|
|
25
|
+
}
|
|
26
|
+
const DEPLOY_HINT = 'Deploy to publish the new assets: gipity deploy dev';
|
|
27
|
+
export const brandCommand = new Command('brand')
|
|
28
|
+
.description("The app's generated icons + share card (favicons, iOS icon, og-image)")
|
|
29
|
+
.addHelpText('after', `
|
|
30
|
+
Examples:
|
|
31
|
+
gipity brand Show the current brand
|
|
32
|
+
gipity brand set --emoji 🦍 Emoji icon + share card, regenerate all assets
|
|
33
|
+
gipity brand set --glyph R --color "#3b82f6" Letter glyph on a blue accent
|
|
34
|
+
gipity brand set --tagline "Bananas at 60fps" Subtitle on the share card
|
|
35
|
+
gipity brand apply Re-render assets from the stored brand
|
|
36
|
+
gipity brand apply --fix-head Migrate an older app: assets + splice the
|
|
37
|
+
share/SEO tags into src/index.html
|
|
38
|
+
|
|
39
|
+
Assets are generated server-side into src/images/ (+ src/manifest.webmanifest)
|
|
40
|
+
and are versioned like any other file - rollback restores previous ones.`);
|
|
41
|
+
brandCommand
|
|
42
|
+
.command('show', { isDefault: true })
|
|
43
|
+
.description('Show the stored brand and the resolved render spec')
|
|
44
|
+
.option('--project <guid-or-slug>', 'Target a specific project instead of cwd / Home')
|
|
45
|
+
.option('--json', 'Output raw JSON')
|
|
46
|
+
.action((opts) => run('Brand', async () => {
|
|
47
|
+
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
48
|
+
const res = await get(`/projects/${config.projectGuid}/brand`);
|
|
49
|
+
if (opts.json) {
|
|
50
|
+
console.log(JSON.stringify(res.data));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
printBrand(res.data);
|
|
54
|
+
console.log(muted(`\nAssets: ${(res.data.assets ?? []).join(', ')}`));
|
|
55
|
+
console.log(muted(`Change it: gipity brand set --emoji 🎨 | --glyph A | --color "#RRGGBB" | --tagline "..."`));
|
|
56
|
+
}));
|
|
57
|
+
brandCommand
|
|
58
|
+
.command('set')
|
|
59
|
+
.description('Change brand fields and regenerate all assets')
|
|
60
|
+
.option('--emoji <emoji>', 'Emoji glyph for the icon + share card (e.g. 🦍)')
|
|
61
|
+
.option('--glyph <char>', 'Letter/digit glyph (alias of --emoji, any single character)')
|
|
62
|
+
.option('--color <hex>', 'Accent color, #RRGGBB')
|
|
63
|
+
.option('--name <name>', 'Display name override (og/share title stays the page title)')
|
|
64
|
+
.option('--tagline <text>', 'One-line subtitle on the share card (max 120 chars)')
|
|
65
|
+
.option('--fix-head', 'Also splice the share/SEO head block into src/index.html (for apps installed before templates shipped it)')
|
|
66
|
+
.option('--no-regenerate', 'Store the fields without re-rendering assets')
|
|
67
|
+
.option('--project <guid-or-slug>', 'Target a specific project instead of cwd / Home')
|
|
68
|
+
.option('--json', 'Output raw JSON')
|
|
69
|
+
.action((opts) => run('Brand', async () => {
|
|
70
|
+
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
71
|
+
const body = {};
|
|
72
|
+
const glyph = opts.emoji ?? opts.glyph;
|
|
73
|
+
if (glyph)
|
|
74
|
+
body.icon_glyph = glyph;
|
|
75
|
+
if (opts.color)
|
|
76
|
+
body.primary_color = opts.color;
|
|
77
|
+
if (opts.name)
|
|
78
|
+
body.app_name = opts.name;
|
|
79
|
+
if (opts.tagline)
|
|
80
|
+
body.tagline = opts.tagline;
|
|
81
|
+
if (opts.regenerate === false)
|
|
82
|
+
body.regenerate = false;
|
|
83
|
+
if (opts.fixHead)
|
|
84
|
+
body.fix_head = true;
|
|
85
|
+
if (!glyph && !opts.color && !opts.name && !opts.tagline) {
|
|
86
|
+
console.error('Nothing to set - pass --emoji, --glyph, --color, --name, or --tagline. (gipity brand set --help; use gipity brand apply to just re-render)');
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const res = await post(`/projects/${config.projectGuid}/brand`, body);
|
|
91
|
+
if (opts.json) {
|
|
92
|
+
console.log(JSON.stringify(res.data));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
printBrand(res.data);
|
|
96
|
+
reportFiles(res.data.files ?? []);
|
|
97
|
+
}));
|
|
98
|
+
brandCommand
|
|
99
|
+
.command('apply')
|
|
100
|
+
.description('Re-render all brand assets from the stored brand (no field changes)')
|
|
101
|
+
.option('--fix-head', 'Also splice the share/SEO head block into src/index.html (for apps installed before templates shipped it)')
|
|
102
|
+
.option('--project <guid-or-slug>', 'Target a specific project instead of cwd / Home')
|
|
103
|
+
.option('--json', 'Output raw JSON')
|
|
104
|
+
.action((opts) => run('Brand', async () => {
|
|
105
|
+
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
106
|
+
const body = opts.fixHead ? { fix_head: true } : {};
|
|
107
|
+
const res = await post(`/projects/${config.projectGuid}/brand`, body);
|
|
108
|
+
if (opts.json) {
|
|
109
|
+
console.log(JSON.stringify(res.data));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
reportFiles(res.data.files ?? []);
|
|
113
|
+
}));
|
|
114
|
+
function reportFiles(files) {
|
|
115
|
+
if (!files.length) {
|
|
116
|
+
console.log(muted('\nStored (assets not regenerated - run gipity brand apply).'));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.log(success(`\n✓ Regenerated ${files.length} asset${files.length === 1 ? '' : 's'}.`));
|
|
120
|
+
if (files.includes('src/index.html')) {
|
|
121
|
+
console.log(success('✓ src/index.html head updated (page title/description preserved).'));
|
|
122
|
+
}
|
|
123
|
+
console.log(muted(` ${DEPLOY_HINT}`));
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=brand.js.map
|
package/dist/commands/build.js
CHANGED
|
@@ -50,7 +50,7 @@ const __clPkg = readOwnPkg(__clDir);
|
|
|
50
50
|
* incomplete pull, so we can tell the user plainly that nothing was deleted. */
|
|
51
51
|
function reportSyncResult(result) {
|
|
52
52
|
if (result.aborted) {
|
|
53
|
-
console.log(` ${warning('Merge cancelled
|
|
53
|
+
console.log(` ${warning('Merge cancelled - this folder was NOT synced with the project.')}`);
|
|
54
54
|
console.log(` ${muted('For a clean copy, quit and open the project in an empty folder. To merge anyway, run `gipity sync`.')}`);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
@@ -58,11 +58,11 @@ function reportSyncResult(result) {
|
|
|
58
58
|
console.log(` Synced ${result.applied} change${result.applied > 1 ? 's' : ''} with Gipity.`);
|
|
59
59
|
}
|
|
60
60
|
if (result.errors.length) {
|
|
61
|
-
console.log(` ${warning(`Sync finished with ${result.errors.length} problem${result.errors.length === 1 ? '' : 's'}
|
|
61
|
+
console.log(` ${warning(`Sync finished with ${result.errors.length} problem${result.errors.length === 1 ? '' : 's'} - your local copy may be incomplete:`)}`);
|
|
62
62
|
for (const e of result.errors.slice(0, 8))
|
|
63
63
|
console.log(` - ${e}`);
|
|
64
64
|
if (result.errors.length > 8)
|
|
65
|
-
console.log(`
|
|
65
|
+
console.log(` ...and ${result.errors.length - 8} more.`);
|
|
66
66
|
console.log(` ${muted('Nothing was deleted. Re-run `gipity sync` to finish the pull.')}`);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
@@ -133,7 +133,7 @@ function localFsFallback(dir) {
|
|
|
133
133
|
};
|
|
134
134
|
walk(dir, 0);
|
|
135
135
|
const topLevel = topLevelEntries.length
|
|
136
|
-
? topLevelEntries.slice(0, 20).join(', ') + (topLevelEntries.length > 20 ? ',
|
|
136
|
+
? topLevelEntries.slice(0, 20).join(', ') + (topLevelEntries.length > 20 ? ', ...' : '')
|
|
137
137
|
: '(empty directory)';
|
|
138
138
|
return { fileCount, folderCount, totalBytes, topLevel };
|
|
139
139
|
}
|
|
@@ -502,6 +502,8 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
502
502
|
console.log(` ${warning('Could not sync files (will retry on next prompt):')} ${err.message}`);
|
|
503
503
|
}
|
|
504
504
|
setupProjectTools();
|
|
505
|
+
if (!nonInteractive)
|
|
506
|
+
console.log(` ${muted('Installed Gipity plugin, skills, and hooks.')}`);
|
|
505
507
|
if (nonInteractive) {
|
|
506
508
|
// Headless: the -p message is wrapped later (Step 3). Just record
|
|
507
509
|
// whether to use the new-project framing for that wrap.
|
|
@@ -673,6 +675,8 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
673
675
|
// Claude directly what they want to build or do.
|
|
674
676
|
initialPrompt = '';
|
|
675
677
|
setupProjectTools();
|
|
678
|
+
if (!nonInteractive)
|
|
679
|
+
console.log(` ${muted('Installed Gipity plugin, skills, and hooks.')}`);
|
|
676
680
|
console.log(` ${success(`Project "${project.name}" ready.`)}\n`);
|
|
677
681
|
}
|
|
678
682
|
// ── Step 3: Pick the coding agent + model ─────────────────────────
|
|
@@ -812,9 +816,8 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
812
816
|
}
|
|
813
817
|
if (!nonInteractive) {
|
|
814
818
|
console.log(` ${bold('Launching Claude Code, powered by Gipity.')}`);
|
|
815
|
-
console.log(` ${muted("Just tell Claude what you'd like to build or do - everything Claude can do,")}`);
|
|
816
|
-
console.log(` ${muted('plus hosting, databases, and live deploys on Gipity.')}`);
|
|
817
819
|
if (convGuidForHooks) {
|
|
820
|
+
console.log('');
|
|
818
821
|
console.log(` ${muted(`This session is saved to Gipity - watch it live at ${brand('prompt.gipity.ai')}. (--no-capture on init to disable.)`)}`);
|
|
819
822
|
}
|
|
820
823
|
console.log('');
|
|
@@ -960,13 +963,12 @@ async function pickAgent(lastUsed) {
|
|
|
960
963
|
const defaultIdx = AGENT_ADAPTERS.findIndex(a => a.key === defaultKey) + 1;
|
|
961
964
|
console.log(` ${bold('Which coding agent?')}\n`);
|
|
962
965
|
AGENT_ADAPTERS.forEach((a, i) => {
|
|
963
|
-
|
|
966
|
+
// Keep the list clean: no install-state notes (picking an uninstalled
|
|
967
|
+
// agent installs it, or fails with the install hint at that point).
|
|
964
968
|
const notes = [];
|
|
965
969
|
if (a.key === lastUsed)
|
|
966
970
|
notes.push('last used');
|
|
967
|
-
if (!
|
|
968
|
-
notes.push(a.ensureInstalled ? "not installed - we'll install it" : 'not installed');
|
|
969
|
-
if (a.key === 'codex' && !a.hooksSupportedOnPlatform(process.platform)) {
|
|
971
|
+
if ((a.key === 'codex' || a.key === 'agy') && !a.hooksSupportedOnPlatform(process.platform)) {
|
|
970
972
|
notes.push('session recording unavailable on Windows');
|
|
971
973
|
}
|
|
972
974
|
const note = notes.length ? ` ${muted(`(${notes.join(', ')})`)}` : '';
|
|
@@ -1101,9 +1103,8 @@ async function launchNonClaudeAgent(adapter, ctx) {
|
|
|
1101
1103
|
else {
|
|
1102
1104
|
agentArgs = [...adapter.buildInteractiveArgs({ resume, model }), ...extras];
|
|
1103
1105
|
console.log(` ${bold(`Launching ${adapter.displayName}, powered by Gipity.`)}`);
|
|
1104
|
-
console.log(` ${muted(`Just tell ${adapter.displayName} what you'd like to build or do - plus hosting,`)}`);
|
|
1105
|
-
console.log(` ${muted('databases, and live deploys on Gipity.')}`);
|
|
1106
1106
|
if (convGuid) {
|
|
1107
|
+
console.log('');
|
|
1107
1108
|
console.log(` ${muted(`This session is saved to Gipity - watch it live at ${brand('prompt.gipity.ai')}. (--no-capture on init to disable.)`)}`);
|
|
1108
1109
|
}
|
|
1109
1110
|
if (adapter.oneTimeSetupNote) {
|
package/dist/commands/chat.js
CHANGED
|
@@ -25,7 +25,7 @@ export const chatCommand = new Command('chat')
|
|
|
25
25
|
const doChat = () => post(endpoint, body);
|
|
26
26
|
const res = opts.json
|
|
27
27
|
? await doChat()
|
|
28
|
-
: await withSpinner('Thinking
|
|
28
|
+
: await withSpinner('Thinking...', doChat, { done: null });
|
|
29
29
|
// Save conversation guid for continuity. Skipped in one-off mode: the
|
|
30
30
|
// config was resolved from the server's Home project and there is no
|
|
31
31
|
// local `.gipity.json` to update - persisting here would create one in
|
package/dist/commands/deploy.js
CHANGED
|
@@ -57,7 +57,7 @@ export const deployCommand = new Command('deploy')
|
|
|
57
57
|
});
|
|
58
58
|
const res = opts.json
|
|
59
59
|
? await doDeploy()
|
|
60
|
-
: await withSpinner(`Deploying to ${target}
|
|
60
|
+
: await withSpinner(`Deploying to ${target}...`, doDeploy, { done: null });
|
|
61
61
|
const d = res.data;
|
|
62
62
|
// Deploy + verify in one command: after a successful deploy, run the
|
|
63
63
|
// same inspect pipeline `page inspect` uses against the live URL. An
|
package/dist/commands/fn.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { get, post, del, publicPost } from '../api.js';
|
|
2
|
+
import { get, post, del, publicPost, mintAppToken } from '../api.js';
|
|
3
3
|
import { getAuth } from '../auth.js';
|
|
4
4
|
import { requireConfig, resolveApiBase } from '../config.js';
|
|
5
5
|
import { error as clrError, bold, muted, success, warning } from '../colors.js';
|
|
@@ -62,13 +62,7 @@ fnCommand
|
|
|
62
62
|
* auth-gated function — the server's message is surfaced as-is, with no
|
|
63
63
|
* "run: gipity login" misdirection. */
|
|
64
64
|
async function callAnon(projectGuid, name, body) {
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const minted = await publicPost('/api/token', { app: projectGuid });
|
|
68
|
-
appToken = minted.data.token;
|
|
69
|
-
}
|
|
70
|
-
catch { /* public functions work without a token; auth-gated ones will 401 with the real reason */ }
|
|
71
|
-
return publicPost(`/api/${projectGuid}/fn/${encodeURIComponent(name)}`, body, appToken ? { 'X-App-Token': appToken } : undefined);
|
|
65
|
+
return publicPost(`/api/${projectGuid}/fn/${encodeURIComponent(name)}`, body, await mintAppToken(projectGuid));
|
|
72
66
|
}
|
|
73
67
|
fnCommand
|
|
74
68
|
.command('call <name> [body]')
|
|
@@ -219,7 +219,7 @@ Examples:
|
|
|
219
219
|
seed: Number.isFinite(opts.seed) ? opts.seed : undefined,
|
|
220
220
|
input_images: inputImages,
|
|
221
221
|
});
|
|
222
|
-
const verb = inputImages ? 'Editing image
|
|
222
|
+
const verb = inputImages ? 'Editing image...' : 'Generating image...';
|
|
223
223
|
const result = opts.json
|
|
224
224
|
? await doGenerate()
|
|
225
225
|
: await withSpinner(verb, doGenerate, { done: null });
|
|
@@ -285,7 +285,7 @@ Examples:
|
|
|
285
285
|
// Veo runs 30-120s; the bouncing bar + timer keeps the wait honest.
|
|
286
286
|
const result = opts.json
|
|
287
287
|
? await doGenerate()
|
|
288
|
-
: await withSpinner('Generating video
|
|
288
|
+
: await withSpinner('Generating video...', doGenerate, { done: null });
|
|
289
289
|
const filename = opts.output || 'generated.mp4';
|
|
290
290
|
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
291
291
|
if (opts.json) {
|
|
@@ -349,7 +349,7 @@ Examples:
|
|
|
349
349
|
});
|
|
350
350
|
const result = opts.json
|
|
351
351
|
? await doGenerate()
|
|
352
|
-
: await withSpinner('Generating speech
|
|
352
|
+
: await withSpinner('Generating speech...', doGenerate, { done: null });
|
|
353
353
|
const filename = opts.output || 'speech.mp3';
|
|
354
354
|
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
355
355
|
if (opts.json) {
|
|
@@ -402,7 +402,7 @@ Examples:
|
|
|
402
402
|
});
|
|
403
403
|
const result = opts.json
|
|
404
404
|
? await doGenerate()
|
|
405
|
-
: await withSpinner('Generating music
|
|
405
|
+
: await withSpinner('Generating music...', doGenerate, { done: null });
|
|
406
406
|
const filename = opts.output || 'music.mp3';
|
|
407
407
|
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
408
408
|
if (opts.json) {
|
|
@@ -453,7 +453,7 @@ Examples:
|
|
|
453
453
|
});
|
|
454
454
|
const result = opts.json
|
|
455
455
|
? await doGenerate()
|
|
456
|
-
: await withSpinner('Generating sound effect
|
|
456
|
+
: await withSpinner('Generating sound effect...', doGenerate, { done: null });
|
|
457
457
|
const filename = opts.output || 'sound.mp3';
|
|
458
458
|
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
459
459
|
if (opts.json) {
|
package/dist/commands/gmail.js
CHANGED
|
@@ -61,7 +61,7 @@ gmailCommand
|
|
|
61
61
|
.option('--cc <email>', 'Cc recipient (repeatable)', collect, [])
|
|
62
62
|
.option('--bcc <email>', 'Bcc recipient (repeatable)', collect, [])
|
|
63
63
|
.option('--reply-to <email>', 'Reply-To header address')
|
|
64
|
-
.requiredOption('--subject <s>', 'Subject (usually "Re:
|
|
64
|
+
.requiredOption('--subject <s>', 'Subject (usually "Re: ..." of the original)')
|
|
65
65
|
.requiredOption('--body <text>', 'Reply body (you compose the quote header)')
|
|
66
66
|
.option('--json', 'Output as JSON')
|
|
67
67
|
.action((opts) => run('Reply', async () => {
|
package/dist/commands/init.js
CHANGED
|
@@ -23,7 +23,7 @@ function resolveTools(forFlag) {
|
|
|
23
23
|
}
|
|
24
24
|
export const initCommand = new Command('init')
|
|
25
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, and installs the Gipity skills + file-sync hooks into the agent CLIs found on this machine (Claude Code, Codex, Grok).')
|
|
26
|
+
.addHelpText('after', '\nWrites CLAUDE.md/AGENTS.md primer files so your AI coding tool understands Gipity, and installs the Gipity skills + file-sync hooks into the agent CLIs found on this machine (Claude Code, Codex, Grok, Antigravity).')
|
|
27
27
|
.argument('[name]', 'Project name/slug (defaults to current directory name)')
|
|
28
28
|
.option('--agent <guid>', 'Agent GUID to use')
|
|
29
29
|
.option('--no-capture', 'Don\'t record Claude Code sessions in this directory to your Gipity project (sets captureHooks: false in .gipity.json)')
|
|
@@ -177,7 +177,7 @@ Working with an existing Gipity project:
|
|
|
177
177
|
}
|
|
178
178
|
console.log(success(`Wrote primer files: ${primerSummary}.`));
|
|
179
179
|
if (wantsClaude) {
|
|
180
|
-
console.log(success('Ready! Run your coding agent here (claude, codex, grok), or `gipity build` to launch one with a picker.'));
|
|
180
|
+
console.log(success('Ready! Run your coding agent here (claude, codex, grok, agy), or `gipity build` to launch one with a picker.'));
|
|
181
181
|
// Recording happens by default (however Claude Code is launched), so
|
|
182
182
|
// say so up front - consent should be explicit, not discovered later.
|
|
183
183
|
if (opts.capture === false) {
|
package/dist/commands/job.js
CHANGED
|
@@ -63,8 +63,13 @@ jobCommand
|
|
|
63
63
|
const r = res.data;
|
|
64
64
|
const statusColor = r.status === 'success' ? success : r.status === 'failed' ? clrError : muted;
|
|
65
65
|
console.log(`${statusColor(r.status)} ${muted(r.guid)}`);
|
|
66
|
-
if (r.progress_pct != null)
|
|
67
|
-
|
|
66
|
+
if (r.progress_pct != null) {
|
|
67
|
+
const prog = `${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ''}`;
|
|
68
|
+
// A failed run can have reported 100% (done) right before dying; a bare
|
|
69
|
+
// "progress: 100% (done)" next to status=failed reads as success. Label it.
|
|
70
|
+
const failedish = r.status === 'failed' || r.status === 'cancelled' || r.status === 'canceled' || r.status === 'error';
|
|
71
|
+
console.log(failedish ? `last progress before ${r.status}: ${prog}` : `progress: ${prog}`);
|
|
72
|
+
}
|
|
68
73
|
if (r.duration_ms != null)
|
|
69
74
|
console.log(`duration: ${r.duration_ms}ms`);
|
|
70
75
|
if (r.error)
|
|
@@ -98,7 +103,7 @@ jobCommand
|
|
|
98
103
|
? `${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ''}`
|
|
99
104
|
: r.status;
|
|
100
105
|
if (prog !== lastProgress) {
|
|
101
|
-
console.error(muted(
|
|
106
|
+
console.error(muted(`... ${prog}`));
|
|
102
107
|
lastProgress = prog;
|
|
103
108
|
}
|
|
104
109
|
}
|
|
@@ -198,7 +203,7 @@ jobCommand
|
|
|
198
203
|
}
|
|
199
204
|
catch (e) {
|
|
200
205
|
if (peekedOut) {
|
|
201
|
-
console.error(muted(
|
|
206
|
+
console.error(muted(`... still streaming after ${peekSeconds}s. Run \`gipity job logs ${runGuid} --timeout ${Math.round(peekSeconds)}\` again to keep watching, or \`gipity job wait ${runGuid}\` to block until it finishes.`));
|
|
202
207
|
return;
|
|
203
208
|
}
|
|
204
209
|
throw e;
|
|
@@ -221,7 +226,7 @@ jobCommand
|
|
|
221
226
|
catch (e) {
|
|
222
227
|
// Peek window elapsed mid-stream: report cleanly and exit 0 (not an error).
|
|
223
228
|
if (peekedOut) {
|
|
224
|
-
console.error(muted(
|
|
229
|
+
console.error(muted(`... still streaming after ${peekSeconds}s. Run \`gipity job logs ${runGuid} --timeout ${Math.round(peekSeconds)}\` again to keep watching, or \`gipity job wait ${runGuid}\` to block until it finishes.`));
|
|
225
230
|
return;
|
|
226
231
|
}
|
|
227
232
|
throw e;
|