omniagent 0.1.13 → 0.1.16

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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # omniagent
2
2
 
3
- One source of truth for agent config across Claude, Codex, Gemini, and Copilot (and [any other agent](docs/custom-targets.md))
3
+ One source of truth for agent config across Claude, Codex, Antigravity (agy), and Copilot (and [any other agent](docs/custom-targets.md))
4
4
 
5
5
  Define canonical agent files once in `agents/`, then run `sync` to compile target-specific outputs.
6
6
 
@@ -89,14 +89,17 @@ omniagent --agent codex
89
89
  omniagent -p "Summarize this repo" --agent codex --output json
90
90
  ```
91
91
 
92
- `usage` supports Codex, Claude, and Gemini in v1. Copilot is not supported for usage
93
- extraction yet. Usage extraction may launch agent TUIs and may incur cost if an agent
94
- reads repo context or instructions on startup; omniagent uses cheap/minimal launch settings
95
- where possible. Usage extraction times out after 30 seconds unless the target config defines
96
- a target-specific timeout; built-in TUI probes may use longer defaults. Some CLIs require
97
- session-scoped setup flags to inspect usage, such as Gemini's `--skip-trust`; omniagent does
98
- not complete auth or onboarding prompts for you. Pass `--timeout=<seconds>` to override the
99
- per-agent timeout for the current run.
92
+ `usage` supports Codex, Claude, and Antigravity (`agy`; `gemini` works as an alias). Copilot
93
+ is not supported for usage extraction yet. Usage extraction may launch agent TUIs and may incur
94
+ cost if an agent reads repo context or instructions on startup; omniagent uses cheap/minimal
95
+ launch settings where possible. Usage extraction times out after 30 seconds unless the target
96
+ config defines a target-specific timeout; built-in TUI probes may use longer defaults. Some CLIs
97
+ gate usage inspection behind auth or onboarding state. When Antigravity requests directory trust,
98
+ interactive usage shows the exact directory and forwards approval only after you confirm;
99
+ omniagent never accepts trust automatically or completes authentication prompts. If Antigravity
100
+ requests directory trust, JSON, debug, and non-interactive runs return a `trust_required` error
101
+ instead of prompting. Pass `--timeout=<seconds>` to override the per-agent timeout for the current
102
+ run.
100
103
 
101
104
  ## Local Overrides (`.local`)
102
105
 
@@ -213,6 +216,18 @@ Example usage:
213
216
  ./code-review.sh codex
214
217
  ```
215
218
 
219
+ The shim also unifies structured outputs: pass a JSON schema (file path or inline JSON) with
220
+ `--output-schema`, and stdout is exactly the schema-conforming JSON regardless of agent. Agents
221
+ with native schema support (claude, codex) enforce it server-side; all others get a prompt-based
222
+ fallback with client-side validation and automatic retries:
223
+
224
+ ```bash
225
+ omniagent -p "Top 3 benefits of TypeScript" --agent claude \
226
+ --output-schema ./schema.json | jq .answer
227
+ ```
228
+
229
+ See [`docs/cli-shim.md`](docs/cli-shim.md) for the full shared-flag capability matrix.
230
+
216
231
  ## Documentation
217
232
 
218
233
  - Docs index: [`docs/README.md`](docs/README.md)
