omniagent 0.1.14 → 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
@@ -94,9 +94,11 @@ is not supported for usage extraction yet. Usage extraction may launch agent TUI
94
94
  cost if an agent reads repo context or instructions on startup; omniagent uses cheap/minimal
95
95
  launch settings where possible. Usage extraction times out after 30 seconds unless the target
96
96
  config defines a target-specific timeout; built-in TUI probes may use longer defaults. Some CLIs
97
- gate usage inspection behind onboarding state Antigravity requires the project to be trusted
98
- (run `agy` once and accept the trust prompt); omniagent does not complete auth or onboarding
99
- prompts for you. Pass `--timeout=<seconds>` to override the per-agent timeout for the current
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
100
102
  run.
101
103
 
102
104
  ## Local Overrides (`.local`)
@@ -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, a as enterKey, e as escapeKey } from "./pty-CMd3Wxfe.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"];
package/dist/cli.js CHANGED
@@ -1332,13 +1332,13 @@ const agyTarget = {
1332
1332
  }
1333
1333
  },
1334
1334
  usage: {
1335
- windows: ["credits"],
1335
+ windows: ["weekly"],
1336
1336
  launch: {
1337
1337
  command: "agy",
1338
1338
  timeoutMs: 7e4
1339
1339
  },
1340
1340
  extract: async (context) => {
1341
- const { extractAgyUsage } = await import("./agy-Clt40QYQ.js");
1341
+ const { extractAgyUsage } = await import("./agy-DlbWeyP_.js");
1342
1342
  return extractAgyUsage(context);
1343
1343
  }
1344
1344
  }
@@ -1399,7 +1399,7 @@ const claudeTarget = {
1399
1399
  timeoutMs: 6e4
1400
1400
  },
1401
1401
  extract: async (context) => {
1402
- const { extractClaudeUsage } = await import("./claude-BuXp9FHS.js");
1402
+ const { extractClaudeUsage } = await import("./claude-CcxRb8OW.js");
1403
1403
  return extractClaudeUsage(context);
1404
1404
  }
1405
1405
  }
@@ -1484,7 +1484,7 @@ const codexTarget = {
1484
1484
  timeoutMs: 6e4
1485
1485
  },
1486
1486
  extract: async (context) => {
1487
- const { extractCodexUsage } = await import("./codex-OCvtxDjw.js");
1487
+ const { extractCodexUsage } = await import("./codex-DhlBosXm.js");
1488
1488
  return extractCodexUsage(context);
1489
1489
  }
1490
1490
  }
