gipity 1.1.5 → 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/agents/agy.js +52 -0
- package/dist/agents/index.js +2 -0
- package/dist/api.js +19 -3
- package/dist/capture/sources/agy.js +88 -0
- package/dist/client-context.js +9 -0
- package/dist/commands/brand.js +125 -0
- package/dist/commands/build.js +1 -1
- package/dist/commands/fn.js +2 -8
- package/dist/commands/init.js +2 -2
- package/dist/commands/key.js +91 -0
- package/dist/commands/page-eval.js +42 -3
- package/dist/commands/records.js +57 -19
- package/dist/commands/token.js +6 -1
- package/dist/commands/uninstall.js +14 -1
- package/dist/hooks/capture-runner.js +20 -8
- package/dist/index.js +553 -105
- package/dist/knowledge.js +1 -1
- package/dist/project-setup.js +2 -0
- package/dist/relay/diagnostics.js +4 -2
- package/dist/setup.js +198 -16
- package/dist/template-vars.js +74 -0
- package/package.json +2 -2
package/dist/commands/records.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
-
import { get, post, put, patch, del } from '../api.js';
|
|
2
|
+
import { get, post, put, patch, del, publicRequest, mintAppToken } from '../api.js';
|
|
3
|
+
import { getAuth } from '../auth.js';
|
|
3
4
|
import { requireConfig } from '../config.js';
|
|
4
5
|
import { bold, muted } from '../colors.js';
|
|
5
6
|
import { run, printList, printResult } from '../helpers/index.js';
|
|
@@ -10,6 +11,33 @@ import { confirm } from '../utils.js';
|
|
|
10
11
|
// /projects/<guid>/records mirror.)
|
|
11
12
|
export const recordsCommand = new Command('records')
|
|
12
13
|
.description('Manage app records (Gipity Records - validated CRUD with audit history)');
|
|
14
|
+
const ANON_FLAG = '--anon';
|
|
15
|
+
const ANON_HELP = 'Call as an anonymous visitor (the public path a signed-out user hits) instead of as your signed-in account';
|
|
16
|
+
/** One HTTP surface for every records subcommand, so `--anon` is a persona
|
|
17
|
+
* switch rather than a different code path. Anonymous calls mint the same
|
|
18
|
+
* short-lived app token the browser SDK uses and send no owner credentials -
|
|
19
|
+
* this is exactly what a signed-out visitor's request looks like, so a 401 here
|
|
20
|
+
* is the true answer for an auth-gated table. The persona goes to stderr so
|
|
21
|
+
* stdout stays parseable; without it, "verify the anonymous path" silently runs
|
|
22
|
+
* as the owner and the public path never gets exercised. */
|
|
23
|
+
async function recordsHttp(opts) {
|
|
24
|
+
const config = requireConfig();
|
|
25
|
+
if (!opts.anon) {
|
|
26
|
+
const who = getAuth()?.email;
|
|
27
|
+
console.error(muted(`Auth: calling as ${who ?? 'your signed-in account'} (the owner persona; use ${ANON_FLAG} for the public visitor path)`));
|
|
28
|
+
return { guid: config.projectGuid, get, post, put, patch, del };
|
|
29
|
+
}
|
|
30
|
+
console.error(muted('Auth: anonymous visitor (the public path a signed-out user hits)'));
|
|
31
|
+
const headers = await mintAppToken(config.projectGuid);
|
|
32
|
+
return {
|
|
33
|
+
guid: config.projectGuid,
|
|
34
|
+
get: (path) => publicRequest('GET', path, undefined, headers),
|
|
35
|
+
post: (path, body) => publicRequest('POST', path, body, headers),
|
|
36
|
+
put: (path, body) => publicRequest('PUT', path, body, headers),
|
|
37
|
+
patch: (path, body) => publicRequest('PATCH', path, body, headers),
|
|
38
|
+
del: (path, body) => publicRequest('DELETE', path, body, headers),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
13
41
|
recordsCommand
|
|
14
42
|
.command('list')
|
|
15
43
|
.description('List record tables')
|
|
@@ -57,9 +85,10 @@ recordsCommand
|
|
|
57
85
|
.option('--limit <n>', 'Max rows', '20')
|
|
58
86
|
.option('--offset <n>', 'Offset', '0')
|
|
59
87
|
.option('--fields <fields>', 'Comma-separated column names')
|
|
88
|
+
.option(ANON_FLAG, ANON_HELP)
|
|
60
89
|
.option('--json', 'Output as JSON')
|
|
61
90
|
.action((table, opts) => run('Query', async () => {
|
|
62
|
-
const
|
|
91
|
+
const api = await recordsHttp(opts);
|
|
63
92
|
const params = new URLSearchParams();
|
|
64
93
|
if (opts.filter)
|
|
65
94
|
params.set('filter', opts.filter);
|
|
@@ -69,7 +98,7 @@ recordsCommand
|
|
|
69
98
|
params.set('offset', opts.offset);
|
|
70
99
|
if (opts.fields)
|
|
71
100
|
params.set('fields', opts.fields);
|
|
72
|
-
const res = await get(`/api/${
|
|
101
|
+
const res = await api.get(`/api/${api.guid}/records/${table}?${params}`);
|
|
73
102
|
if (opts.json) {
|
|
74
103
|
console.log(JSON.stringify(res));
|
|
75
104
|
}
|
|
@@ -85,21 +114,26 @@ recordsCommand
|
|
|
85
114
|
recordsCommand
|
|
86
115
|
.command('get <table> <id>')
|
|
87
116
|
.description('Get a record')
|
|
117
|
+
.option(ANON_FLAG, ANON_HELP)
|
|
88
118
|
.option('--json', 'Output as JSON')
|
|
89
119
|
.action((table, id, opts) => run('Get', async () => {
|
|
90
|
-
const
|
|
91
|
-
const res = await get(`/api/${
|
|
120
|
+
const api = await recordsHttp(opts);
|
|
121
|
+
const res = await api.get(`/api/${api.guid}/records/${table}/${id}`);
|
|
92
122
|
console.log(opts.json ? JSON.stringify(res.data) : JSON.stringify(res.data, null, 2));
|
|
93
123
|
}));
|
|
94
124
|
recordsCommand
|
|
95
|
-
.command('history <table>
|
|
96
|
-
.description('Audit history
|
|
125
|
+
.command('history <table> [id]')
|
|
126
|
+
.description('Audit history (who/what changed it, with English summaries). Omit <id> for the whole table\'s feed.')
|
|
97
127
|
.option('--limit <n>', 'Max events', '20')
|
|
128
|
+
.option(ANON_FLAG, ANON_HELP)
|
|
98
129
|
.option('--json', 'Output as JSON')
|
|
99
130
|
.action((table, id, opts) => run('History', async () => {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
131
|
+
const api = await recordsHttp(opts);
|
|
132
|
+
// Table-wide feed when no id is given - the same endpoint an activity/history
|
|
133
|
+
// view reads, so verifying it needs no hand-built HTTP call.
|
|
134
|
+
const scope = id ? `${table}/${encodeURIComponent(id)}` : table;
|
|
135
|
+
const res = await api.get(`/api/${api.guid}/records/${scope}/history?limit=${encodeURIComponent(opts.limit)}`);
|
|
136
|
+
printList(res.data, opts, id ? 'No history for this record.' : `No history for "${table}".`, e => {
|
|
103
137
|
const summary = e.detail?.summary || `${e.action} ${e.entity_type} ${e.entity_id}`;
|
|
104
138
|
return `${muted(e.created_at)} ${bold(e.source || '-')} ${summary}`;
|
|
105
139
|
});
|
|
@@ -108,34 +142,38 @@ recordsCommand
|
|
|
108
142
|
.command('create <table>')
|
|
109
143
|
.description('Create a record')
|
|
110
144
|
.requiredOption('--data <json>', 'JSON object with field values')
|
|
145
|
+
.option(ANON_FLAG, ANON_HELP)
|
|
111
146
|
.option('--json', 'Output as JSON')
|
|
112
147
|
.action((table, opts) => run('Create', async () => {
|
|
113
|
-
const
|
|
148
|
+
const api = await recordsHttp(opts);
|
|
114
149
|
const data = JSON.parse(opts.data);
|
|
115
|
-
const res = await post(`/api/${
|
|
150
|
+
const res = await api.post(`/api/${api.guid}/records/${table}`, data);
|
|
116
151
|
printResult(`Created: ${JSON.stringify(res.data)}`, opts, res.data);
|
|
117
152
|
}));
|
|
118
153
|
recordsCommand
|
|
119
154
|
.command('update <table> <id>')
|
|
120
155
|
.description('Update a record')
|
|
121
156
|
.requiredOption('--data <json>', 'JSON object with fields to update')
|
|
157
|
+
.option(ANON_FLAG, ANON_HELP)
|
|
122
158
|
.option('--json', 'Output as JSON')
|
|
123
159
|
.action((table, id, opts) => run('Update', async () => {
|
|
124
|
-
const
|
|
160
|
+
const api = await recordsHttp(opts);
|
|
125
161
|
const data = JSON.parse(opts.data);
|
|
126
|
-
const res = await put(`/api/${
|
|
162
|
+
const res = await api.put(`/api/${api.guid}/records/${table}/${id}`, data);
|
|
127
163
|
printResult(`Updated: ${JSON.stringify(res.data)}`, opts, res.data);
|
|
128
164
|
}));
|
|
129
165
|
recordsCommand
|
|
130
166
|
.command('delete <table> <id>')
|
|
131
167
|
.description('Delete a record')
|
|
132
|
-
.
|
|
168
|
+
.option(ANON_FLAG, ANON_HELP)
|
|
169
|
+
.option('--json', 'Output as JSON')
|
|
170
|
+
.action((table, id, opts) => run('Delete', async () => {
|
|
133
171
|
if (!await confirm(`Delete record ${id} from "${table}"?`)) {
|
|
134
|
-
|
|
172
|
+
printResult('Cancelled.', opts, { table, id, deleted: false, cancelled: true });
|
|
135
173
|
return;
|
|
136
174
|
}
|
|
137
|
-
const
|
|
138
|
-
await del(`/api/${
|
|
139
|
-
printResult('Deleted.', {
|
|
175
|
+
const api = await recordsHttp(opts);
|
|
176
|
+
const res = await api.del(`/api/${api.guid}/records/${table}/${id}`);
|
|
177
|
+
printResult('Deleted.', opts, res?.data ?? { table, id, deleted: true });
|
|
140
178
|
}));
|
|
141
179
|
//# sourceMappingURL=records.js.map
|
package/dist/commands/token.js
CHANGED
|
@@ -4,7 +4,12 @@ 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
6
|
.description('Manage API tokens')
|
|
7
|
-
.addHelpText('after',
|
|
7
|
+
.addHelpText('after', `
|
|
8
|
+
Long-lived agent API tokens (gip_at_*) for headless agents and CI - they drive
|
|
9
|
+
the gipity CLI as YOU.
|
|
10
|
+
|
|
11
|
+
To let a script or cron write to one app instead, mint a project API key:
|
|
12
|
+
gipity key create "my script" --role editor (sent as X-Api-Key).`);
|
|
8
13
|
const fmtDate = (d) => (d ? new Date(d).toLocaleDateString() : 'never');
|
|
9
14
|
tokenCommand
|
|
10
15
|
.command('create')
|
|
@@ -19,7 +19,7 @@ import { confirm, getAutoConfirm } from '../utils.js';
|
|
|
19
19
|
import { bold, brand, dim, success, error as clrError, muted } from '../colors.js';
|
|
20
20
|
import * as relayState from '../relay/state.js';
|
|
21
21
|
import { planFor, UnsupportedPlatformError } from '../relay/installers.js';
|
|
22
|
-
import { GIPITY_PLUGIN_ID, GIPITY_MARKETPLACE_NAME, stripGipityHooks, grokInstallState, agentSkillsState, AGENTS_SKILLS_DIR } from '../setup.js';
|
|
22
|
+
import { GIPITY_PLUGIN_ID, GIPITY_MARKETPLACE_NAME, stripGipityHooks, grokInstallState, agentSkillsState, AGENTS_SKILLS_DIR, agySkillsState, AGY_SKILLS_DIR, } from '../setup.js';
|
|
23
23
|
/** Remove Gipity's entries from the user-scope Claude Code settings: the
|
|
24
24
|
* plugin enablement, the marketplace registration, and any legacy hook
|
|
25
25
|
* blocks older CLI versions wrote there. Surgical - everything else in the
|
|
@@ -240,6 +240,19 @@ export const uninstallCommand = new Command('uninstall')
|
|
|
240
240
|
}
|
|
241
241
|
console.log(`${success(`Removed ${agentSkills.skills.length} Gipity skills from ~/.agents/skills.`)}`);
|
|
242
242
|
}
|
|
243
|
+
// 4c. Remove the skills the CLI copied into Antigravity's own global
|
|
244
|
+
// skill root (~/.gemini/config/skills - a different directory from
|
|
245
|
+
// Codex's ~/.agents/skills, see setup.ts).
|
|
246
|
+
const agySkills = agySkillsState();
|
|
247
|
+
if (agySkills.skills.length) {
|
|
248
|
+
for (const name of agySkills.skills) {
|
|
249
|
+
try {
|
|
250
|
+
rmSync(join(AGY_SKILLS_DIR, name), { recursive: true, force: true });
|
|
251
|
+
}
|
|
252
|
+
catch { /* best-effort */ }
|
|
253
|
+
}
|
|
254
|
+
console.log(`${success(`Removed ${agySkills.skills.length} Gipity skills from ~/.gemini/config/skills.`)}`);
|
|
255
|
+
}
|
|
243
256
|
// 5. Wipe ~/.gipity/ (this also removes the agent-hooks scripts and the
|
|
244
257
|
// agent-skills manifest, which live under it).
|
|
245
258
|
if (existsSync(gipityDir)) {
|
|
@@ -51,6 +51,7 @@ import { getConfig } from '../config.js';
|
|
|
51
51
|
import { parseTranscript as parseClaudeTranscript, } from '../capture/sources/claude-code.js';
|
|
52
52
|
import { parseTranscript as parseCodexTranscript } from '../capture/sources/codex.js';
|
|
53
53
|
import { parseTranscript as parseGrokTranscript } from '../capture/sources/grok.js';
|
|
54
|
+
import { parseTranscript as parseAgyTranscript } from '../capture/sources/agy.js';
|
|
54
55
|
const CAPTURE_DIR = join(homedir(), '.gipity', 'capture-state');
|
|
55
56
|
const INGEST_BATCH_MAX = 100; // server caps at 200; stay comfortably under
|
|
56
57
|
export const CAPTURE_SOURCES = {
|
|
@@ -77,6 +78,14 @@ export const CAPTURE_SOURCES = {
|
|
|
77
78
|
return join(homedir(), '.grok', 'sessions', encodeURIComponent(cwd), hook.session_id, 'chat_history.jsonl');
|
|
78
79
|
},
|
|
79
80
|
},
|
|
81
|
+
agy: {
|
|
82
|
+
serverSource: 'agy',
|
|
83
|
+
displayName: 'Antigravity',
|
|
84
|
+
// agy hook payloads always carry transcriptPath directly (no derivation
|
|
85
|
+
// needed, unlike Grok) - the agy-specific hook wrapper normalizes it into
|
|
86
|
+
// this HookInput's transcript_path before invoking this runner.
|
|
87
|
+
parse: (content, afterUuid, hook) => parseAgyTranscript(content, afterUuid, { conversationId: hook.session_id }),
|
|
88
|
+
},
|
|
80
89
|
};
|
|
81
90
|
// PostToolUse fires after every tool call. We flush on it so a session that is
|
|
82
91
|
// killed/crashes mid-run (e.g. a long headless `gipity claude -p` build that
|
|
@@ -430,9 +439,10 @@ async function handleSessionEnd(convGuid, src, hook) {
|
|
|
430
439
|
if (hook.session_id)
|
|
431
440
|
deleteSessionMap(hook.session_id);
|
|
432
441
|
}
|
|
433
|
-
// Codex
|
|
434
|
-
// never cleaned by handleSessionEnd. Sweep anything
|
|
435
|
-
// - the files are tiny, so a generous TTL is fine; a
|
|
442
|
+
// Codex and agy have no SessionEnd-equivalent hook, so their capture-state/
|
|
443
|
+
// session-map files are never cleaned by handleSessionEnd. Sweep anything
|
|
444
|
+
// stale on the way through - the files are tiny, so a generous TTL is fine; a
|
|
445
|
+
// live session's state is
|
|
436
446
|
// rewritten on every flush and never gets this old.
|
|
437
447
|
const STATE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
438
448
|
function sweepStaleState() {
|
|
@@ -455,8 +465,10 @@ function sweepStaleState() {
|
|
|
455
465
|
}
|
|
456
466
|
/** Normalize a raw hook payload to snake_case HookInput. Claude Code and
|
|
457
467
|
* Codex deliver snake_case (`session_id`, `transcript_path`, `cwd`); Grok
|
|
458
|
-
* Build delivers camelCase (`sessionId`, `hookEventName`, …)
|
|
459
|
-
*
|
|
468
|
+
* Build delivers camelCase (`sessionId`, `hookEventName`, …); agy calls its
|
|
469
|
+
* session id `conversationId` (its own hook payload has no session/cwd/event
|
|
470
|
+
* fields under any other name - see cli/src/agents/agy.ts). Accept all of
|
|
471
|
+
* these so one runner serves every harness. */
|
|
460
472
|
export function normalizeHookInput(raw) {
|
|
461
473
|
if (!raw || typeof raw !== 'object')
|
|
462
474
|
return {};
|
|
@@ -469,7 +481,7 @@ export function normalizeHookInput(raw) {
|
|
|
469
481
|
return undefined;
|
|
470
482
|
};
|
|
471
483
|
const hook = {
|
|
472
|
-
session_id: pick('session_id', 'sessionId'),
|
|
484
|
+
session_id: pick('session_id', 'sessionId', 'conversationId'),
|
|
473
485
|
transcript_path: pick('transcript_path', 'transcriptPath'),
|
|
474
486
|
cwd: pick('cwd', 'workingDirectory'),
|
|
475
487
|
hook_event_name: pick('hook_event_name', 'hookEventName'),
|
|
@@ -509,8 +521,8 @@ async function main() {
|
|
|
509
521
|
if (derived)
|
|
510
522
|
hook.transcript_path = derived;
|
|
511
523
|
}
|
|
512
|
-
// Opportunistic hygiene: Codex never
|
|
513
|
-
// files are TTL-swept instead of deleted at end-of-session.
|
|
524
|
+
// Opportunistic hygiene: Codex and agy never fire session-end, so their
|
|
525
|
+
// state files are TTL-swept instead of deleted at end-of-session.
|
|
514
526
|
sweepStaleState();
|
|
515
527
|
const convGuid = await resolveConvGuid(hook, src.serverSource);
|
|
516
528
|
if (!convGuid)
|