@phnx-labs/agents-cli 1.20.21 → 1.20.22
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/CHANGELOG.md +14 -0
- package/dist/commands/cloud.js +16 -7
- package/dist/commands/menubar.d.ts +10 -0
- package/dist/commands/menubar.js +83 -0
- package/dist/commands/routines.js +34 -1
- package/dist/commands/secrets.d.ts +1 -1
- package/dist/commands/secrets.js +95 -38
- package/dist/index.js +28 -3
- package/dist/lib/agents.js +8 -0
- package/dist/lib/cloud/antigravity.d.ts +70 -0
- package/dist/lib/cloud/antigravity.js +196 -0
- package/dist/lib/cloud/factory.d.ts +68 -18
- package/dist/lib/cloud/factory.js +269 -26
- package/dist/lib/cloud/registry.d.ts +18 -2
- package/dist/lib/cloud/registry.js +28 -4
- package/dist/lib/cloud/types.d.ts +32 -2
- package/dist/lib/menubar/install-menubar.d.ts +57 -0
- package/dist/lib/menubar/install-menubar.js +291 -0
- package/dist/lib/secrets/agent.d.ts +9 -1
- package/dist/lib/secrets/agent.js +91 -10
- package/dist/lib/secrets/bundles.d.ts +19 -12
- package/dist/lib/secrets/bundles.js +22 -14
- package/dist/lib/self-update.d.ts +34 -0
- package/dist/lib/self-update.js +63 -2
- package/dist/lib/types.d.ts +8 -0
- package/dist/lib/version.d.ts +11 -0
- package/dist/lib/version.js +20 -0
- package/package.json +3 -2
- package/scripts/postinstall.js +35 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity cloud provider — Google's Antigravity agent via the Gemini
|
|
3
|
+
* **Managed Agents Interactions API**.
|
|
4
|
+
*
|
|
5
|
+
* The `agy` CLI is local-only (`--print` / `--sandbox`); Antigravity's cloud
|
|
6
|
+
* surface is an HTTP endpoint that runs the Antigravity harness in a remote
|
|
7
|
+
* ephemeral Linux sandbox:
|
|
8
|
+
*
|
|
9
|
+
* POST https://generativelanguage.googleapis.com/v1beta/interactions
|
|
10
|
+
* x-goog-api-key: <GEMINI_API_KEY>
|
|
11
|
+
* { "agent": "antigravity-preview-05-2026", "input": "<prompt>", "environment": "remote" }
|
|
12
|
+
*
|
|
13
|
+
* The call is synchronous by default (the response carries `status` +
|
|
14
|
+
* `output_text`), so `dispatch()` awaits it, returns a terminal CloudTask, and
|
|
15
|
+
* `stream()` replays the buffered text + done events — same shape as the Factory
|
|
16
|
+
* provider. It is a raw sandbox: no GitHub repo → PR (that's Rush's job).
|
|
17
|
+
*
|
|
18
|
+
* Auth: the Gemini API key comes from an `agents secrets` bundle named in
|
|
19
|
+
* `cloud.providers.antigravity.secretsBundle` (never from agents.yaml), falling
|
|
20
|
+
* back to GEMINI_API_KEY / GOOGLE_API_KEY in the environment.
|
|
21
|
+
*/
|
|
22
|
+
import type { CloudProvider, CloudTask, CloudTaskStatus, CloudEvent, DispatchOptions, ProviderCapabilities } from './types.js';
|
|
23
|
+
/** Shape of the Interactions API response we consume (defensive: all optional). */
|
|
24
|
+
interface InteractionResponse {
|
|
25
|
+
id?: string;
|
|
26
|
+
interaction_id?: string;
|
|
27
|
+
environment_id?: string;
|
|
28
|
+
status?: string;
|
|
29
|
+
output_text?: string;
|
|
30
|
+
error?: {
|
|
31
|
+
message?: string;
|
|
32
|
+
} | string;
|
|
33
|
+
}
|
|
34
|
+
/** Map an Interactions API status string to the canonical CloudTaskStatus. */
|
|
35
|
+
export declare function mapStatus(s: string | undefined): CloudTaskStatus;
|
|
36
|
+
/** Build the Interactions API request body for a fresh dispatch. */
|
|
37
|
+
export declare function buildInteractionBody(prompt: string, model: string): Record<string, unknown>;
|
|
38
|
+
/** Parse an Interactions API response into a CloudTask (minus prompt/timestamps). */
|
|
39
|
+
export declare function parseInteraction(resp: InteractionResponse): {
|
|
40
|
+
id: string;
|
|
41
|
+
status: CloudTaskStatus;
|
|
42
|
+
summary?: string;
|
|
43
|
+
environmentId?: string;
|
|
44
|
+
};
|
|
45
|
+
export declare class AntigravityCloudProvider implements CloudProvider {
|
|
46
|
+
id: "antigravity";
|
|
47
|
+
name: string;
|
|
48
|
+
private secretsBundle?;
|
|
49
|
+
private model;
|
|
50
|
+
private runs;
|
|
51
|
+
constructor(config?: {
|
|
52
|
+
secretsBundle?: string;
|
|
53
|
+
model?: string;
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* True when a key *source* is configured. Cheap: never resolves the bundle
|
|
57
|
+
* (which could prompt for biometry) — that happens lazily at dispatch.
|
|
58
|
+
*/
|
|
59
|
+
private hasKeySource;
|
|
60
|
+
/** Resolve the Gemini API key from the configured bundle or the environment. */
|
|
61
|
+
private resolveApiKey;
|
|
62
|
+
capabilities(): ProviderCapabilities;
|
|
63
|
+
dispatch(options: DispatchOptions): Promise<CloudTask>;
|
|
64
|
+
status(taskId: string): Promise<CloudTask>;
|
|
65
|
+
list(): Promise<CloudTask[]>;
|
|
66
|
+
stream(taskId: string): AsyncIterable<CloudEvent>;
|
|
67
|
+
cancel(_taskId: string): Promise<void>;
|
|
68
|
+
message(_taskId: string, _content: string): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity cloud provider — Google's Antigravity agent via the Gemini
|
|
3
|
+
* **Managed Agents Interactions API**.
|
|
4
|
+
*
|
|
5
|
+
* The `agy` CLI is local-only (`--print` / `--sandbox`); Antigravity's cloud
|
|
6
|
+
* surface is an HTTP endpoint that runs the Antigravity harness in a remote
|
|
7
|
+
* ephemeral Linux sandbox:
|
|
8
|
+
*
|
|
9
|
+
* POST https://generativelanguage.googleapis.com/v1beta/interactions
|
|
10
|
+
* x-goog-api-key: <GEMINI_API_KEY>
|
|
11
|
+
* { "agent": "antigravity-preview-05-2026", "input": "<prompt>", "environment": "remote" }
|
|
12
|
+
*
|
|
13
|
+
* The call is synchronous by default (the response carries `status` +
|
|
14
|
+
* `output_text`), so `dispatch()` awaits it, returns a terminal CloudTask, and
|
|
15
|
+
* `stream()` replays the buffered text + done events — same shape as the Factory
|
|
16
|
+
* provider. It is a raw sandbox: no GitHub repo → PR (that's Rush's job).
|
|
17
|
+
*
|
|
18
|
+
* Auth: the Gemini API key comes from an `agents secrets` bundle named in
|
|
19
|
+
* `cloud.providers.antigravity.secretsBundle` (never from agents.yaml), falling
|
|
20
|
+
* back to GEMINI_API_KEY / GOOGLE_API_KEY in the environment.
|
|
21
|
+
*/
|
|
22
|
+
import { resolveDispatchRepos } from './types.js';
|
|
23
|
+
import { readAndResolveBundleEnv } from '../secrets/bundles.js';
|
|
24
|
+
const INTERACTIONS_URL = 'https://generativelanguage.googleapis.com/v1beta/interactions';
|
|
25
|
+
const DEFAULT_MODEL = 'antigravity-preview-05-2026';
|
|
26
|
+
const KEY_NAMES = ['GEMINI_API_KEY', 'GOOGLE_API_KEY'];
|
|
27
|
+
/** Map an Interactions API status string to the canonical CloudTaskStatus. */
|
|
28
|
+
export function mapStatus(s) {
|
|
29
|
+
const lower = (s ?? '').toLowerCase();
|
|
30
|
+
if (lower.includes('queue') || lower.includes('pending'))
|
|
31
|
+
return 'queued';
|
|
32
|
+
if (lower.includes('run') || lower.includes('progress'))
|
|
33
|
+
return 'running';
|
|
34
|
+
if (lower.includes('complete') || lower.includes('success'))
|
|
35
|
+
return 'completed';
|
|
36
|
+
if (lower.includes('fail') || lower.includes('error'))
|
|
37
|
+
return 'failed';
|
|
38
|
+
if (lower.includes('cancel'))
|
|
39
|
+
return 'cancelled';
|
|
40
|
+
return 'completed';
|
|
41
|
+
}
|
|
42
|
+
/** Build the Interactions API request body for a fresh dispatch. */
|
|
43
|
+
export function buildInteractionBody(prompt, model) {
|
|
44
|
+
return {
|
|
45
|
+
agent: model,
|
|
46
|
+
input: prompt,
|
|
47
|
+
environment: 'remote',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Parse an Interactions API response into a CloudTask (minus prompt/timestamps). */
|
|
51
|
+
export function parseInteraction(resp) {
|
|
52
|
+
const id = resp.id ?? resp.interaction_id ?? `antigravity-${Date.now()}`;
|
|
53
|
+
return {
|
|
54
|
+
id,
|
|
55
|
+
status: mapStatus(resp.status),
|
|
56
|
+
summary: resp.output_text,
|
|
57
|
+
environmentId: resp.environment_id,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export class AntigravityCloudProvider {
|
|
61
|
+
id = 'antigravity';
|
|
62
|
+
name = 'Antigravity (Gemini)';
|
|
63
|
+
secretsBundle;
|
|
64
|
+
model;
|
|
65
|
+
runs = new Map();
|
|
66
|
+
constructor(config) {
|
|
67
|
+
this.secretsBundle = config?.secretsBundle;
|
|
68
|
+
this.model = config?.model ?? DEFAULT_MODEL;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* True when a key *source* is configured. Cheap: never resolves the bundle
|
|
72
|
+
* (which could prompt for biometry) — that happens lazily at dispatch.
|
|
73
|
+
*/
|
|
74
|
+
hasKeySource() {
|
|
75
|
+
if (this.secretsBundle)
|
|
76
|
+
return true;
|
|
77
|
+
return KEY_NAMES.some((k) => Boolean(process.env[k]));
|
|
78
|
+
}
|
|
79
|
+
/** Resolve the Gemini API key from the configured bundle or the environment. */
|
|
80
|
+
resolveApiKey() {
|
|
81
|
+
if (this.secretsBundle) {
|
|
82
|
+
try {
|
|
83
|
+
const { env } = readAndResolveBundleEnv(this.secretsBundle, { caller: 'cloud:antigravity' });
|
|
84
|
+
for (const k of KEY_NAMES) {
|
|
85
|
+
if (env[k])
|
|
86
|
+
return env[k];
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`Secrets bundle '${this.secretsBundle}' has no ${KEY_NAMES.join(' or ')}. Add one: agents secrets add ${this.secretsBundle} GEMINI_API_KEY`);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
throw new Error(`Could not read Gemini API key from bundle '${this.secretsBundle}': ${err.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const k of KEY_NAMES) {
|
|
95
|
+
const v = process.env[k];
|
|
96
|
+
if (v)
|
|
97
|
+
return v;
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`Antigravity cloud needs a Gemini API key. Set cloud.providers.antigravity.secretsBundle in ~/.agents/agents.yaml, or export ${KEY_NAMES.join(' / ')}.`);
|
|
100
|
+
}
|
|
101
|
+
capabilities() {
|
|
102
|
+
const available = this.hasKeySource();
|
|
103
|
+
return {
|
|
104
|
+
available,
|
|
105
|
+
dispatch: available,
|
|
106
|
+
status: available,
|
|
107
|
+
list: available,
|
|
108
|
+
stream: available,
|
|
109
|
+
cancel: false,
|
|
110
|
+
message: false,
|
|
111
|
+
multiRepo: false,
|
|
112
|
+
skills: false,
|
|
113
|
+
images: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async dispatch(options) {
|
|
117
|
+
// The Interactions sandbox has no GitHub repo → PR flow. Reject repos
|
|
118
|
+
// loudly rather than silently ignoring them (point the user at Rush).
|
|
119
|
+
const repos = resolveDispatchRepos(options);
|
|
120
|
+
if (repos.length > 0) {
|
|
121
|
+
throw new Error(`Antigravity cloud is a raw sandbox with no GitHub repo → PR flow. Got repo(s): ${repos.join(', ')}. ` +
|
|
122
|
+
`Use --provider rush for repo-backed dispatch.`);
|
|
123
|
+
}
|
|
124
|
+
const apiKey = this.resolveApiKey();
|
|
125
|
+
const model = options.model ?? this.model;
|
|
126
|
+
const body = buildInteractionBody(options.prompt, model);
|
|
127
|
+
let resp;
|
|
128
|
+
try {
|
|
129
|
+
resp = await fetch(INTERACTIONS_URL, {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
headers: { 'content-type': 'application/json', 'x-goog-api-key': apiKey },
|
|
132
|
+
body: JSON.stringify(body),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
throw new Error(`Antigravity dispatch failed (network): ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
const text = await resp.text();
|
|
139
|
+
if (!resp.ok) {
|
|
140
|
+
throw new Error(`Antigravity dispatch failed (${resp.status}): ${text.slice(0, 500)}`);
|
|
141
|
+
}
|
|
142
|
+
let parsed;
|
|
143
|
+
try {
|
|
144
|
+
parsed = JSON.parse(text);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
throw new Error(`Antigravity returned non-JSON response: ${text.slice(0, 300)}`);
|
|
148
|
+
}
|
|
149
|
+
const { id, status, summary } = parseInteraction(parsed);
|
|
150
|
+
const now = new Date().toISOString();
|
|
151
|
+
const task = {
|
|
152
|
+
id,
|
|
153
|
+
provider: 'antigravity',
|
|
154
|
+
status,
|
|
155
|
+
agent: 'antigravity',
|
|
156
|
+
prompt: options.prompt,
|
|
157
|
+
summary,
|
|
158
|
+
createdAt: now,
|
|
159
|
+
updatedAt: now,
|
|
160
|
+
};
|
|
161
|
+
const events = [];
|
|
162
|
+
if (summary)
|
|
163
|
+
events.push({ type: 'text', content: summary, timestamp: now });
|
|
164
|
+
events.push({ type: 'done', status, summary, timestamp: now });
|
|
165
|
+
this.runs.set(id, { events, task });
|
|
166
|
+
return task;
|
|
167
|
+
}
|
|
168
|
+
async status(taskId) {
|
|
169
|
+
const run = this.runs.get(taskId);
|
|
170
|
+
if (run)
|
|
171
|
+
return run.task;
|
|
172
|
+
throw new Error(`No live status for Antigravity task ${taskId} (synchronous interaction; see local cache).`);
|
|
173
|
+
}
|
|
174
|
+
async list() {
|
|
175
|
+
return [...this.runs.values()].map((r) => r.task);
|
|
176
|
+
}
|
|
177
|
+
async *stream(taskId) {
|
|
178
|
+
const run = this.runs.get(taskId);
|
|
179
|
+
if (!run) {
|
|
180
|
+
yield {
|
|
181
|
+
type: 'error',
|
|
182
|
+
message: `Antigravity interaction ${taskId} is not buffered in this process. The interaction is synchronous — see 'agents cloud status ${taskId}' for the result.`,
|
|
183
|
+
timestamp: new Date().toISOString(),
|
|
184
|
+
};
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
for (const event of run.events)
|
|
188
|
+
yield event;
|
|
189
|
+
}
|
|
190
|
+
async cancel(_taskId) {
|
|
191
|
+
throw new Error('Cancel is not supported for Antigravity — interactions run synchronously to completion.');
|
|
192
|
+
}
|
|
193
|
+
async message(_taskId, _content) {
|
|
194
|
+
throw new Error('Follow-up messages are not yet supported for Antigravity cloud tasks.');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -1,31 +1,81 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Factory
|
|
2
|
+
* Factory (Droid) cloud provider.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Dispatches to a Factory **Droid Computer** — a persistent cloud VM (managed
|
|
5
|
+
* by Factory, or bring-your-own via `droid computer register`). There is no
|
|
6
|
+
* `droid cloud run`; remote execution = reach the computer over the Droid relay
|
|
7
|
+
* (`droid computer ssh <name>`) and run a headless `droid exec` there.
|
|
8
|
+
*
|
|
9
|
+
* `droid exec` is synchronous (it runs to completion and exits, unlike Codex
|
|
10
|
+
* Cloud which is async server-side). So `dispatch()` runs the remote exec to
|
|
11
|
+
* completion with `--output-format stream-json`, buffers the NDJSON events, and
|
|
12
|
+
* `stream()` replays them. The task id is droid's own `session_id` (captured
|
|
13
|
+
* from the run output), so it lines up with `droid exec -s <id>` for future
|
|
14
|
+
* resume support.
|
|
15
|
+
*
|
|
16
|
+
* Transport note: the exact relay SSH composition (user + ProxyCommand) is
|
|
17
|
+
* built in `buildSshArgs()` and must be confirmed against a live provisioned
|
|
18
|
+
* Droid Computer (Factory auth required). `capabilities().available` gates on
|
|
19
|
+
* the droid binary + a configured computer so the provider fails with a clear
|
|
20
|
+
* message rather than misfiring when unconfigured.
|
|
6
21
|
*/
|
|
7
|
-
import type { CloudProvider, CloudTask, CloudTaskStatus, CloudEvent, DispatchOptions, ProviderCapabilities } from './types.js';
|
|
22
|
+
import type { CloudProvider, CloudTask, CloudTaskStatus, CloudEvent, DispatchOptions, ProviderCapabilities, DroidAutonomy } from './types.js';
|
|
23
|
+
/** Locate the droid binary, checking agents-cli shims first then PATH. */
|
|
24
|
+
export declare function findDroidBinary(): string | null;
|
|
25
|
+
/** Normalize an autonomy value, falling back to the safe cloud default (`high`). */
|
|
26
|
+
export declare function resolveAutonomy(value: unknown, fallback?: DroidAutonomy): DroidAutonomy;
|
|
8
27
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
|
|
12
|
-
|
|
28
|
+
* Build the remote `droid exec` argv. Headless, stream-json output, given
|
|
29
|
+
* autonomy. `sessionId` (when resuming) maps to `-s`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildExecArgs(prompt: string, opts: {
|
|
32
|
+
autonomy: DroidAutonomy;
|
|
33
|
+
sessionId?: string;
|
|
34
|
+
}): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Build the `ssh` argv that runs a remote command on a Droid Computer through
|
|
37
|
+
* the Droid relay. The relay is used as an OpenSSH ProxyCommand
|
|
38
|
+
* (`droid computer ssh <name> --proxy`), so the connection rides Factory's
|
|
39
|
+
* brokered tunnel rather than a directly reachable host.
|
|
13
40
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
41
|
+
* `remoteArgv` is the already-built remote command (e.g. droid exec argv); it is
|
|
42
|
+
* shell-quoted into a single remote command string.
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildSshArgs(computer: string, remoteBin: string, remoteArgv: string[], opts: {
|
|
45
|
+
droidBin: string;
|
|
46
|
+
user: string;
|
|
47
|
+
port?: string;
|
|
48
|
+
}): string[];
|
|
49
|
+
/** Map a droid stream-json `result.subtype` / `is_error` to a CloudTaskStatus. */
|
|
50
|
+
export declare function mapResultStatus(line: {
|
|
51
|
+
is_error?: boolean;
|
|
52
|
+
subtype?: string;
|
|
53
|
+
}): CloudTaskStatus;
|
|
54
|
+
/**
|
|
55
|
+
* Map one parsed droid stream-json event to a CloudEvent. The stream-json
|
|
56
|
+
* schema is only partially documented, so this is defensive: known shapes map
|
|
57
|
+
* to typed events, everything else surfaces as `unknown` rather than being
|
|
58
|
+
* dropped (mirrors the rest of the cloud event pipeline).
|
|
18
59
|
*/
|
|
60
|
+
export declare function mapDroidEvent(obj: Record<string, unknown>): CloudEvent;
|
|
19
61
|
export declare class FactoryCloudProvider implements CloudProvider {
|
|
20
62
|
id: "factory";
|
|
21
63
|
name: string;
|
|
64
|
+
private defaultComputer?;
|
|
65
|
+
private defaultAutonomy;
|
|
66
|
+
/** session_id → buffered run, populated by dispatch, drained by stream. */
|
|
67
|
+
private runs;
|
|
68
|
+
constructor(config?: {
|
|
69
|
+
computer?: string;
|
|
70
|
+
autonomy?: DroidAutonomy;
|
|
71
|
+
});
|
|
22
72
|
capabilities(): ProviderCapabilities;
|
|
23
|
-
dispatch(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
stream(
|
|
73
|
+
dispatch(options: DispatchOptions): Promise<CloudTask>;
|
|
74
|
+
/** Run the remote droid exec to completion, collecting events + final result. */
|
|
75
|
+
private runRemote;
|
|
76
|
+
status(taskId: string): Promise<CloudTask>;
|
|
77
|
+
list(): Promise<CloudTask[]>;
|
|
78
|
+
stream(taskId: string): AsyncIterable<CloudEvent>;
|
|
29
79
|
cancel(_taskId: string): Promise<void>;
|
|
30
80
|
message(_taskId: string, _content: string): Promise<void>;
|
|
31
81
|
}
|