@@ -10701,6 +10701,13 @@ function emptyToNull(value) {
10701
10701
  function escapeRegExp(value) {
10702
10702
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10703
10703
  }
10704
+ class UsageExtractionError extends Error {
10705
+ constructor(code, message) {
10706
+ super(message);
10707
+ this.code = code;
10708
+ this.name = "UsageExtractionError";
10709
+ }
10710
+ }
10704
10711
  const USAGE_BAR_WIDTH = 12;
10705
10712
  const ANSI = {
10706
10713
  reset: "\x1B[0m",
@@ -10735,6 +10742,12 @@ class UsageExtractionTimeoutError extends Error {
10735
10742
  this.timeoutMs = timeoutMs;
10736
10743
  }
10737
10744
  }
10745
+ class UsageCommandInterruptedError extends Error {
10746
+ constructor() {
10747
+ super("Usage cancelled.");
10748
+ this.name = "UsageCommandInterruptedError";
10749
+ }
10750
+ }
10738
10751
  function normalizeOptionalWindow(value) {
10739
10752
  if (value == null) {
10740
10753
  return null;
@@ -10805,6 +10818,68 @@ function uniqueNormalizedWindows(values) {
10805
10818
  }
10806
10819
  return result;
10807
10820
  }
10821
+ class UsageConfirmationPrompter {
10822
+ rl = null;
10823
+ queue = Promise.resolve();
10824
+ closed = false;
10825
+ interrupt;
10826
+ constructor(interrupt) {
10827
+ this.interrupt = interrupt;
10828
+ }
10829
+ confirm = (request, signal) => {
10830
+ const confirmation = this.queue.then(() => this.ask(request, signal));
10831
+ this.queue = confirmation.then(
10832
+ () => void 0,
10833
+ () => void 0
10834
+ );
10835
+ return confirmation;
10836
+ };
10837
+ close() {
10838
+ this.closed = true;
10839
+ this.rl?.close();
10840
+ this.rl = null;
10841
+ }
10842
+ async ask(request, signal) {
10843
+ if (this.closed) {
10844
+ throw new Error("Usage confirmation is no longer available.");
10845
+ }
10846
+ if (signal.aborted) {
10847
+ throw signal.reason instanceof Error ? signal.reason : new Error("Usage confirmation was cancelled.");
10848
+ }
10849
+ const scope = request.managed ? "managed usage directory" : "project directory";
10850
+ const question = `${request.displayName} needs to trust this ${scope}:
10851
+ ${request.path}
10852
+
10853
+ Allow this? [y/N] `;
10854
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
10855
+ this.rl = rl;
10856
+ const handleInterrupt = () => this.interrupt();
10857
+ rl.once("SIGINT", handleInterrupt);
10858
+ try {
10859
+ while (true) {
10860
+ const answer = (await rl.question(question, { signal })).trim().toLowerCase();
10861
+ if (!answer || answer === "n" || answer === "no") {
10862
+ return false;
10863
+ }
10864
+ if (answer === "y" || answer === "yes") {
10865
+ return true;
10866
+ }
10867
+ console.error("Please enter yes or no.");
10868
+ }
10869
+ } catch (error) {
10870
+ if (signal.aborted && signal.reason instanceof Error) {
10871
+ throw signal.reason;
10872
+ }
10873
+ throw error;
10874
+ } finally {
10875
+ rl.removeListener("SIGINT", handleInterrupt);
10876
+ rl.close();
10877
+ if (this.rl === rl) {
10878
+ this.rl = null;
10879
+ }
10880
+ }
10881
+ }
10882
+ }
10808
10883
  function formatUsageTargetLabel(targets) {
10809
10884
  return buildSupportedTargetLabel(targets);
10810
10885
  }
@@ -10815,6 +10890,13 @@ function formatSupportedUsageTargetsMessage(targets) {
10815
10890
  function getUsageCommand(target) {
10816
10891
  return target.usage?.launch?.command;
10817
10892
  }
10893
+ function supportsUsageConfirmation(target) {
10894
+ if (target.id.trim().toLowerCase() === "agy") {
10895
+ return true;
10896
+ }
10897
+ const builtInAgyExtractor = BUILTIN_TARGETS.find((candidate) => candidate.id === "agy")?.usage?.extract;
10898
+ return builtInAgyExtractor != null && target.usage?.extract === builtInAgyExtractor;
10899
+ }
10818
10900
  async function checkUsageCommandAvailability(target) {
10819
10901
  const command = getUsageCommand(target)?.trim();
10820
10902
  if (!command) {
@@ -10872,6 +10954,7 @@ function validateCodexCommand(candidate) {
10872
10954
  }
10873
10955
  function buildContext(options) {
10874
10956
  const windows = uniqueNormalizedWindows(options.target.usage?.windows ?? []);
10957
+ const confirm = options.confirm;
10875
10958
  const launch = {
10876
10959
  ...options.target.usage?.launch ?? {},
10877
10960
  timeoutMs: options.timeoutMs
@@ -10888,6 +10971,7 @@ function buildContext(options) {
10888
10971
  homeDir: options.homeDir,
10889
10972
  launch,
10890
10973
  signal: options.signal,
10974
+ confirm: confirm == null ? void 0 : (request) => confirm(request, options.signal),
10891
10975
  debug: {
10892
10976
  enabled: options.debug,
10893
10977
  includeRawOutput: options.debug,
@@ -10901,7 +10985,11 @@ function filterTargetResult(target, result, selectedWindow) {
10901
10985
  window: normalizeUsageWindow(limit.window)
10902
10986
  }));
10903
10987
  const filteredLimits = selectedWindow == null ? normalizedLimits : normalizedLimits.filter((limit) => limit.window === selectedWindow);
10904
- const notes = selectedWindow != null && filteredLimits.length === 0 ? [`${target.displayName} reported no usage rows for window "${selectedWindow}".`] : [];
10988
+ const resultNotes = result.notes ?? [];
10989
+ const notes = selectedWindow != null && filteredLimits.length === 0 ? [
10990
+ ...resultNotes,
10991
+ `${target.displayName} reported no usage rows for window "${selectedWindow}".`
10992
+ ] : resultNotes;
10905
10993
  return {
10906
10994
  result: {
10907
10995
  targetId: result.targetId,
@@ -10936,10 +11024,14 @@ async function extractUsageForTarget(options) {
10936
11024
  debug: []
10937
11025
  };
10938
11026
  }
10939
- const result = await withUsageTimeout((signal) => {
10940
- const context = buildContext({ ...options, signal });
10941
- return extractor(context);
10942
- }, options.timeoutMs);
11027
+ const result = await withUsageTimeout(
11028
+ (signal) => {
11029
+ const context = buildContext({ ...options, signal });
11030
+ return extractor(context);
11031
+ },
11032
+ options.timeoutMs,
11033
+ options.commandSignal
11034
+ );
10943
11035
  const filtered = filterTargetResult(options.target, result, options.selectedWindow);
10944
11036
  return {
10945
11037
  status: "success",
@@ -10951,7 +11043,7 @@ async function extractUsageForTarget(options) {
10951
11043
  };
10952
11044
  } catch (error) {
10953
11045
  const message = error instanceof Error ? error.message : String(error);
10954
- const code = isUsageTimeoutError(error) ? "usage_extraction_timeout" : "usage_extraction_failed";
11046
+ const code = isUsageTimeoutError(error) ? "usage_extraction_timeout" : getUsageExtractionErrorCode(error);
10955
11047
  return {
10956
11048
  status: "error",
10957
11049
  target: options.target,
@@ -10960,6 +11052,21 @@ async function extractUsageForTarget(options) {
10960
11052
  };
10961
11053
  }
10962
11054
  }
11055
+ function getUsageExtractionErrorCode(error) {
11056
+ if (error instanceof UsageExtractionError) {
11057
+ return error.code;
11058
+ }
11059
+ if (error && typeof error === "object") {
11060
+ const code = error.code;
11061
+ if (typeof code === "string") {
11062
+ const normalized = code.trim();
11063
+ if (/^[a-z][a-z0-9_]*$/.test(normalized)) {
11064
+ return normalized;
11065
+ }
11066
+ }
11067
+ }
11068
+ return "usage_extraction_failed";
11069
+ }
10963
11070
  function isUsageTimeoutError(error) {
10964
11071
  if (error instanceof UsageExtractionTimeoutError) {
10965
11072
  return true;
@@ -10983,18 +11090,25 @@ function isDebugArtifact(value) {
10983
11090
  const artifact = value;
10984
11091
  return (artifact.type === "raw-output" || artifact.type === "screen-snapshot") && typeof artifact.label === "string";
10985
11092
  }
10986
- function withUsageTimeout(run, timeoutMs) {
11093
+ function withUsageTimeout(run, timeoutMs, commandSignal) {
10987
11094
  return new Promise((resolve, reject) => {
10988
11095
  const controller = new AbortController();
10989
11096
  let settled = false;
10990
11097
  let timeoutFired = false;
10991
- let timeout;
11098
+ let timeout = null;
11099
+ let removeCommandAbortListener = null;
11100
+ const cleanup = () => {
11101
+ if (timeout != null) {
11102
+ clearTimeout(timeout);
11103
+ }
11104
+ removeCommandAbortListener?.();
11105
+ };
10992
11106
  const settleResolve = (value) => {
10993
11107
  if (settled) {
10994
11108
  return;
10995
11109
  }
10996
11110
  settled = true;
10997
- clearTimeout(timeout);
11111
+ cleanup();
10998
11112
  resolve(value);
10999
11113
  };
11000
11114
  const settleReject = (error) => {
@@ -11002,7 +11116,7 @@ function withUsageTimeout(run, timeoutMs) {
11002
11116
  return;
11003
11117
  }
11004
11118
  settled = true;
11005
- clearTimeout(timeout);
11119
+ cleanup();
11006
11120
  reject(error);
11007
11121
  };
11008
11122
  timeout = setTimeout(() => {
@@ -11013,6 +11127,19 @@ function withUsageTimeout(run, timeoutMs) {
11013
11127
  settleReject(error);
11014
11128
  });
11015
11129
  }, timeoutMs);
11130
+ if (commandSignal) {
11131
+ const abortFromCommand = () => {
11132
+ const reason = commandSignal.reason instanceof Error ? commandSignal.reason : new UsageCommandInterruptedError();
11133
+ controller.abort(reason);
11134
+ settleReject(reason);
11135
+ };
11136
+ if (commandSignal.aborted) {
11137
+ abortFromCommand();
11138
+ return;
11139
+ }
11140
+ commandSignal.addEventListener("abort", abortFromCommand, { once: true });
11141
+ removeCommandAbortListener = () => commandSignal.removeEventListener("abort", abortFromCommand);
11142
+ }
11016
11143
  let promise;
11017
11144
  try {
11018
11145
  promise = run(controller.signal);
@@ -11631,9 +11758,17 @@ async function runUsageCommand(argv) {
11631
11758
  }
11632
11759
  }
11633
11760
  const now = /* @__PURE__ */ new Date();
11634
- const outcomes = await Promise.all(
11635
- selectedTargets.map(
11636
- (target) => extractUsageForTarget({
11761
+ const commandController = new AbortController();
11762
+ const confirmationPrompter = !jsonOutput && Boolean(process.stdin.isTTY) && Boolean(process.stderr.isTTY) ? new UsageConfirmationPrompter(() => {
11763
+ if (!commandController.signal.aborted) {
11764
+ commandController.abort(new UsageCommandInterruptedError());
11765
+ }
11766
+ }) : null;
11767
+ let outcomes;
11768
+ try {
11769
+ let confirmationTargetChain = Promise.resolve();
11770
+ const outcomePromises = selectedTargets.map((target) => {
11771
+ const extract = () => extractUsageForTarget({
11637
11772
  target,
11638
11773
  repoRoot,
11639
11774
  agentsDir,
@@ -11642,10 +11777,29 @@ async function runUsageCommand(argv) {
11642
11777
  timeoutMs: resolveTargetTimeoutMs(target, cliTimeoutMs),
11643
11778
  debug: debugOutput,
11644
11779
  now,
11645
- command: resolvedUsageCommands.get(target.id)
11646
- })
11647
- )
11648
- );
11780
+ command: resolvedUsageCommands.get(target.id),
11781
+ confirm: supportsUsageConfirmation(target) ? confirmationPrompter?.confirm : void 0,
11782
+ commandSignal: commandController.signal
11783
+ });
11784
+ if (confirmationPrompter == null || !supportsUsageConfirmation(target)) {
11785
+ return extract();
11786
+ }
11787
+ const outcome = confirmationTargetChain.then(extract);
11788
+ confirmationTargetChain = outcome.then(
11789
+ () => void 0,
11790
+ () => void 0
11791
+ );
11792
+ return outcome;
11793
+ });
11794
+ outcomes = await Promise.all(outcomePromises);
11795
+ } finally {
11796
+ confirmationPrompter?.close();
11797
+ }
11798
+ if (commandController.signal.reason instanceof UsageCommandInterruptedError) {
11799
+ console.error(commandController.signal.reason.message);
11800
+ process.exit(130);
11801
+ return null;
11802
+ }
11649
11803
  const targets = [];
11650
11804
  const errors = [];
11651
11805
  const notes = [];
@@ -11731,7 +11885,7 @@ const usageCommand = {
11731
11885
  type: "boolean",
11732
11886
  describe: "Print JSON and include extractor debug artifacts when available."
11733
11887
  }).epilog(
11734
- "Usage extraction may launch agent TUIs and may incur cost if an agent reads repo context on startup. omniagent uses cheap/minimal launch settings where possible."
11888
+ "Usage extraction may launch agent TUIs and may incur cost if an agent reads repo context on startup. Interactive runs may ask before forwarding directory trust; JSON, debug, and non-interactive runs never prompt."
11735
11889
  ),
11736
11890
  handler: async (argv) => {
11737
11891
  await runUsageCommand(argv);
@@ -13102,11 +13256,12 @@ if (!entry) {
13102
13256
  }
13103
13257
  }
13104
13258
  export {
13259
+ UsageExtractionError as U,
13105
13260
  compactLines as a,
13106
- parsePercentRemaining as b,
13261
+ parsePercentUsed as b,
13107
13262
  cleanControlOutput as c,
13108
13263
  parseResetText as d,
13109
13264
  makeUsageLimit as m,
13110
- parsePercentUsed as p,
13265
+ parsePercentRemaining as p,
13111
13266
  runCli
13112
13267
  };
@@ -1,7 +1,7 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { c as cleanControlOutput, b as parsePercentRemaining, m as makeUsageLimit, d as parseResetText } from "./cli.js";
4
- import { r as runPtyScenario, a as enterKey, t as typeTextSteps } from "./pty-CMd3Wxfe.js";
3
+ import { c as cleanControlOutput, p as parsePercentRemaining, m as makeUsageLimit, d as parseResetText } from "./cli.js";
4
+ import { r as runPtyScenario, e as enterKey, t as typeTextSteps } from "./pty-D8BFQQQg.js";
5
5
  const CODEX_WINDOWS = [
6
6
  ["main", "5h", "main5hLimit"],
7
7
  ["main", "weekly", "mainWeeklyLimit"],
@@ -78,17 +78,18 @@ async function runPtyScenario(options) {
78
78
  });
79
79
  const cancellationPromise = new Promise((_, reject) => {
80
80
  cancelScenario = (message) => {
81
- if (!exited) {
82
- timedOut = true;
83
- if (child) {
84
- safeKillPty(child);
85
- }
81
+ timedOut = true;
82
+ if (!exited && child) {
83
+ safeKillPty(child);
86
84
  }
87
85
  reject(buildScenarioError(message));
88
86
  };
89
87
  });
90
88
  cancellationPromise.catch(() => {
91
89
  });
90
+ timeout = setTimeout(() => {
91
+ cancelScenario?.(`PTY scenario timed out after ${formatDuration(timeoutMs)}.`);
92
+ }, timeoutMs);
92
93
  if (options.signal) {
93
94
  const abortHandler = () => {
94
95
  cancelScenario?.(formatAbortReason(options.signal, timeoutMs));
@@ -99,10 +100,6 @@ async function runPtyScenario(options) {
99
100
  options.signal.addEventListener("abort", abortHandler, { once: true });
100
101
  removeAbortListener = () => options.signal?.removeEventListener("abort", abortHandler);
101
102
  }
102
- } else {
103
- timeout = setTimeout(() => {
104
- cancelScenario?.(`PTY scenario timed out after ${formatDuration(timeoutMs)}.`);
105
- }, timeoutMs);
106
103
  }
107
104
  const withScenarioTimeout = async (promise) => Promise.race([promise, cancellationPromise]);
108
105
  const throwIfTimedOut = () => {
@@ -160,7 +157,13 @@ async function runPtyScenario(options) {
160
157
  }
161
158
  if (step.write != null) {
162
159
  throwIfTimedOut();
163
- child.write(step.write);
160
+ const write = typeof step.write === "function" ? await withScenarioTimeout(
161
+ Promise.resolve(step.write({ raw, screen: readScreen(terminal) }))
162
+ ) : step.write;
163
+ throwIfTimedOut();
164
+ if (write != null && !exited) {
165
+ child.write(write);
166
+ }
164
167
  }
165
168
  if (step.waitFor != null) {
166
169
  const matched = await withScenarioTimeout(
@@ -355,8 +358,8 @@ function sleep(ms) {
355
358
  return new Promise((resolve) => setTimeout(resolve, ms));
356
359
  }
357
360
  export {
358
- enterKey as a,
359
- escapeKey as e,
361
+ escapeKey as a,
362
+ enterKey as e,
360
363
  runPtyScenario as r,
361
364
  typeTextSteps as t
362
365
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omniagent",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "Unified agent configuration CLI that compiles canonical agent configs to multiple runtimes.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,138 +0,0 @@
1
- import { c as cleanControlOutput, m as makeUsageLimit, a as compactLines } from "./cli.js";
2
- import { r as runPtyScenario, t as typeTextSteps, e as escapeKey, a as enterKey } from "./pty-CMd3Wxfe.js";
3
- const TRUST_DIALOG_PATTERN = /Do you trust the contents of this project\?/i;
4
- const READY_PATTERN = /\?\s+for shortcuts/i;
5
- const REMAINING_CREDITS_PATTERN = /Remaining AI Credits:\s*(.+?)\s*$/i;
6
- const CREDITS_NOT_ENABLED_PATTERN = /AI Credits not enabled/i;
7
- const ACCOUNT_PLAN_PATTERN = /^\S+@\S+\.\S+\s+\((.+?)\)\s*$/;
8
- const STANDALONE_PLAN_PATTERN = /^\((.*(?:quota|plan|tier).*)\)$/i;
9
- const SIGN_IN_PATTERN = /\b(?:not signed in|Signing in)\b/i;
10
- function isTrustDialog(snapshot) {
11
- return TRUST_DIALOG_PATTERN.test(snapshot.raw) || TRUST_DIALOG_PATTERN.test(snapshot.screen);
12
- }
13
- function isReadyOrTrustDialog(snapshot) {
14
- return READY_PATTERN.test(snapshot.screen) || isTrustDialog(snapshot);
15
- }
16
- function hasCreditsPanel(snapshot) {
17
- return REMAINING_CREDITS_PATTERN.test(snapshot.screen) || REMAINING_CREDITS_PATTERN.test(cleanControlOutput(snapshot.raw));
18
- }
19
- function withTrustSkip(step) {
20
- return { ...step, skipIf: isTrustDialog, skipIfSource: "raw" };
21
- }
22
- async function extractAgyUsage(context) {
23
- const command = context.command ?? context.launch?.command ?? "agy";
24
- const ptyResult = await runPtyScenario({
25
- command,
26
- args: context.launch?.args ?? [],
27
- cwd: context.repoRoot,
28
- cols: 120,
29
- rows: 40,
30
- timeoutMs: context.launch?.timeoutMs ?? 7e4,
31
- signal: context.signal,
32
- debug: context.debug,
33
- steps: [
34
- { waitFor: isReadyOrTrustDialog, waitForTimeoutMs: 25e3 },
35
- ...typeTextSteps("/credits", 25).map(withTrustSkip),
36
- withTrustSkip({ waitMs: 250, write: enterKey() }),
37
- withTrustSkip({
38
- waitFor: hasCreditsPanel,
39
- waitForTimeoutMs: 15e3,
40
- optional: true,
41
- capture: "credits",
42
- captureWaitMs: 500
43
- }),
44
- { write: escapeKey(), waitMs: 250 }
45
- ]
46
- });
47
- const buildError = (message) => {
48
- const error = new Error(message);
49
- if (ptyResult.debug.length > 0) {
50
- Object.assign(error, { debug: ptyResult.debug });
51
- }
52
- return error;
53
- };
54
- if (isTrustDialog(ptyResult)) {
55
- throw buildError(
56
- `Antigravity has not trusted this project yet. Run \`${command}\` in ${context.repoRoot} once, accept the trust prompt, then re-run usage.`
57
- );
58
- }
59
- const snapshot = ptyResult.snapshots.credits ?? ptyResult;
60
- const cleanedOutput = cleanControlOutput(snapshot.raw);
61
- const parsed = parseAgyCredits(snapshot.screen, cleanedOutput);
62
- if (parsed.notEnabled) {
63
- throw buildError(
64
- "Antigravity AI Credits are not enabled for this account. Enable them via /settings in agy."
65
- );
66
- }
67
- if (parsed.remaining == null) {
68
- if (SIGN_IN_PATTERN.test(snapshot.screen) || SIGN_IN_PATTERN.test(cleanedOutput)) {
69
- throw buildError(`Antigravity is not signed in. Run \`${command}\` and complete the login.`);
70
- }
71
- throw buildError("Antigravity /credits output did not include a Remaining AI Credits value.");
72
- }
73
- return {
74
- targetId: context.targetId,
75
- displayName: context.displayName,
76
- command,
77
- limits: [
78
- // Credits are an absolute balance rather than a percentage window.
79
- makeUsageLimit({
80
- targetId: context.targetId,
81
- scope: "ai_credits",
82
- window: "credits",
83
- label: parsed.plan ? `AI Credits (${parsed.plan})` : "AI Credits",
84
- percentUsed: null,
85
- percentRemaining: null,
86
- remainingText: parsed.remaining,
87
- resetText: null,
88
- raw: parsed.rawLine,
89
- now: context.now
90
- })
91
- ],
92
- debug: ptyResult.debug.length > 0 ? ptyResult.debug : void 0
93
- };
94
- }
95
- function parseAgyCredits(screen, cleanedOutput = "") {
96
- const fromScreen = parseAgyCreditsLines(compactLines(screen));
97
- if (fromScreen.remaining != null || fromScreen.notEnabled) {
98
- return fromScreen;
99
- }
100
- const fromRaw = parseAgyCreditsLines(compactLines(cleanedOutput));
101
- if (fromRaw.remaining != null || fromRaw.notEnabled) {
102
- return fromRaw;
103
- }
104
- return fromScreen.plan != null ? fromScreen : fromRaw;
105
- }
106
- function parseAgyCreditsLines(lines) {
107
- const parsed = {
108
- remaining: null,
109
- notEnabled: false,
110
- plan: null,
111
- rawLine: ""
112
- };
113
- for (const line of lines) {
114
- const remainingMatch = REMAINING_CREDITS_PATTERN.exec(line);
115
- if (remainingMatch?.[1]) {
116
- const value = remainingMatch[1].trim();
117
- if (CREDITS_NOT_ENABLED_PATTERN.test(value)) {
118
- parsed.notEnabled = true;
119
- parsed.rawLine = line.trim();
120
- continue;
121
- }
122
- parsed.remaining = value;
123
- parsed.rawLine = line.trim();
124
- continue;
125
- }
126
- if (parsed.plan == null) {
127
- const planMatch = ACCOUNT_PLAN_PATTERN.exec(line) ?? STANDALONE_PLAN_PATTERN.exec(line);
128
- if (planMatch?.[1]) {
129
- parsed.plan = planMatch[1].trim();
130
- }
131
- }
132
- }
133
- return parsed;
134
- }
135
- export {
136
- extractAgyUsage,
137
- parseAgyCredits
138
- };