agy-paperclip-adapter 1.0.0

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 ADDED
@@ -0,0 +1,43 @@
1
+ # Antigravity Paperclip Adapter (`agy-paperclip-adapter`)
2
+
3
+ An external adapter plugin for [Paperclip](https://github.com/paperclipai/paperclip) that integrates the Google Antigravity (`agy`) local CLI agent runtime.
4
+
5
+ ## Features
6
+
7
+ - **Full Model Selection**: Supports all Antigravity models (`gemini-3.8-flash-high`, `gemini-3.7-flash-high`, `gemini-3.6-flash-high`, `gemini-3.1-pro-high`, `claude-sonnet-4-6`, `claude-opus-4-6-thinking`, `gpt-oss-120b-medium`, `auto`).
8
+ - **Adapter Icon**: Includes native SVG branding icon for Paperclip's UI.
9
+ - **Paperclip Skills to AGY Sync**: Automatically materializes skills assigned in Paperclip into `${workspace}/.agents/skills/<skill-name>/SKILL.md` for AGY agent discovery.
10
+ - **Multi-Agent Isolation**: Allows hiring multiple agents with distinct roles, instructions, and skills on a single local `agy` runtime via isolated conversation sessions (`--conversation`) and working directories.
11
+ - **UI Transcript Parser**: Self-contained streaming parser for tool calls, thinking steps, system logs, and assistant markdown output.
12
+ - **Package Import Ready (`npm run pack`)**: Easily build and package into `dist/agy-paperclip-adapter-1.0.0.tgz` for importing via Paperclip UI or CLI.
13
+
14
+ ## Quick Start & Packing
15
+
16
+ To build and generate the importable tarball in `dist/`:
17
+
18
+ ```bash
19
+ npm run pack
20
+ # Output: dist/agy-paperclip-adapter-1.0.0.tgz
21
+ ```
22
+
23
+ ## Installation into Paperclip
24
+
25
+ ### Option 1: Via UI (Custom Adapter Import)
26
+ Upload `dist/agy-paperclip-adapter-1.0.0.tgz` in the Paperclip UI under **Settings → Adapters → Install Custom Adapter**.
27
+
28
+ ### Option 2: Via Paperclip CLI
29
+ ```bash
30
+ paperclipai adapter install --payload-json "{\"packageName\":\"$PWD/dist/agy-paperclip-adapter-1.0.0.tgz\"}"
31
+ ```
32
+
33
+ ### Option 3: Local Symlink Development
34
+ ```bash
35
+ curl -X POST http://localhost:3100/api/adapters \
36
+ -H "Authorization: Bearer <PAPERCLIP_API_KEY>" \
37
+ -H "Content-Type: application/json" \
38
+ -d "{\"localPath\":\"$PWD\"}"
39
+ ```
40
+
41
+ ## License
42
+
43
+ MIT
package/dist/icon.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare const iconSvg = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 2L2 19h20L12 2z\" stroke=\"#4285F4\" stroke-width=\"2\"/>\n <ellipse cx=\"12\" cy=\"14\" rx=\"8\" ry=\"3\" stroke=\"#A142F4\" stroke-width=\"1.5\" stroke-dasharray=\"3 2\"/>\n <circle cx=\"12\" cy=\"10\" r=\"2\" fill=\"#4285F4\"/>\n</svg>";
2
+ export declare const icon = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" width=\"24\" height=\"24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.75\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M12 2L2 19h20L12 2z\" stroke=\"#4285F4\" stroke-width=\"2\"/>\n <ellipse cx=\"12\" cy=\"14\" rx=\"8\" ry=\"3\" stroke=\"#A142F4\" stroke-width=\"1.5\" stroke-dasharray=\"3 2\"/>\n <circle cx=\"12\" cy=\"10\" r=\"2\" fill=\"#4285F4\"/>\n</svg>";
package/dist/icon.js ADDED
@@ -0,0 +1,6 @@
1
+ export const iconSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M12 2L2 19h20L12 2z" stroke="#4285F4" stroke-width="2"/>
3
+ <ellipse cx="12" cy="14" rx="8" ry="3" stroke="#A142F4" stroke-width="1.5" stroke-dasharray="3 2"/>
4
+ <circle cx="12" cy="10" r="2" fill="#4285F4"/>
5
+ </svg>`;
6
+ export const icon = iconSvg;
@@ -0,0 +1,6 @@
1
+ export declare const type = "antigravity";
2
+ export declare const label = "Antigravity";
3
+ export { icon, iconSvg } from "./icon.js";
4
+ export { models, DEFAULT_ANTIGRAVITY_MODEL } from "./models.js";
5
+ export { parseStdoutLine } from "./ui-parser.js";
6
+ export { agentConfigurationDoc, createServerAdapter, sessionCodec, getConfigSchema, } from "./server/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export const type = "antigravity";
2
+ export const label = "Antigravity";
3
+ export { icon, iconSvg } from "./icon.js";
4
+ export { models, DEFAULT_ANTIGRAVITY_MODEL } from "./models.js";
5
+ export { parseStdoutLine } from "./ui-parser.js";
6
+ export { agentConfigurationDoc, createServerAdapter, sessionCodec, getConfigSchema, } from "./server/index.js";
@@ -0,0 +1,5 @@
1
+ export declare const DEFAULT_ANTIGRAVITY_MODEL = "gemini-3.8-flash-high";
2
+ export declare const models: {
3
+ id: string;
4
+ label: string;
5
+ }[];
package/dist/models.js ADDED
@@ -0,0 +1,18 @@
1
+ export const DEFAULT_ANTIGRAVITY_MODEL = "gemini-3.8-flash-high";
2
+ export const models = [
3
+ { id: "gemini-3.8-flash-high", label: "Gemini 3.8 Flash (High)" },
4
+ { id: "gemini-3.8-flash-medium", label: "Gemini 3.8 Flash (Medium)" },
5
+ { id: "gemini-3.8-flash-low", label: "Gemini 3.8 Flash (Low)" },
6
+ { id: "gemini-3.7-flash-high", label: "Gemini 3.7 Flash (High)" },
7
+ { id: "gemini-3.7-flash-medium", label: "Gemini 3.7 Flash (Medium)" },
8
+ { id: "gemini-3.7-flash-low", label: "Gemini 3.7 Flash (Low)" },
9
+ { id: "gemini-3.6-flash-high", label: "Gemini 3.6 Flash (High)" },
10
+ { id: "gemini-3.6-flash-medium", label: "Gemini 3.6 Flash (Medium)" },
11
+ { id: "gemini-3.6-flash-low", label: "Gemini 3.6 Flash (Low)" },
12
+ { id: "gemini-3.1-pro-high", label: "Gemini 3.1 Pro (High)" },
13
+ { id: "gemini-3.1-pro-low", label: "Gemini 3.1 Pro (Low)" },
14
+ { id: "claude-sonnet-4-6", label: "Claude Sonnet 4.6 (Thinking)" },
15
+ { id: "claude-opus-4-6-thinking", label: "Claude Opus 4.6 (Thinking)" },
16
+ { id: "gpt-oss-120b-medium", label: "GPT-OSS 120B (Medium)" },
17
+ { id: "auto", label: "Auto" },
18
+ ];
@@ -0,0 +1,2 @@
1
+ import type { AdapterExecutionContext, AdapterExecutionResult } from "@paperclipai/adapter-utils";
2
+ export declare function execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult>;
@@ -0,0 +1,238 @@
1
+ import fs from "node:fs/promises";
2
+ import crypto from "node:crypto";
3
+ import { runChildProcess, buildPaperclipEnv, applyPaperclipWorkspaceEnv, renderPaperclipWakePrompt, renderTemplate, joinPromptSections, stringifyPaperclipWakePayload, parseObject, asString, DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE, } from "@paperclipai/adapter-utils/server-utils";
4
+ import { DEFAULT_ANTIGRAVITY_MODEL } from "../models.js";
5
+ import { syncSkillsToWorkspace } from "./skills-sync.js";
6
+ function parseSkills(raw) {
7
+ if (!Array.isArray(raw))
8
+ return [];
9
+ const items = [];
10
+ for (const item of raw) {
11
+ if (typeof item === "object" && item !== null) {
12
+ const obj = item;
13
+ if (typeof obj.name === "string" && obj.name.trim().length > 0) {
14
+ const files = [];
15
+ if (Array.isArray(obj.files)) {
16
+ for (const f of obj.files) {
17
+ if (typeof f === "object" && f !== null) {
18
+ const fileObj = f;
19
+ if (typeof fileObj.path === "string" && fileObj.path.trim().length > 0) {
20
+ files.push({
21
+ path: fileObj.path,
22
+ content: typeof fileObj.content === "string" ? fileObj.content : "",
23
+ });
24
+ }
25
+ }
26
+ }
27
+ }
28
+ items.push({
29
+ id: typeof obj.id === "string" ? obj.id : undefined,
30
+ name: obj.name,
31
+ description: typeof obj.description === "string" ? obj.description : undefined,
32
+ instructions: typeof obj.instructions === "string" ? obj.instructions : undefined,
33
+ content: typeof obj.content === "string" ? obj.content : undefined,
34
+ files: files.length > 0 ? files : undefined,
35
+ });
36
+ }
37
+ }
38
+ }
39
+ return items;
40
+ }
41
+ function buildPrompt(ctx) {
42
+ const { config, agent, context } = ctx;
43
+ const wakePrompt = renderPaperclipWakePrompt(context.paperclipWake, {
44
+ conversationMode: context.conversationMode === true,
45
+ resumedSession: Boolean(ctx.runtime.sessionId),
46
+ suppressIssueDescription: false,
47
+ });
48
+ const template = typeof config.promptTemplate === "string" && config.promptTemplate.trim().length > 0
49
+ ? config.promptTemplate
50
+ : DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE;
51
+ const data = {
52
+ agentId: agent.id,
53
+ agentName: agent.name,
54
+ companyId: agent.companyId,
55
+ runId: ctx.runId,
56
+ taskId: asString(context.taskId, ""),
57
+ taskTitle: asString(context.taskTitle, ""),
58
+ wakeReason: asString(context.wakeReason, ""),
59
+ };
60
+ const renderedTemplate = renderTemplate(template, data);
61
+ const sections = [];
62
+ const agentInstructions = asString(context.agentInstructions, "");
63
+ if (agentInstructions.trim().length > 0) {
64
+ sections.push(`## Agent Role & Instructions\n${agentInstructions.trim()}`);
65
+ }
66
+ if (wakePrompt && wakePrompt.trim().length > 0) {
67
+ sections.push(wakePrompt);
68
+ }
69
+ sections.push(renderedTemplate);
70
+ const taskDescription = asString(context.taskDescription, "");
71
+ if (taskDescription.trim().length > 0) {
72
+ sections.push(`## Task Details\n${taskDescription.trim()}`);
73
+ }
74
+ return joinPromptSections(sections);
75
+ }
76
+ export async function execute(ctx) {
77
+ const { config, agent, runId, onLog, context } = ctx;
78
+ const command = String(config.command ?? "agy");
79
+ const model = String(config.model ?? DEFAULT_ANTIGRAVITY_MODEL);
80
+ const effort = config.effort ? String(config.effort) : null;
81
+ const mode = config.mode ? String(config.mode) : null;
82
+ const timeoutSec = Number(config.timeoutSec ?? 600);
83
+ const graceSec = Number(config.graceSec ?? 15);
84
+ const prevConversationId = typeof ctx.runtime.sessionParams?.conversationId === "string"
85
+ ? ctx.runtime.sessionParams.conversationId
86
+ : typeof ctx.runtime.sessionId === "string"
87
+ ? ctx.runtime.sessionId
88
+ : null;
89
+ const conversationId = prevConversationId ?? `${agent.id}-${crypto.randomUUID()}`;
90
+ const workspaceContext = parseObject(context.paperclipWorkspace);
91
+ const workspaceCwd = asString(workspaceContext.cwd, "");
92
+ const effectiveCwd = workspaceCwd || String(config.cwd ?? process.cwd());
93
+ try {
94
+ await fs.mkdir(effectiveCwd, { recursive: true });
95
+ }
96
+ catch {
97
+ // Ignore directory creation error
98
+ }
99
+ const skillsList = parseSkills(context.skills);
100
+ const syncedSkills = await syncSkillsToWorkspace(effectiveCwd, skillsList);
101
+ const paperclipEnv = buildPaperclipEnv(agent);
102
+ const env = {
103
+ ...process.env,
104
+ ...paperclipEnv,
105
+ HOME: process.env.HOME || "/root",
106
+ PATH: `/root/.local/bin:${process.env.PATH ?? ""}`,
107
+ PAPERCLIP_RUN_ID: runId,
108
+ };
109
+ if (ctx.authToken) {
110
+ env.PAPERCLIP_API_KEY = ctx.authToken;
111
+ }
112
+ const wakeTaskId = asString(context.taskId, "") || asString(context.issueId, "");
113
+ if (wakeTaskId.trim().length > 0) {
114
+ env.PAPERCLIP_TASK_ID = wakeTaskId.trim();
115
+ }
116
+ const wakeReason = asString(context.wakeReason, "");
117
+ if (wakeReason.trim().length > 0) {
118
+ env.PAPERCLIP_WAKE_REASON = wakeReason.trim();
119
+ }
120
+ const wakeCommentId = asString(context.wakeCommentId, "") || asString(context.commentId, "");
121
+ if (wakeCommentId.trim().length > 0) {
122
+ env.PAPERCLIP_WAKE_COMMENT_ID = wakeCommentId.trim();
123
+ }
124
+ const approvalId = asString(context.approvalId, "");
125
+ if (approvalId.trim().length > 0) {
126
+ env.PAPERCLIP_APPROVAL_ID = approvalId.trim();
127
+ }
128
+ const approvalStatus = asString(context.approvalStatus, "");
129
+ if (approvalStatus.trim().length > 0) {
130
+ env.PAPERCLIP_APPROVAL_STATUS = approvalStatus.trim();
131
+ }
132
+ const linkedIssueIds = [];
133
+ if (Array.isArray(context.issueIds)) {
134
+ for (const item of context.issueIds) {
135
+ if (typeof item === "string" && item.trim().length > 0) {
136
+ linkedIssueIds.push(item.trim());
137
+ }
138
+ }
139
+ }
140
+ if (linkedIssueIds.length > 0) {
141
+ env.PAPERCLIP_LINKED_ISSUE_IDS = linkedIssueIds.join(",");
142
+ }
143
+ const wakePayloadJson = stringifyPaperclipWakePayload(context.paperclipWake);
144
+ if (wakePayloadJson) {
145
+ env.PAPERCLIP_WAKE_PAYLOAD_JSON = wakePayloadJson;
146
+ }
147
+ applyPaperclipWorkspaceEnv(env, {
148
+ workspaceCwd: effectiveCwd,
149
+ workspaceSource: asString(workspaceContext.source, ""),
150
+ workspaceStrategy: asString(workspaceContext.strategy, ""),
151
+ workspaceId: asString(workspaceContext.workspaceId, "") || null,
152
+ workspaceRepoUrl: asString(workspaceContext.repoUrl, "") || null,
153
+ workspaceRepoRef: asString(workspaceContext.repoRef, "") || null,
154
+ workspaceBranch: asString(workspaceContext.branchName, "") || null,
155
+ workspaceWorktreePath: asString(workspaceContext.worktreePath, "") || null,
156
+ agentHome: asString(workspaceContext.agentHome, "") || null,
157
+ });
158
+ if (typeof config.env === "object" && config.env !== null) {
159
+ for (const [k, v] of Object.entries(config.env)) {
160
+ if (typeof v === "string")
161
+ env[k] = v;
162
+ }
163
+ }
164
+ let promptText = buildPrompt(ctx);
165
+ const instructionsPath = config.instructionsFilePath
166
+ ? String(config.instructionsFilePath)
167
+ : null;
168
+ if (instructionsPath) {
169
+ try {
170
+ const instructions = await fs.readFile(instructionsPath, "utf-8");
171
+ promptText = `${instructions}\n\n---\n\n${promptText}`;
172
+ }
173
+ catch {
174
+ // Ignore missing instructions file
175
+ }
176
+ }
177
+ if (syncedSkills.length > 0) {
178
+ await onLog("stdout", `[antigravity] Synchronized ${syncedSkills.length} skill(s) for agent "${agent.name}": ${syncedSkills.join(", ")}\n`);
179
+ }
180
+ const args = ["--dangerously-skip-permissions"];
181
+ if (model && model !== "auto") {
182
+ args.push(`--model=${model}`);
183
+ }
184
+ if (effort) {
185
+ args.push(`--effort=${effort}`);
186
+ }
187
+ if (mode) {
188
+ args.push(`--mode=${mode}`);
189
+ }
190
+ if (prevConversationId) {
191
+ args.push(`--conversation=${prevConversationId}`);
192
+ }
193
+ args.push(`--print=${promptText}`);
194
+ await onLog("stdout", `[antigravity] Starting agy session (agent: ${agent.name}, model: ${model}, conversation: ${conversationId})...\n`);
195
+ try {
196
+ const processResult = await runChildProcess(runId, command, args, {
197
+ cwd: effectiveCwd,
198
+ env,
199
+ timeoutSec,
200
+ graceSec,
201
+ onLog: async (stream, chunk) => {
202
+ await onLog(stream, chunk);
203
+ },
204
+ onSpawn: ctx.onSpawn,
205
+ });
206
+ const isSuccess = processResult.exitCode === 0;
207
+ return {
208
+ exitCode: processResult.exitCode,
209
+ signal: processResult.signal,
210
+ timedOut: processResult.timedOut,
211
+ provider: "antigravity",
212
+ model,
213
+ sessionId: conversationId,
214
+ sessionDisplayId: conversationId.slice(0, 8),
215
+ sessionParams: {
216
+ conversationId,
217
+ cwd: effectiveCwd,
218
+ model,
219
+ agentId: agent.id,
220
+ },
221
+ errorMessage: isSuccess ? null : `agy exited with code ${processResult.exitCode}`,
222
+ summary: isSuccess ? "Completed Antigravity run" : `Run failed with exit code ${processResult.exitCode}`,
223
+ };
224
+ }
225
+ catch (err) {
226
+ const errorMsg = err instanceof Error ? err.message : String(err);
227
+ await onLog("stderr", `[antigravity] Error executing agy: ${errorMsg}\n`);
228
+ return {
229
+ exitCode: 1,
230
+ signal: null,
231
+ timedOut: false,
232
+ provider: "antigravity",
233
+ model,
234
+ errorMessage: errorMsg,
235
+ summary: `Antigravity execution failed: ${errorMsg}`,
236
+ };
237
+ }
238
+ }
@@ -0,0 +1,35 @@
1
+ import type { ServerAdapterModule, AdapterSessionCodec } from "@paperclipai/adapter-utils";
2
+ import { icon, iconSvg } from "../icon.js";
3
+ export declare const type = "antigravity";
4
+ export declare const label = "Antigravity";
5
+ export { icon, iconSvg };
6
+ export declare const sessionCodec: AdapterSessionCodec;
7
+ export declare const agentConfigurationDoc = "# Antigravity Agent Configuration\n\nAdapter: antigravity\n\nUse when:\n- You want Paperclip to orchestrate Google Antigravity (agy) local CLI agents.\n- You want model selection across Gemini 3.8 Flash, Gemini 3.7 Flash, Gemini 3.6 Flash, Gemini 3.1 Pro, Claude Sonnet/Opus, or GPT-OSS models.\n- You want to hire multiple local agents with individual instructions and Paperclip skills.\n- You want persistent conversation session state across heartbeats.\n\nCore fields:\n- model (string): Antigravity model ID (e.g. gemini-3.8-flash-high, gemini-3.7-flash-high, claude-sonnet-4-6).\n- effort (string, optional): Reasoning effort (low, medium, high, max).\n- mode (string, optional): accept-edits or plan.\n- command (string, optional): agy binary command path (defaults to \"agy\").\n- cwd (string, optional): Working directory fallback.\n- instructionsFilePath (string, optional): Path to instructions markdown file (e.g. AGENTS.md).\n- timeoutSec (number, optional): Execution timeout in seconds (default: 600).\n";
8
+ export declare function getConfigSchema(): {
9
+ fields: ({
10
+ key: string;
11
+ label: string;
12
+ type: "select";
13
+ default: string;
14
+ options: {
15
+ value: string;
16
+ label: string;
17
+ }[];
18
+ hint: string;
19
+ } | {
20
+ key: string;
21
+ label: string;
22
+ type: "text";
23
+ default: string;
24
+ hint: string;
25
+ options?: undefined;
26
+ } | {
27
+ key: string;
28
+ label: string;
29
+ type: "number";
30
+ default: number;
31
+ hint: string;
32
+ options?: undefined;
33
+ })[];
34
+ };
35
+ export declare function createServerAdapter(): ServerAdapterModule;
@@ -0,0 +1,111 @@
1
+ import { models, DEFAULT_ANTIGRAVITY_MODEL } from "../models.js";
2
+ import { icon, iconSvg } from "../icon.js";
3
+ import { execute } from "./execute.js";
4
+ import { testEnvironment } from "./test.js";
5
+ export const type = "antigravity";
6
+ export const label = "Antigravity";
7
+ export { icon, iconSvg };
8
+ export const sessionCodec = {
9
+ deserialize(raw) {
10
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
11
+ return null;
12
+ const r = raw;
13
+ const conversationId = r.conversationId ?? r.sessionId;
14
+ return conversationId ? { conversationId: String(conversationId) } : null;
15
+ },
16
+ serialize(params) {
17
+ if (!params?.conversationId && !params?.sessionId)
18
+ return null;
19
+ return { conversationId: String(params.conversationId ?? params.sessionId) };
20
+ },
21
+ getDisplayId(params) {
22
+ const id = params?.conversationId ?? params?.sessionId;
23
+ return id ? String(id).slice(0, 8) : null;
24
+ },
25
+ };
26
+ export const agentConfigurationDoc = `# Antigravity Agent Configuration
27
+
28
+ Adapter: antigravity
29
+
30
+ Use when:
31
+ - You want Paperclip to orchestrate Google Antigravity (agy) local CLI agents.
32
+ - You want model selection across Gemini 3.8 Flash, Gemini 3.7 Flash, Gemini 3.6 Flash, Gemini 3.1 Pro, Claude Sonnet/Opus, or GPT-OSS models.
33
+ - You want to hire multiple local agents with individual instructions and Paperclip skills.
34
+ - You want persistent conversation session state across heartbeats.
35
+
36
+ Core fields:
37
+ - model (string): Antigravity model ID (e.g. gemini-3.8-flash-high, gemini-3.7-flash-high, claude-sonnet-4-6).
38
+ - effort (string, optional): Reasoning effort (low, medium, high, max).
39
+ - mode (string, optional): accept-edits or plan.
40
+ - command (string, optional): agy binary command path (defaults to "agy").
41
+ - cwd (string, optional): Working directory fallback.
42
+ - instructionsFilePath (string, optional): Path to instructions markdown file (e.g. AGENTS.md).
43
+ - timeoutSec (number, optional): Execution timeout in seconds (default: 600).
44
+ `;
45
+ export function getConfigSchema() {
46
+ return {
47
+ fields: [
48
+ {
49
+ key: "model",
50
+ label: "Model",
51
+ type: "select",
52
+ default: DEFAULT_ANTIGRAVITY_MODEL,
53
+ options: models.map((m) => ({ value: m.id, label: m.label })),
54
+ hint: "Select the AI model for this Antigravity agent.",
55
+ },
56
+ {
57
+ key: "effort",
58
+ label: "Reasoning Effort",
59
+ type: "select",
60
+ default: "high",
61
+ options: [
62
+ { value: "low", label: "Low" },
63
+ { value: "medium", label: "Medium" },
64
+ { value: "high", label: "High" },
65
+ { value: "max", label: "Max" },
66
+ ],
67
+ hint: "Reasoning effort level for thinking models.",
68
+ },
69
+ {
70
+ key: "mode",
71
+ label: "Execution Mode",
72
+ type: "select",
73
+ default: "accept-edits",
74
+ options: [
75
+ { value: "accept-edits", label: "Accept Edits" },
76
+ { value: "plan", label: "Plan" },
77
+ ],
78
+ hint: "Execution mode for the Antigravity session.",
79
+ },
80
+ {
81
+ key: "command",
82
+ label: "CLI Command",
83
+ type: "text",
84
+ default: "agy",
85
+ hint: "Override command or binary path for Antigravity CLI.",
86
+ },
87
+ {
88
+ key: "timeoutSec",
89
+ label: "Timeout (seconds)",
90
+ type: "number",
91
+ default: 600,
92
+ hint: "Execution timeout in seconds.",
93
+ },
94
+ ],
95
+ };
96
+ }
97
+ export function createServerAdapter() {
98
+ return {
99
+ type,
100
+ execute,
101
+ testEnvironment,
102
+ sessionCodec,
103
+ models,
104
+ agentConfigurationDoc,
105
+ getConfigSchema,
106
+ supportsSkills: true,
107
+ supportsInstructionsBundle: true,
108
+ instructionsPathKey: "instructionsFilePath",
109
+ supportsLocalAgentJwt: true,
110
+ };
111
+ }
@@ -0,0 +1,13 @@
1
+ export interface SkillFile {
2
+ path: string;
3
+ content: string;
4
+ }
5
+ export interface SkillItem {
6
+ id?: string;
7
+ name: string;
8
+ description?: string;
9
+ instructions?: string;
10
+ content?: string;
11
+ files?: SkillFile[];
12
+ }
13
+ export declare function syncSkillsToWorkspace(effectiveCwd: string, skills?: SkillItem[]): Promise<string[]>;
@@ -0,0 +1,39 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export async function syncSkillsToWorkspace(effectiveCwd, skills) {
4
+ if (!Array.isArray(skills) || skills.length === 0) {
5
+ return [];
6
+ }
7
+ const baseSkillsDir = path.join(effectiveCwd, ".agents", "skills");
8
+ await fs.mkdir(baseSkillsDir, { recursive: true });
9
+ const syncedSkillNames = [];
10
+ for (const skill of skills) {
11
+ if (!skill || !skill.name)
12
+ continue;
13
+ const skillName = skill.name.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-");
14
+ const skillDir = path.join(baseSkillsDir, skillName);
15
+ await fs.mkdir(skillDir, { recursive: true });
16
+ const description = skill.description ? skill.description.trim() : `Skill: ${skill.name}`;
17
+ const body = skill.instructions ?? skill.content ?? "";
18
+ const skillMdContent = `---
19
+ name: ${skillName}
20
+ description: ${description.replace(/\n/g, " ")}
21
+ ---
22
+
23
+ ${body}
24
+ `;
25
+ const skillMdPath = path.join(skillDir, "SKILL.md");
26
+ await fs.writeFile(skillMdPath, skillMdContent, "utf-8");
27
+ if (Array.isArray(skill.files)) {
28
+ for (const file of skill.files) {
29
+ if (!file || !file.path)
30
+ continue;
31
+ const filePath = path.join(skillDir, file.path);
32
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
33
+ await fs.writeFile(filePath, file.content ?? "", "utf-8");
34
+ }
35
+ }
36
+ syncedSkillNames.push(skillName);
37
+ }
38
+ return syncedSkillNames;
39
+ }
@@ -0,0 +1,2 @@
1
+ import type { AdapterEnvironmentTestContext, AdapterEnvironmentTestResult } from "@paperclipai/adapter-utils";
2
+ export declare function testEnvironment(ctx: AdapterEnvironmentTestContext): Promise<AdapterEnvironmentTestResult>;
@@ -0,0 +1,42 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ export async function testEnvironment(ctx) {
5
+ const checks = [];
6
+ const command = String(ctx.config.command ?? "agy");
7
+ try {
8
+ const { stdout } = await execFileAsync(command, ["--version"], {
9
+ timeout: 5000,
10
+ });
11
+ const version = stdout.trim();
12
+ checks.push({
13
+ level: "info",
14
+ message: `Antigravity CLI detected: ${version}`,
15
+ code: "agy_cli_detected",
16
+ });
17
+ }
18
+ catch (err) {
19
+ checks.push({
20
+ level: "error",
21
+ message: `Failed to execute Antigravity CLI command "${command}": ${err instanceof Error ? err.message : String(err)}`,
22
+ hint: "Ensure 'agy' is installed in PATH (e.g. /root/.local/bin/agy) or specify adapterConfig.command.",
23
+ code: "agy_cli_missing",
24
+ });
25
+ }
26
+ const cwd = ctx.config.cwd ? String(ctx.config.cwd) : null;
27
+ if (cwd && !cwd.startsWith("/")) {
28
+ checks.push({
29
+ level: "error",
30
+ message: `Working directory must be an absolute path: "${cwd}"`,
31
+ hint: "Specify an absolute path such as /root or a workspace directory.",
32
+ code: "invalid_cwd",
33
+ });
34
+ }
35
+ const hasError = checks.some((c) => c.level === "error");
36
+ return {
37
+ adapterType: ctx.adapterType,
38
+ status: hasError ? "fail" : "pass",
39
+ checks,
40
+ testedAt: new Date().toISOString(),
41
+ };
42
+ }
@@ -0,0 +1,11 @@
1
+ export interface TranscriptEntry {
2
+ kind: "assistant" | "thinking" | "system" | "tool_call" | "tool_result" | "stderr" | "stdout";
3
+ ts: string;
4
+ text?: string;
5
+ name?: string;
6
+ input?: unknown;
7
+ toolUseId?: string;
8
+ content?: string;
9
+ isError?: boolean;
10
+ }
11
+ export declare function parseStdoutLine(line: string, ts: string): TranscriptEntry[];
@@ -0,0 +1,42 @@
1
+ // Self-contained UI transcript parser for Antigravity (zero runtime imports)
2
+ export function parseStdoutLine(line, ts) {
3
+ const trimmed = line.trim();
4
+ if (!trimmed)
5
+ return [];
6
+ // Antigravity adapter log messages
7
+ if (trimmed.startsWith("[antigravity]")) {
8
+ return [{ kind: "system", ts, text: trimmed }];
9
+ }
10
+ // Spinner / progress indicators from CLI
11
+ if (/^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]/.test(trimmed)) {
12
+ return [{ kind: "thinking", ts, text: trimmed.replace(/^[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]\s*/, "") }];
13
+ }
14
+ // Thinking markers
15
+ if (trimmed.startsWith("Thinking:") || trimmed.startsWith("Thought:")) {
16
+ return [{ kind: "thinking", ts, text: trimmed }];
17
+ }
18
+ // Tool calls (e.g. [tool] call: name)
19
+ const toolMatch = trimmed.match(/^\[(?:tool|call)\]\s+([a-zA-Z0-9_-]+)(?:\s+(.*))?$/);
20
+ if (toolMatch) {
21
+ const name = toolMatch[1];
22
+ let input = {};
23
+ if (toolMatch[2]) {
24
+ try {
25
+ input = JSON.parse(toolMatch[2]);
26
+ }
27
+ catch {
28
+ input = { raw: toolMatch[2] };
29
+ }
30
+ }
31
+ return [
32
+ {
33
+ kind: "tool_call",
34
+ ts,
35
+ name,
36
+ input,
37
+ },
38
+ ];
39
+ }
40
+ // Default to assistant output
41
+ return [{ kind: "assistant", ts, text: line }];
42
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "agy-paperclip-adapter",
3
+ "version": "1.0.0",
4
+ "description": "Google Antigravity (agy) local agent adapter for Paperclip",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "keywords": [
11
+ "paperclip",
12
+ "paperclip-adapter",
13
+ "antigravity",
14
+ "agy",
15
+ "ai-agent",
16
+ "gemini"
17
+ ],
18
+ "paperclip": {
19
+ "adapterUiParser": "1.0.0"
20
+ },
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "import": "./dist/index.js",
25
+ "default": "./dist/index.js"
26
+ },
27
+ "./server": {
28
+ "types": "./dist/server/index.d.ts",
29
+ "import": "./dist/server/index.js",
30
+ "default": "./dist/server/index.js"
31
+ },
32
+ "./ui-parser": {
33
+ "types": "./dist/ui-parser.d.ts",
34
+ "import": "./dist/ui-parser.js",
35
+ "default": "./dist/ui-parser.js"
36
+ }
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "README.md"
41
+ ],
42
+ "scripts": {
43
+ "build": "tsc",
44
+ "clean": "rm -rf dist *.tgz",
45
+ "pack": "npm run clean && npm run build && npm pack --pack-destination=dist",
46
+ "prepublishOnly": "npm run clean && npm run build"
47
+ },
48
+ "dependencies": {
49
+ "@paperclipai/adapter-utils": "^2026.325.0",
50
+ "picocolors": "^1.1.1"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^24.0.0",
54
+ "typescript": "^5.7.0"
55
+ }
56
+ }