gipity 1.1.5 → 1.1.7
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/catalog.js +2 -2
- package/dist/client-context.js +9 -0
- package/dist/commands/brand.js +125 -0
- package/dist/commands/build.js +1 -1
- package/dist/commands/db.js +61 -0
- 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 +70 -3
- package/dist/commands/page-screenshot.js +34 -6
- package/dist/commands/records.js +93 -23
- package/dist/commands/service.js +71 -7
- package/dist/commands/token.js +6 -1
- package/dist/commands/uninstall.js +14 -1
- package/dist/commands/workflow.js +59 -6
- package/dist/db-checkpoint.js +42 -0
- package/dist/hooks/capture-runner.js +20 -8
- package/dist/index.js +777 -137
- package/dist/knowledge.js +3 -3
- package/dist/project-setup.js +2 -0
- package/dist/relay/diagnostics.js +4 -2
- package/dist/relay/onboarding.js +3 -3
- package/dist/setup.js +198 -16
- package/dist/template-vars.js +74 -0
- package/dist/utils.js +16 -5
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
2
|
import { get, post, put, del } from '../api.js';
|
|
3
3
|
import { getConfig, requireConfig } from '../config.js';
|
|
4
4
|
import { success, error as clrError, muted, bold } from '../colors.js';
|
|
@@ -29,6 +29,51 @@ function formatRunLine(r) {
|
|
|
29
29
|
const statusColor = r.status === 'completed' ? success : r.status === 'failed' ? clrError : muted;
|
|
30
30
|
return `${muted(r.short_guid)} ${statusColor(r.status)} ${dur} ${runTokens(r)} tokens ${muted(fmtTime(r.started_at))}`;
|
|
31
31
|
}
|
|
32
|
+
/** Classify a step-output value that is a *string* holding JSON: parsed, or
|
|
33
|
+
* looks-like-JSON-but-doesn't-parse (a truncated / cut-off llm response — the
|
|
34
|
+
* step reports `completed` and the next step then can't read a field out of
|
|
35
|
+
* it). `null` means "not a JSON string at all". */
|
|
36
|
+
function jsonStringInfo(v) {
|
|
37
|
+
if (typeof v !== 'string')
|
|
38
|
+
return null;
|
|
39
|
+
const t = v.trim();
|
|
40
|
+
if (!t.startsWith('{') && !t.startsWith('['))
|
|
41
|
+
return null;
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(t);
|
|
44
|
+
return typeof parsed === 'object' && parsed !== null ? { ok: true, value: parsed } : null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { ok: false };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function labelFor(info) {
|
|
51
|
+
return info.ok ? '(JSON string)' : clrError('(truncated/invalid JSON string)');
|
|
52
|
+
}
|
|
53
|
+
/** Render one step's output for humans.
|
|
54
|
+
*
|
|
55
|
+
* Step outputs routinely carry a JSON *string* under a key — an `llm` step's
|
|
56
|
+
* `result` is the common case. Dumped through a plain JSON.stringify that
|
|
57
|
+
* arrives as one enormous backslash-escaped line: unreadable, and it hides the
|
|
58
|
+
* single fact you need most, that the NEXT step sees a string, not an object
|
|
59
|
+
* (so `{{step.summary}}` resolves to nothing). Label such keys
|
|
60
|
+
* `"key" (JSON string)` and print the decoded value indented under it, so both
|
|
61
|
+
* the content and the string-vs-object handoff are obvious at a glance. */
|
|
62
|
+
function renderStepOutput(output) {
|
|
63
|
+
if (output && typeof output === 'object' && !Array.isArray(output)) {
|
|
64
|
+
return Object.entries(output).map(([k, v]) => {
|
|
65
|
+
const info = jsonStringInfo(v);
|
|
66
|
+
if (!info)
|
|
67
|
+
return `${JSON.stringify(k)}: ${JSON.stringify(v, null, 2)}`;
|
|
68
|
+
const body = info.ok ? JSON.stringify(info.value, null, 2) : JSON.stringify(v);
|
|
69
|
+
return `${JSON.stringify(k)} ${labelFor(info)}: ${body}`;
|
|
70
|
+
}).join('\n');
|
|
71
|
+
}
|
|
72
|
+
const info = jsonStringInfo(output);
|
|
73
|
+
if (info?.ok)
|
|
74
|
+
return JSON.stringify(info.value, null, 2);
|
|
75
|
+
return JSON.stringify(output, null, 2);
|
|
76
|
+
}
|
|
32
77
|
/** Print each step's status, tokens, model, error and output. A run line alone
|
|
33
78
|
* says a run finished, not what it did — without the steps you can't tell a
|
|
34
79
|
* workflow that wrote a row from one that silently skipped every step. */
|
|
@@ -46,7 +91,7 @@ function printStepRuns(steps, emptyNote) {
|
|
|
46
91
|
if (s.error_message)
|
|
47
92
|
console.log(` ${clrError(s.error_message)}`);
|
|
48
93
|
if (s.output_json !== null && s.output_json !== undefined) {
|
|
49
|
-
console.log(
|
|
94
|
+
console.log(renderStepOutput(s.output_json).split('\n').map(l => ` ${l}`).join('\n'));
|
|
50
95
|
}
|
|
51
96
|
}
|
|
52
97
|
}
|
|
@@ -149,10 +194,18 @@ workflowCommand
|
|
|
149
194
|
}));
|
|
150
195
|
workflowCommand
|
|
151
196
|
.command('run <name>')
|
|
152
|
-
.description(
|
|
197
|
+
.description("Run a workflow: waits for it to finish and prints each step's status and output (--no-wait to fire and forget)")
|
|
153
198
|
.option('--json', 'Output as JSON')
|
|
154
|
-
|
|
155
|
-
|
|
199
|
+
// Waiting is the default: a bare trigger tells you nothing about what the
|
|
200
|
+
// workflow did, so every caller followed it with a poll-and-drill-in loop.
|
|
201
|
+
// `--no-wait` is the escape hatch for fire-and-forget.
|
|
202
|
+
.option('--no-wait', 'Trigger and return immediately instead of waiting for the run')
|
|
203
|
+
// Waiting is already the default, but `--wait` reads naturally and older
|
|
204
|
+
// docs/muscle memory reach for it — accept it silently rather than erroring.
|
|
205
|
+
.addOption(new Option('--wait', 'Wait for the run (already the default)').hideHelp())
|
|
206
|
+
// LLM steps routinely take a couple of minutes; a 120s default timed out on
|
|
207
|
+
// healthy runs and taught callers to always pass --timeout.
|
|
208
|
+
.option('--timeout <s>', 'Max seconds to wait for the run to finish', '300')
|
|
156
209
|
.action((name, _opts, cmd) => run('Run', async () => {
|
|
157
210
|
const opts = mergedOpts(cmd);
|
|
158
211
|
const wf = await resolveWorkflow(name);
|
|
@@ -168,7 +221,7 @@ workflowCommand
|
|
|
168
221
|
const before = await get(`/workflows/${wf.short_guid}/runs?limit=1`);
|
|
169
222
|
const prevGuid = before.data[0]?.short_guid;
|
|
170
223
|
await post(`/workflows/${wf.short_guid}/run`, {});
|
|
171
|
-
const r = await waitForRun(wf.short_guid, prevGuid, Number(opts.timeout) ||
|
|
224
|
+
const r = await waitForRun(wf.short_guid, prevGuid, Number(opts.timeout) || 300);
|
|
172
225
|
if (opts.json) {
|
|
173
226
|
console.log(JSON.stringify(r));
|
|
174
227
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database checkpoint / restore — the undo for a LIVE write test.
|
|
3
|
+
*
|
|
4
|
+
* Verifying a write path for real (clicking Approve in the deployed UI, calling
|
|
5
|
+
* a function that inserts, letting a scheduled workflow run) mutates the
|
|
6
|
+
* project's real data, and there was no way to put it back - agents ended up
|
|
7
|
+
* hand-writing SQL to scrub their own test strings out.
|
|
8
|
+
*
|
|
9
|
+
* The snapshot itself is the SERVER's job: it lives in a sibling schema the app
|
|
10
|
+
* can't see, and restore runs in one transaction. This module is just the
|
|
11
|
+
* client - the CLI never composes checkpoint DDL, so a snapshot can never show
|
|
12
|
+
* up in `db query`, in a migration diff, or in app code that enumerates tables.
|
|
13
|
+
*/
|
|
14
|
+
import { get, post } from './api.js';
|
|
15
|
+
/** First database in the project, or null when the project has none. */
|
|
16
|
+
export async function resolveDatabase(projectGuid, explicit) {
|
|
17
|
+
if (explicit)
|
|
18
|
+
return explicit;
|
|
19
|
+
const res = await get(`/projects/${projectGuid}/databases`);
|
|
20
|
+
return res.data.length > 0 ? res.data[0].friendlyName : null;
|
|
21
|
+
}
|
|
22
|
+
async function checkpoint(projectGuid, database, action, keep) {
|
|
23
|
+
const res = await post(`/projects/${projectGuid}/db/checkpoint`, { database, action, ...(keep ? { keep } : {}) });
|
|
24
|
+
return res.data;
|
|
25
|
+
}
|
|
26
|
+
/** Snapshot every app table. Replaces any previous checkpoint. */
|
|
27
|
+
export function createCheckpoint(projectGuid, database) {
|
|
28
|
+
return checkpoint(projectGuid, database, 'create');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Put every checkpointed table back to its snapshot contents and drop the
|
|
32
|
+
* snapshot (unless `keep`). Sequences are deliberately NOT rewound - leaving
|
|
33
|
+
* them ahead keeps ids unique against anything the test run handed out.
|
|
34
|
+
*/
|
|
35
|
+
export function restoreCheckpoint(projectGuid, database, opts = {}) {
|
|
36
|
+
return checkpoint(projectGuid, database, 'restore', opts.keep);
|
|
37
|
+
}
|
|
38
|
+
/** Drop the checkpoint without restoring (keep whatever the run wrote). */
|
|
39
|
+
export function dropCheckpoint(projectGuid, database) {
|
|
40
|
+
return checkpoint(projectGuid, database, 'drop');
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=db-checkpoint.js.map
|
|
@@ -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)
|