@@ -0,0 +1,379 @@
1
+ import { mkdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { c as cleanControlOutput, m as makeUsageLimit, U as UsageExtractionError, a as compactLines, p as parsePercentRemaining } from "./cli.js";
4
+ import { r as runPtyScenario, e as enterKey, a as escapeKey } from "./pty-D8BFQQQg.js";
5
+ const TRUST_DIALOG_PATTERN = /Do you trust the contents of this project\?/i;
6
+ const READY_PATTERN = /\?\s+for shortcuts/i;
7
+ const LOGIN_SELECTION_PATTERN = /Select login method:/i;
8
+ const USAGE_GROUP_HEADING_PATTERN = /^[A-Z][A-Z0-9 &/-]+$/;
9
+ const LIMIT_LABEL_PATTERN = /limit$/i;
10
+ const MODELS_LINE_PATTERN = /^Models within this group:\s*(.+)$/i;
11
+ const REFRESH_PATTERN = /Refreshes\s+in\s+(?:(\d+)h)?\s*(?:(\d+)m)?/i;
12
+ const NOT_SIGNED_IN_PATTERN = /\bnot signed in\b/i;
13
+ const SIGNING_IN_PATTERN = /\bsigning in\b/i;
14
+ const DISABLED_PATTERN = /^Disabled$/i;
15
+ const AGY_USAGE_FALLBACK_PATH = [".omniagent", "state", "usage", "antigravity-cli"];
16
+ const STARTUP_READY_TIMEOUT_MS = 25e3;
17
+ const USAGE_PANEL_STABLE_MS = 1e3;
18
+ const USAGE_PANEL_MIN_OBSERVE_MS = 2e3;
19
+ function isCurrentTrustDialog(snapshot) {
20
+ return TRUST_DIALOG_PATTERN.test(snapshot.screen);
21
+ }
22
+ function isNotCurrentTrustDialog(snapshot) {
23
+ return !isCurrentTrustDialog(snapshot);
24
+ }
25
+ function isReady(snapshot) {
26
+ return READY_PATTERN.test(snapshot.screen);
27
+ }
28
+ function isLoginSelection(snapshot) {
29
+ return LOGIN_SELECTION_PATTERN.test(snapshot.screen);
30
+ }
31
+ function isStartupTerminalState(snapshot) {
32
+ return isReady(snapshot) || isCurrentTrustDialog(snapshot) || isLoginSelection(snapshot);
33
+ }
34
+ function isPostTrustTerminalState(snapshot) {
35
+ return isReady(snapshot) || isLoginSelection(snapshot);
36
+ }
37
+ function isCurrentSignInFailure(snapshot) {
38
+ return NOT_SIGNED_IN_PATTERN.test(snapshot.screen) || isLoginSelection(snapshot);
39
+ }
40
+ function isInteractionBlocked(snapshot) {
41
+ return isCurrentTrustDialog(snapshot) || isCurrentSignInFailure(snapshot);
42
+ }
43
+ function isAuthenticationTransition(snapshot) {
44
+ return SIGNING_IN_PATTERN.test(snapshot.screen);
45
+ }
46
+ function isUsageWriteBlocked(snapshot) {
47
+ return isInteractionBlocked(snapshot) || isAuthenticationTransition(snapshot);
48
+ }
49
+ function createStableUsagePanelWait(stableMs = USAGE_PANEL_STABLE_MS, minObserveMs = USAGE_PANEL_MIN_OBSERVE_MS) {
50
+ let previousSignature = "";
51
+ let firstSeenAt = null;
52
+ let stableSince = 0;
53
+ return (snapshot) => {
54
+ if (isInteractionBlocked(snapshot)) {
55
+ return true;
56
+ }
57
+ const groups = parseAgyUsage(snapshot.screen, cleanControlOutput(snapshot.raw));
58
+ if (groups.length === 0) {
59
+ previousSignature = "";
60
+ firstSeenAt = null;
61
+ stableSince = 0;
62
+ return false;
63
+ }
64
+ const signature = usageGroupSignature(groups);
65
+ const now = Date.now();
66
+ if (firstSeenAt == null) {
67
+ firstSeenAt = now;
68
+ }
69
+ if (signature !== previousSignature) {
70
+ previousSignature = signature;
71
+ stableSince = now;
72
+ return false;
73
+ }
74
+ return now - firstSeenAt >= minObserveMs && now - stableSince >= stableMs;
75
+ };
76
+ }
77
+ function guardedUsageWrite(value, scenarioState, waitMs) {
78
+ return {
79
+ waitMs,
80
+ write: (snapshot) => {
81
+ if (isUsageWriteBlocked(snapshot)) {
82
+ scenarioState.canEnterUsage = false;
83
+ return void 0;
84
+ }
85
+ return scenarioState.canEnterUsage ? value : void 0;
86
+ }
87
+ };
88
+ }
89
+ async function extractAgyUsage(context) {
90
+ const command = context.command ?? context.launch?.command ?? "agy";
91
+ const launchCwd = await ensureAgyUsageCwd(context.homeDir, context.repoRoot);
92
+ const scenarioState = {
93
+ trustOutcome: "not-requested",
94
+ canEnterUsage: false,
95
+ reachedReadyAfterTrust: false
96
+ };
97
+ const ptyResult = await runPtyScenario({
98
+ command,
99
+ args: context.launch?.args ?? [],
100
+ cwd: launchCwd.path,
101
+ cols: 120,
102
+ rows: 40,
103
+ timeoutMs: context.launch?.timeoutMs ?? 7e4,
104
+ signal: context.signal,
105
+ debug: context.debug,
106
+ steps: [
107
+ {
108
+ waitFor: isStartupTerminalState,
109
+ waitForTimeoutMs: STARTUP_READY_TIMEOUT_MS,
110
+ optional: true,
111
+ capture: "startup",
112
+ captureWaitMs: 0
113
+ },
114
+ {
115
+ skipIf: isNotCurrentTrustDialog,
116
+ skipIfSource: "screen",
117
+ write: async () => {
118
+ if (context.confirm == null) {
119
+ scenarioState.trustOutcome = "required";
120
+ return void 0;
121
+ }
122
+ const approved = await context.confirm({
123
+ type: "trust-directory",
124
+ targetId: context.targetId,
125
+ displayName: context.displayName,
126
+ path: launchCwd.path,
127
+ managed: launchCwd.managed
128
+ });
129
+ scenarioState.trustOutcome = approved ? "approved" : "denied";
130
+ return void 0;
131
+ }
132
+ },
133
+ {
134
+ write: (snapshot2) => {
135
+ if (!isCurrentTrustDialog(snapshot2)) {
136
+ return void 0;
137
+ }
138
+ if (scenarioState.trustOutcome === "approved") {
139
+ return enterKey();
140
+ }
141
+ if (scenarioState.trustOutcome === "denied") {
142
+ return escapeKey();
143
+ }
144
+ return void 0;
145
+ }
146
+ },
147
+ {
148
+ skipIf: () => scenarioState.trustOutcome !== "approved",
149
+ waitFor: isPostTrustTerminalState,
150
+ waitForTimeoutMs: STARTUP_READY_TIMEOUT_MS,
151
+ optional: true
152
+ },
153
+ {
154
+ write: (snapshot2) => {
155
+ const readyForUsage = isReady(snapshot2) && !isInteractionBlocked(snapshot2);
156
+ if (scenarioState.trustOutcome === "approved" && readyForUsage) {
157
+ scenarioState.reachedReadyAfterTrust = true;
158
+ }
159
+ scenarioState.canEnterUsage = scenarioState.trustOutcome !== "denied" && scenarioState.trustOutcome !== "required" && readyForUsage;
160
+ return void 0;
161
+ }
162
+ },
163
+ ...[..."/usage"].map((character) => guardedUsageWrite(character, scenarioState, 25)),
164
+ guardedUsageWrite(enterKey(), scenarioState, 250),
165
+ {
166
+ skipIf: () => !scenarioState.canEnterUsage,
167
+ waitFor: createStableUsagePanelWait(),
168
+ waitForTimeoutMs: 15e3,
169
+ optional: true,
170
+ capture: "usage",
171
+ captureWaitMs: 0
172
+ },
173
+ guardedUsageWrite(escapeKey(), scenarioState, 250)
174
+ ]
175
+ });
176
+ const buildError = (message, code) => {
177
+ const error = code == null ? new Error(message) : new UsageExtractionError(code, message);
178
+ if (ptyResult.debug.length > 0) {
179
+ Object.assign(error, { debug: ptyResult.debug });
180
+ }
181
+ return error;
182
+ };
183
+ const trustSubject = launchCwd.managed ? "managed usage directory" : "project directory";
184
+ if (scenarioState.trustOutcome === "required") {
185
+ throw buildError(
186
+ `Antigravity needs permission to trust the ${trustSubject} at ${launchCwd.path}. Re-run usage in an interactive terminal to review this request.`,
187
+ "trust_required"
188
+ );
189
+ }
190
+ if (scenarioState.trustOutcome === "denied") {
191
+ throw buildError(
192
+ `Antigravity trust was declined for the ${trustSubject} at ${launchCwd.path}.`,
193
+ "trust_denied"
194
+ );
195
+ }
196
+ if (isCurrentSignInFailure(ptyResult)) {
197
+ throw buildError(`Antigravity is not signed in. Run \`${command}\` and complete the login.`);
198
+ }
199
+ if (scenarioState.trustOutcome === "approved" && !scenarioState.reachedReadyAfterTrust) {
200
+ throw buildError(
201
+ `Antigravity did not accept trust for the ${trustSubject} at ${launchCwd.path}. Run \`${command}\` there once, accept the trust prompt, then re-run usage.`,
202
+ "trust_acceptance_failed"
203
+ );
204
+ }
205
+ if (isCurrentTrustDialog(ptyResult)) {
206
+ throw buildError(
207
+ `Antigravity still requires trust for the ${trustSubject} at ${launchCwd.path}.`,
208
+ "trust_required"
209
+ );
210
+ }
211
+ const snapshot = ptyResult.snapshots.usage ?? ptyResult;
212
+ const cleanedOutput = cleanControlOutput(snapshot.raw);
213
+ if (isCurrentSignInFailure(snapshot)) {
214
+ throw buildError(`Antigravity is not signed in. Run \`${command}\` and complete the login.`);
215
+ }
216
+ const groups = parseAgyUsage(snapshot.screen, cleanedOutput);
217
+ if (groups.length === 0) {
218
+ throw buildError("Antigravity /usage output did not include Models & Quota limit groups.");
219
+ }
220
+ return {
221
+ targetId: context.targetId,
222
+ displayName: context.displayName,
223
+ command,
224
+ limits: groups.map((group) => {
225
+ const percentRemaining = group.disabled || group.percentRemaining == null ? null : clampPercent(group.percentRemaining);
226
+ const limit = makeUsageLimit({
227
+ targetId: context.targetId,
228
+ scope: usageScope(group.heading),
229
+ window: "weekly",
230
+ label: titleCase(group.heading),
231
+ percentUsed: percentRemaining == null ? null : 100 - percentRemaining,
232
+ percentRemaining,
233
+ remainingText: group.disabled ? "Disabled" : null,
234
+ resetText: group.resetText,
235
+ raw: group.raw,
236
+ now: context.now
237
+ });
238
+ return {
239
+ ...limit,
240
+ resetAt: parseAgyRefreshResetAt(group.resetText, context.now) ?? limit.resetAt
241
+ };
242
+ }),
243
+ debug: ptyResult.debug.length > 0 ? ptyResult.debug : void 0
244
+ };
245
+ }
246
+ async function ensureAgyUsageCwd(homeDir, repoRoot) {
247
+ const fallbackDir = path.join(homeDir, ...AGY_USAGE_FALLBACK_PATH);
248
+ try {
249
+ await mkdir(fallbackDir, { recursive: true });
250
+ return { path: fallbackDir, managed: true };
251
+ } catch {
252
+ return { path: repoRoot, managed: false };
253
+ }
254
+ }
255
+ function parseAgyUsage(screen, cleanedOutput = "") {
256
+ const fromRaw = parseAgyUsageLines(compactLines(cleanedOutput));
257
+ const fromScreen = parseAgyUsageLines(compactLines(screen));
258
+ const merged = /* @__PURE__ */ new Map();
259
+ for (const group of [...fromRaw, ...fromScreen]) {
260
+ merged.set(usageGroupKey(group), group);
261
+ }
262
+ return [...merged.values()];
263
+ }
264
+ function usageGroupKey(group) {
265
+ return `${group.heading.trim().toLowerCase()}\0${group.limitLabel.trim().toLowerCase()}`;
266
+ }
267
+ function usageGroupSignature(groups) {
268
+ return JSON.stringify(
269
+ groups.map((group) => ({
270
+ heading: group.heading,
271
+ models: group.models,
272
+ limitLabel: group.limitLabel,
273
+ percentRemaining: group.percentRemaining,
274
+ resetText: group.resetText,
275
+ disabled: group.disabled
276
+ }))
277
+ );
278
+ }
279
+ function parseAgyUsageLines(lines) {
280
+ const groups = [];
281
+ let index = 0;
282
+ while (index < lines.length) {
283
+ const heading = lines[index] ?? "";
284
+ if (!isUsageGroupHeading(heading)) {
285
+ index += 1;
286
+ continue;
287
+ }
288
+ const rawLines = [heading];
289
+ let models = null;
290
+ let limitLabel = "";
291
+ let percentRemaining = null;
292
+ let resetText = null;
293
+ let disabled = false;
294
+ index += 1;
295
+ while (index < lines.length && !isUsageGroupHeading(lines[index] ?? "")) {
296
+ const line = lines[index] ?? "";
297
+ rawLines.push(line);
298
+ const modelsMatch = MODELS_LINE_PATTERN.exec(line);
299
+ if (modelsMatch?.[1]) {
300
+ models = modelsMatch[1].trim();
301
+ }
302
+ if (LIMIT_LABEL_PATTERN.test(line)) {
303
+ limitLabel = line.trim();
304
+ }
305
+ const linePercentRemaining = parseAgyRemainingPercent(line);
306
+ if (linePercentRemaining != null && percentRemaining == null) {
307
+ percentRemaining = linePercentRemaining;
308
+ }
309
+ const lineResetText = parseAgyRefreshText(line);
310
+ if (lineResetText != null) {
311
+ resetText = lineResetText;
312
+ }
313
+ if (DISABLED_PATTERN.test(line)) {
314
+ disabled = true;
315
+ }
316
+ index += 1;
317
+ }
318
+ if (limitLabel && (percentRemaining != null || resetText != null || disabled)) {
319
+ groups.push({
320
+ heading,
321
+ models,
322
+ limitLabel,
323
+ percentRemaining,
324
+ resetText,
325
+ disabled,
326
+ raw: rawLines.join("\n")
327
+ });
328
+ }
329
+ }
330
+ return groups;
331
+ }
332
+ function isUsageGroupHeading(line) {
333
+ if (!USAGE_GROUP_HEADING_PATTERN.test(line)) {
334
+ return false;
335
+ }
336
+ return !/^(MODELS|WEEKLY|MONTHLY|DAILY|ACCOUNT)$/i.test(line);
337
+ }
338
+ function parseAgyRemainingPercent(line) {
339
+ return parsePercentRemaining(line) ?? parseStandalonePercent(line);
340
+ }
341
+ function parseStandalonePercent(line) {
342
+ const match = /(?:^|\])\s*(\d+(?:\.\d+)?)\s*%\s*$/i.exec(line);
343
+ if (!match?.[1]) {
344
+ return null;
345
+ }
346
+ return Number(match[1]);
347
+ }
348
+ function parseAgyRefreshText(line) {
349
+ const match = /(Refreshes\s+in\s+(?:(?:\d+)h)?\s*(?:(?:\d+)m)?)/i.exec(line);
350
+ return match?.[1]?.trim() ?? null;
351
+ }
352
+ function parseAgyRefreshResetAt(resetText, now) {
353
+ if (resetText == null) {
354
+ return null;
355
+ }
356
+ const match = REFRESH_PATTERN.exec(resetText);
357
+ if (match == null) {
358
+ return null;
359
+ }
360
+ const hours = Number(match[1] ?? 0);
361
+ const minutes = Number(match[2] ?? 0);
362
+ if (!Number.isFinite(hours) || !Number.isFinite(minutes) || hours + minutes <= 0) {
363
+ return null;
364
+ }
365
+ return new Date(now.getTime() + hours * 36e5 + minutes * 6e4).toISOString();
366
+ }
367
+ function clampPercent(value) {
368
+ return Math.max(0, Math.min(100, value));
369
+ }
370
+ function usageScope(heading) {
371
+ return heading.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
372
+ }
373
+ function titleCase(value) {
374
+ return value.trim().toLowerCase().split(/\s+/).map((word) => word === "gpt" ? "GPT" : `${word.charAt(0).toUpperCase()}${word.slice(1)}`).join(" ");
375
+ }
376
+ export {
377
+ extractAgyUsage,
378
+ parseAgyUsage
379
+ };
@@ -3,8 +3,8 @@ import { readFile } from "node:fs/promises";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { promisify } from "node:util";
6
- import { c as cleanControlOutput, a as compactLines, p as parsePercentUsed, m as makeUsageLimit } from "./cli.js";
7
- import { r as runPtyScenario, e as enterKey, a as escapeKey } from "./pty-CZBSAJzE.js";
6
+ import { c as cleanControlOutput, a as compactLines, b as parsePercentUsed, m as makeUsageLimit } from "./cli.js";
7
+ import { r as runPtyScenario, e as enterKey, a as escapeKey } from "./pty-D8BFQQQg.js";
8
8
  const execFileAsync = promisify(execFile);
9
9
  const CLAUDE_CODE_KEYCHAIN_SERVICE = "Claude Code-credentials";
10
10
  const CLAUDE_CODE_CREDENTIALS_PATH = [".claude", ".credentials.json"];