@agentex/agent 0.0.37 → 0.0.39
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 +47 -0
- package/README.md +54 -17
- package/dist/providers/antigravity/codec.d.ts +4 -0
- package/dist/providers/antigravity/codec.d.ts.map +1 -0
- package/dist/providers/antigravity/codec.js +27 -0
- package/dist/providers/antigravity/codec.js.map +1 -0
- package/dist/providers/antigravity/connection.d.ts +100 -0
- package/dist/providers/antigravity/connection.d.ts.map +1 -0
- package/dist/providers/antigravity/connection.js +267 -0
- package/dist/providers/antigravity/connection.js.map +1 -0
- package/dist/providers/antigravity/discovery.d.ts +11 -0
- package/dist/providers/antigravity/discovery.d.ts.map +1 -0
- package/dist/providers/antigravity/discovery.js +92 -0
- package/dist/providers/antigravity/discovery.js.map +1 -0
- package/dist/providers/antigravity/execute.d.ts +9 -0
- package/dist/providers/antigravity/execute.d.ts.map +1 -0
- package/dist/providers/antigravity/execute.js +206 -0
- package/dist/providers/antigravity/execute.js.map +1 -0
- package/dist/providers/antigravity/index.d.ts +12 -0
- package/dist/providers/antigravity/index.d.ts.map +1 -0
- package/dist/providers/antigravity/index.js +46 -0
- package/dist/providers/antigravity/index.js.map +1 -0
- package/dist/providers/antigravity/parse.d.ts +114 -0
- package/dist/providers/antigravity/parse.d.ts.map +1 -0
- package/dist/providers/antigravity/parse.js +369 -0
- package/dist/providers/antigravity/parse.js.map +1 -0
- package/dist/providers/antigravity/probe.d.ts +3 -0
- package/dist/providers/antigravity/probe.d.ts.map +1 -0
- package/dist/providers/antigravity/probe.js +84 -0
- package/dist/providers/antigravity/probe.js.map +1 -0
- package/dist/providers/antigravity/runtime.d.ts +30 -0
- package/dist/providers/antigravity/runtime.d.ts.map +1 -0
- package/dist/providers/antigravity/runtime.js +69 -0
- package/dist/providers/antigravity/runtime.js.map +1 -0
- package/dist/providers/antigravity/session.d.ts +7 -0
- package/dist/providers/antigravity/session.d.ts.map +1 -0
- package/dist/providers/antigravity/session.js +317 -0
- package/dist/providers/antigravity/session.js.map +1 -0
- package/dist/providers/gemini/index.d.ts.map +1 -1
- package/dist/providers/gemini/index.js +6 -0
- package/dist/providers/gemini/index.js.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +2 -0
- package/dist/registry.js.map +1 -1
- package/dist/types.d.ts +6 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/auth.d.ts +1 -1
- package/dist/utils/auth.d.ts.map +1 -1
- package/dist/utils/auth.js +79 -1
- package/dist/utils/auth.js.map +1 -1
- package/dist/utils/binary.d.ts.map +1 -1
- package/dist/utils/binary.js +10 -0
- package/dist/utils/binary.js.map +1 -1
- package/dist/utils/instructions.d.ts +36 -12
- package/dist/utils/instructions.d.ts.map +1 -1
- package/dist/utils/instructions.js +116 -62
- package/dist/utils/instructions.js.map +1 -1
- package/dist/utils/runtime-homes.d.ts.map +1 -1
- package/dist/utils/runtime-homes.js +4 -0
- package/dist/utils/runtime-homes.js.map +1 -1
- package/dist/utils/skills.d.ts +3 -3
- package/dist/utils/skills.d.ts.map +1 -1
- package/dist/utils/skills.js +16 -2
- package/dist/utils/skills.js.map +1 -1
- package/package.json +3 -2
- package/src/providers/antigravity/codec.ts +29 -0
- package/src/providers/antigravity/connection.ts +304 -0
- package/src/providers/antigravity/discovery.ts +90 -0
- package/src/providers/antigravity/execute.ts +214 -0
- package/src/providers/antigravity/index.ts +48 -0
- package/src/providers/antigravity/parse.ts +439 -0
- package/src/providers/antigravity/probe.ts +89 -0
- package/src/providers/antigravity/runtime.ts +64 -0
- package/src/providers/antigravity/session.ts +359 -0
- package/src/providers/gemini/index.ts +6 -0
- package/src/registry.ts +2 -0
- package/src/types.ts +6 -5
- package/src/utils/auth.ts +82 -1
- package/src/utils/binary.ts +10 -0
- package/src/utils/instructions.ts +162 -73
- package/src/utils/runtime-homes.ts +4 -0
- package/src/utils/skills.ts +17 -4
|
@@ -29,18 +29,27 @@ export async function resolveInstructions(filePath?: string): Promise<string | n
|
|
|
29
29
|
// (.agents/skills + .claude/skills) with a per-runtime "native" escape hatch.
|
|
30
30
|
// Instruction files follow the same shape:
|
|
31
31
|
//
|
|
32
|
-
// - every runtime
|
|
33
|
-
//
|
|
34
|
-
//
|
|
32
|
+
// - every runtime reads AGENTS.md at a workspace root (Claude Code since
|
|
33
|
+
// 2.1.277, where the folder has no CLAUDE.md)
|
|
34
|
+
// - Claude's CLAUDE.md and Gemini's GEMINI.md are native files, written only
|
|
35
|
+
// on opt-in (`includeNativeFiles`)
|
|
35
36
|
//
|
|
36
37
|
// Two locations, mirroring installSkills:
|
|
37
38
|
//
|
|
38
39
|
// - "workspace": files at {cwd}/ — the repo-root AGENTS.md convention. Files
|
|
39
|
-
// dedupe by name, so the default writes
|
|
40
|
+
// dedupe by name, so the default writes AGENTS.md once.
|
|
40
41
|
// - "global": each runtime reads its own file in its own home dir
|
|
41
42
|
// (~/.claude/CLAUDE.md, ~/.codex/AGENTS.md, ~/.gemini/GEMINI.md, ...). There
|
|
42
43
|
// is no universal ~/AGENTS.md, so global is inherently per-runtime.
|
|
43
44
|
//
|
|
45
|
+
// Claude Code reads AGENTS.md only where no CLAUDE.md exists: a CLAUDE.md (or
|
|
46
|
+
// a CLAUDE.local.md, or one in a parent directory) hides AGENTS.md completely.
|
|
47
|
+
// So a workspace CLAUDE.md is only ever a one-line pointer (`@AGENTS.md`),
|
|
48
|
+
// never a second copy of the brief, and a CLAUDE.md already on disk is
|
|
49
|
+
// reconciled on every install: one holding nothing but our managed region is
|
|
50
|
+
// removed when the opt-in is off, and one the user wrote gets the pointer so
|
|
51
|
+
// the brief still reaches Claude.
|
|
52
|
+
//
|
|
44
53
|
// Unlike skills (which are symlinked dirs), instruction files carry content, so
|
|
45
54
|
// installInstructions does a managed-region merge: it wraps `content` in marker
|
|
46
55
|
// comments and replaces only that region on re-install, preserving anything the
|
|
@@ -48,24 +57,32 @@ export async function resolveInstructions(filePath?: string): Promise<string | n
|
|
|
48
57
|
// ===========================================================================
|
|
49
58
|
|
|
50
59
|
interface RuntimeInstructionSpec {
|
|
51
|
-
/** File this runtime reads at a workspace/repo root. AGENTS.md for
|
|
60
|
+
/** File this runtime reads at a workspace/repo root. AGENTS.md for every runtime. */
|
|
52
61
|
projectFile: string;
|
|
53
|
-
/** The runtime's own preferred filename.
|
|
62
|
+
/** The runtime's own preferred filename. Written in a workspace only on opt-in (`includeNativeFiles`). */
|
|
54
63
|
nativeFile: string;
|
|
64
|
+
/**
|
|
65
|
+
* How an opt-in native file carries the brief. "pointer": only an import of
|
|
66
|
+
* the project file (`@AGENTS.md`), because the native file's mere presence
|
|
67
|
+
* hides the project file from the runtime (Claude). "copy": the brief itself
|
|
68
|
+
* (Gemini, which doesn't read AGENTS.md by default).
|
|
69
|
+
*/
|
|
70
|
+
nativeFileMode: "pointer" | "copy";
|
|
55
71
|
/** Whether the runtime has a file-based global config. False for Cursor (global = app User Rules). */
|
|
56
72
|
hasGlobalFile: boolean;
|
|
57
73
|
}
|
|
58
74
|
|
|
59
75
|
const RUNTIME_INSTRUCTIONS: Record<SkillRuntime, RuntimeInstructionSpec> = {
|
|
60
|
-
claude: { projectFile: "
|
|
61
|
-
codex: { projectFile: "AGENTS.md", nativeFile: "AGENTS.md", hasGlobalFile: true },
|
|
62
|
-
opencode: { projectFile: "AGENTS.md", nativeFile: "AGENTS.md", hasGlobalFile: true },
|
|
63
|
-
gemini: { projectFile: "AGENTS.md", nativeFile: "GEMINI.md", hasGlobalFile: true },
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
claude: { projectFile: "AGENTS.md", nativeFile: "CLAUDE.md", nativeFileMode: "pointer", hasGlobalFile: true },
|
|
77
|
+
codex: { projectFile: "AGENTS.md", nativeFile: "AGENTS.md", nativeFileMode: "copy", hasGlobalFile: true },
|
|
78
|
+
opencode: { projectFile: "AGENTS.md", nativeFile: "AGENTS.md", nativeFileMode: "copy", hasGlobalFile: true },
|
|
79
|
+
gemini: { projectFile: "AGENTS.md", nativeFile: "GEMINI.md", nativeFileMode: "copy", hasGlobalFile: true },
|
|
80
|
+
antigravity: { projectFile: "AGENTS.md", nativeFile: "GEMINI.md", nativeFileMode: "copy", hasGlobalFile: true },
|
|
81
|
+
cursor: { projectFile: "AGENTS.md", nativeFile: "AGENTS.md", nativeFileMode: "copy", hasGlobalFile: false },
|
|
82
|
+
pi: { projectFile: "AGENTS.md", nativeFile: "AGENTS.md", nativeFileMode: "copy", hasGlobalFile: true },
|
|
66
83
|
};
|
|
67
84
|
|
|
68
|
-
const ALL_RUNTIMES: SkillRuntime[] = ["claude", "codex", "gemini", "cursor", "opencode", "pi"];
|
|
85
|
+
const ALL_RUNTIMES: SkillRuntime[] = ["claude", "codex", "gemini", "antigravity", "cursor", "opencode", "pi"];
|
|
69
86
|
|
|
70
87
|
const DEFAULT_MANAGED_TAG = "agentex";
|
|
71
88
|
|
|
@@ -78,15 +95,20 @@ export interface InstallInstructionsOptions {
|
|
|
78
95
|
cwd?: string;
|
|
79
96
|
/**
|
|
80
97
|
* Also write each runtime's native file when it differs from the shared
|
|
81
|
-
*
|
|
82
|
-
*
|
|
98
|
+
* AGENTS.md: Claude's CLAUDE.md (as a one-line `@AGENTS.md` pointer) and
|
|
99
|
+
* Gemini's GEMINI.md (as a copy). Only affects "workspace"; "global" always
|
|
100
|
+
* uses native files. Default: false.
|
|
101
|
+
*
|
|
102
|
+
* Opt in for Claude Code before 2.1.277, or on Bedrock, Vertex or Foundry,
|
|
103
|
+
* where Claude doesn't read AGENTS.md on its own.
|
|
83
104
|
*/
|
|
84
105
|
includeNativeFiles?: boolean;
|
|
85
106
|
/**
|
|
86
107
|
* Wrap `content` in managed markers and merge into any existing file,
|
|
87
108
|
* replacing only the previously-managed region and preserving everything the
|
|
88
109
|
* user wrote outside it. Default: true. When false, the file is overwritten
|
|
89
|
-
* with raw `content` (escape hatch for fully-owned files)
|
|
110
|
+
* with raw `content` (escape hatch for fully-owned files), and an existing
|
|
111
|
+
* CLAUDE.md is left alone even though it hides AGENTS.md from Claude Code.
|
|
90
112
|
*/
|
|
91
113
|
managed?: boolean;
|
|
92
114
|
/** Marker tag, so the comment reads `<!-- <tag>:managed:start -->`. Default: "agentex". */
|
|
@@ -98,7 +120,11 @@ export interface InstallInstructionsOptions {
|
|
|
98
120
|
homeDir?: string;
|
|
99
121
|
}
|
|
100
122
|
|
|
101
|
-
|
|
123
|
+
/**
|
|
124
|
+
* "removed": a workspace CLAUDE.md that held nothing but this installer's
|
|
125
|
+
* managed region, deleted because the opt-in (`includeNativeFiles`) is off.
|
|
126
|
+
*/
|
|
127
|
+
export type InstructionStatus = "created" | "updated" | "skipped" | "removed" | "error";
|
|
102
128
|
|
|
103
129
|
export interface InstructionInstallEntry {
|
|
104
130
|
/** The filename written, e.g. "AGENTS.md", "CLAUDE.md", "GEMINI.md". */
|
|
@@ -107,6 +133,8 @@ export interface InstructionInstallEntry {
|
|
|
107
133
|
targetPath: string;
|
|
108
134
|
/** Which requested runtimes this file serves. */
|
|
109
135
|
runtimes: SkillRuntime[];
|
|
136
|
+
/** Set when the file is a pointer: its managed region is `@<importsFile>`, not the brief. */
|
|
137
|
+
importsFile?: string;
|
|
110
138
|
status: InstructionStatus;
|
|
111
139
|
error?: string;
|
|
112
140
|
}
|
|
@@ -116,6 +144,7 @@ export interface InstructionInstallResult {
|
|
|
116
144
|
installed: number; // newly created files
|
|
117
145
|
updated: number; // existing files whose content changed
|
|
118
146
|
skipped: number; // content already current → no write
|
|
147
|
+
removed: number; // managed-only CLAUDE.md deleted because the opt-in is off
|
|
119
148
|
errors: number;
|
|
120
149
|
}
|
|
121
150
|
|
|
@@ -123,6 +152,8 @@ export interface InstructionTarget {
|
|
|
123
152
|
filename: string;
|
|
124
153
|
targetPath: string;
|
|
125
154
|
runtimes: SkillRuntime[];
|
|
155
|
+
/** Set when the file is a pointer: its managed region is `@<importsFile>`, not the brief. */
|
|
156
|
+
importsFile?: string;
|
|
126
157
|
}
|
|
127
158
|
|
|
128
159
|
export interface RemoveInstructionsOptions {
|
|
@@ -163,7 +194,8 @@ export interface ManagedBlockOptions {
|
|
|
163
194
|
* without touching disk.
|
|
164
195
|
*
|
|
165
196
|
* - "workspace": files dedupe by name under {cwd}/ (the default writes
|
|
166
|
-
*
|
|
197
|
+
* AGENTS.md once). `includeNativeFiles` adds CLAUDE.md (a pointer, marked by
|
|
198
|
+
* `importsFile`) and GEMINI.md.
|
|
167
199
|
* - "global": one file per runtime in each runtime's home dir. Runtimes without
|
|
168
200
|
* a file-based global config (Cursor) are omitted.
|
|
169
201
|
*/
|
|
@@ -181,41 +213,42 @@ export function resolveInstructionTargets(options?: {
|
|
|
181
213
|
const cwd = options?.cwd;
|
|
182
214
|
if (!cwd) throw new Error("cwd is required when location is 'workspace'");
|
|
183
215
|
|
|
184
|
-
const byFile = new Map<string,
|
|
185
|
-
const add = (filename: string, runtime: SkillRuntime) => {
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
216
|
+
const byFile = new Map<string, InstructionTarget>();
|
|
217
|
+
const add = (filename: string, runtime: SkillRuntime, importsFile?: string) => {
|
|
218
|
+
const target: InstructionTarget = byFile.get(filename) ?? {
|
|
219
|
+
filename,
|
|
220
|
+
targetPath: path.join(cwd, filename),
|
|
221
|
+
runtimes: [],
|
|
222
|
+
...(importsFile !== undefined && { importsFile }),
|
|
223
|
+
};
|
|
224
|
+
target.runtimes.push(runtime);
|
|
225
|
+
byFile.set(filename, target);
|
|
189
226
|
};
|
|
190
227
|
|
|
191
228
|
for (const runtime of runtimes) {
|
|
192
229
|
const spec = RUNTIME_INSTRUCTIONS[runtime];
|
|
193
230
|
add(spec.projectFile, runtime);
|
|
194
231
|
if (options?.includeNativeFiles && spec.nativeFile !== spec.projectFile) {
|
|
195
|
-
add(spec.nativeFile, runtime);
|
|
232
|
+
add(spec.nativeFile, runtime, spec.nativeFileMode === "pointer" ? spec.projectFile : undefined);
|
|
196
233
|
}
|
|
197
234
|
}
|
|
198
235
|
|
|
199
|
-
return [...byFile.
|
|
200
|
-
filename,
|
|
201
|
-
targetPath: path.join(cwd, filename),
|
|
202
|
-
runtimes: dedupeRuntimes(rts),
|
|
203
|
-
}));
|
|
236
|
+
return [...byFile.values()].map((target) => ({ ...target, runtimes: dedupeRuntimes(target.runtimes) }));
|
|
204
237
|
}
|
|
205
238
|
|
|
206
|
-
// global: each runtime reads its own native file in its own home dir.
|
|
207
|
-
|
|
239
|
+
// global: each runtime reads its own native file in its own home dir. Gemini
|
|
240
|
+
// CLI and Antigravity share ~/.gemini/GEMINI.md, so targets merge by path.
|
|
241
|
+
const byPath = new Map<string, InstructionTarget>();
|
|
208
242
|
for (const runtime of runtimes) {
|
|
209
243
|
const spec = RUNTIME_INSTRUCTIONS[runtime];
|
|
210
244
|
if (!spec.hasGlobalFile) continue; // e.g. cursor global = app User Rules, not a file
|
|
211
245
|
const home = getDefaultRuntimeHome(runtime, options?.homeDir);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
});
|
|
246
|
+
const targetPath = path.join(home, spec.nativeFile);
|
|
247
|
+
const existing = byPath.get(targetPath);
|
|
248
|
+
if (existing) existing.runtimes.push(runtime);
|
|
249
|
+
else byPath.set(targetPath, { filename: spec.nativeFile, targetPath, runtimes: [runtime] });
|
|
217
250
|
}
|
|
218
|
-
return
|
|
251
|
+
return [...byPath.values()];
|
|
219
252
|
}
|
|
220
253
|
|
|
221
254
|
function dedupeRuntimes(runtimes: SkillRuntime[]): SkillRuntime[] {
|
|
@@ -229,18 +262,25 @@ function dedupeRuntimes(runtimes: SkillRuntime[]): SkillRuntime[] {
|
|
|
229
262
|
/**
|
|
230
263
|
* Install an instruction brief into the right per-runtime files.
|
|
231
264
|
*
|
|
232
|
-
* By default merges `content` into a managed region of `{cwd}/
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
265
|
+
* By default merges `content` into a managed region of `{cwd}/AGENTS.md`,
|
|
266
|
+
* preserving any user-authored content outside the markers. Idempotent: a
|
|
267
|
+
* re-install with unchanged content reports every entry as "skipped".
|
|
268
|
+
*
|
|
269
|
+
* When Claude is among the runtimes, a `{cwd}/CLAUDE.md` already on disk is
|
|
270
|
+
* reconciled, because it would hide AGENTS.md from Claude Code: one holding
|
|
271
|
+
* nothing but this installer's managed region is removed, and one the user
|
|
272
|
+
* wrote gets `@AGENTS.md` in its managed region. With `includeNativeFiles`,
|
|
273
|
+
* CLAUDE.md is written as that pointer either way. `managed: false` leaves an
|
|
274
|
+
* existing CLAUDE.md alone.
|
|
236
275
|
*
|
|
237
276
|
* @example
|
|
238
277
|
* ```ts
|
|
239
278
|
* // Workspace (repo-root) install — the common case.
|
|
240
279
|
* await installInstructions(brief, { location: "workspace", cwd: projectDir });
|
|
241
|
-
* // → {cwd}/
|
|
280
|
+
* // → {cwd}/AGENTS.md
|
|
242
281
|
*
|
|
243
|
-
* // Also
|
|
282
|
+
* // Also write the native files: CLAUDE.md as an `@AGENTS.md` pointer (for
|
|
283
|
+
* // Claude Code before 2.1.277, or on Bedrock/Vertex/Foundry) and GEMINI.md.
|
|
244
284
|
* await installInstructions(brief, { location: "workspace", cwd, includeNativeFiles: true });
|
|
245
285
|
*
|
|
246
286
|
* // Global install — per-runtime home files.
|
|
@@ -254,37 +294,16 @@ export async function installInstructions(
|
|
|
254
294
|
): Promise<InstructionInstallResult> {
|
|
255
295
|
const managed = options?.managed ?? true;
|
|
256
296
|
const tag = options?.managedTag ?? DEFAULT_MANAGED_TAG;
|
|
257
|
-
const targets = resolveInstructionTargets(options);
|
|
258
297
|
const entries: InstructionInstallEntry[] = [];
|
|
259
298
|
|
|
260
|
-
for (const target of
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (existing === null) {
|
|
269
|
-
status = "created";
|
|
270
|
-
} else if (existing === next) {
|
|
271
|
-
status = "skipped";
|
|
272
|
-
} else {
|
|
273
|
-
status = "updated";
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (status !== "skipped") {
|
|
277
|
-
await fs.mkdir(path.dirname(target.targetPath), { recursive: true });
|
|
278
|
-
await fs.writeFile(target.targetPath, next, { mode: 0o644 });
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
entries.push({ ...target, status });
|
|
282
|
-
} catch (err) {
|
|
283
|
-
entries.push({
|
|
284
|
-
...target,
|
|
285
|
-
status: "error",
|
|
286
|
-
error: err instanceof Error ? err.message : String(err),
|
|
287
|
-
});
|
|
299
|
+
for (const target of resolveInstructionTargets(options)) {
|
|
300
|
+
const body = target.importsFile !== undefined ? `@${target.importsFile}` : content;
|
|
301
|
+
entries.push(await writeTarget(target, body, managed, tag));
|
|
302
|
+
}
|
|
303
|
+
if (managed) {
|
|
304
|
+
for (const target of unrequestedPointerTargets(options)) {
|
|
305
|
+
const entry = await reconcilePointer(target, tag);
|
|
306
|
+
if (entry) entries.push(entry);
|
|
288
307
|
}
|
|
289
308
|
}
|
|
290
309
|
|
|
@@ -293,15 +312,84 @@ export async function installInstructions(
|
|
|
293
312
|
installed: entries.filter((e) => e.status === "created").length,
|
|
294
313
|
updated: entries.filter((e) => e.status === "updated").length,
|
|
295
314
|
skipped: entries.filter((e) => e.status === "skipped").length,
|
|
315
|
+
removed: entries.filter((e) => e.status === "removed").length,
|
|
296
316
|
errors: entries.filter((e) => e.status === "error").length,
|
|
297
317
|
};
|
|
298
318
|
}
|
|
299
319
|
|
|
320
|
+
async function writeTarget(
|
|
321
|
+
target: InstructionTarget,
|
|
322
|
+
body: string,
|
|
323
|
+
managed: boolean,
|
|
324
|
+
tag: string,
|
|
325
|
+
): Promise<InstructionInstallEntry> {
|
|
326
|
+
try {
|
|
327
|
+
const existing = await readFileOrNull(target.targetPath);
|
|
328
|
+
const next = managed ? upsertManagedBlock(existing, body, { tag }) : ensureTrailingNewline(body);
|
|
329
|
+
|
|
330
|
+
let status: InstructionStatus;
|
|
331
|
+
if (existing === null) {
|
|
332
|
+
status = "created";
|
|
333
|
+
} else if (existing === next) {
|
|
334
|
+
status = "skipped";
|
|
335
|
+
} else {
|
|
336
|
+
status = "updated";
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (status !== "skipped") {
|
|
340
|
+
await fs.mkdir(path.dirname(target.targetPath), { recursive: true });
|
|
341
|
+
await fs.writeFile(target.targetPath, next, { mode: 0o644 });
|
|
342
|
+
}
|
|
343
|
+
return { ...target, status };
|
|
344
|
+
} catch (err) {
|
|
345
|
+
return { ...target, status: "error", error: err instanceof Error ? err.message : String(err) };
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Pointer files (a workspace CLAUDE.md) for the requested runtimes that the
|
|
351
|
+
* install doesn't write because the opt-in is off, but that still need
|
|
352
|
+
* reconciling if they exist.
|
|
353
|
+
*/
|
|
354
|
+
function unrequestedPointerTargets(options?: InstallInstructionsOptions): InstructionTarget[] {
|
|
355
|
+
if ((options?.location ?? "workspace") !== "workspace" || options?.includeNativeFiles) return [];
|
|
356
|
+
return resolveInstructionTargets({ ...options, includeNativeFiles: true }).filter(
|
|
357
|
+
(target) => target.importsFile !== undefined,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* An existing pointer file with the opt-in off: delete it when nothing but our
|
|
363
|
+
* managed region is in it, otherwise keep the user's content and make sure it
|
|
364
|
+
* imports the project file. Returns null when there is no file.
|
|
365
|
+
*/
|
|
366
|
+
async function reconcilePointer(
|
|
367
|
+
target: InstructionTarget,
|
|
368
|
+
tag: string,
|
|
369
|
+
): Promise<InstructionInstallEntry | null> {
|
|
370
|
+
try {
|
|
371
|
+
const existing = await readFileOrNull(target.targetPath);
|
|
372
|
+
if (existing === null) return null;
|
|
373
|
+
if (stripManagedBlock(existing, { tag }) === null) {
|
|
374
|
+
await fs.rm(target.targetPath, { force: true });
|
|
375
|
+
return { ...target, status: "removed" };
|
|
376
|
+
}
|
|
377
|
+
const next = upsertManagedBlock(existing, `@${target.importsFile}`, { tag });
|
|
378
|
+
if (next === existing) return { ...target, status: "skipped" };
|
|
379
|
+
await fs.writeFile(target.targetPath, next);
|
|
380
|
+
return { ...target, status: "updated" };
|
|
381
|
+
} catch (err) {
|
|
382
|
+
return { ...target, status: "error", error: err instanceof Error ? err.message : String(err) };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
300
386
|
/**
|
|
301
387
|
* Remove the managed region installed by {@link installInstructions}, preserving
|
|
302
388
|
* any user-authored content outside the markers. If the file contains nothing
|
|
303
389
|
* but the managed block, it is deleted. User-owned files (no managed region) are
|
|
304
|
-
* left untouched and reported as "skipped".
|
|
390
|
+
* left untouched and reported as "skipped". Native files (CLAUDE.md, GEMINI.md)
|
|
391
|
+
* are always checked, whether or not they were installed with
|
|
392
|
+
* `includeNativeFiles`.
|
|
305
393
|
*/
|
|
306
394
|
export async function removeInstructions(
|
|
307
395
|
options?: RemoveInstructionsOptions,
|
|
@@ -311,6 +399,7 @@ export async function removeInstructions(
|
|
|
311
399
|
runtimes: options?.runtimes,
|
|
312
400
|
location: options?.location,
|
|
313
401
|
cwd: options?.cwd,
|
|
402
|
+
includeNativeFiles: true,
|
|
314
403
|
homeDir: options?.homeDir,
|
|
315
404
|
});
|
|
316
405
|
const entries: InstructionRemoveEntry[] = [];
|
|
@@ -10,6 +10,7 @@ const ENV_VAR_MAP: Record<SkillRuntime, string | null> = {
|
|
|
10
10
|
claude: "CLAUDE_CONFIG_DIR",
|
|
11
11
|
codex: "CODEX_HOME",
|
|
12
12
|
gemini: "GEMINI_CONFIG_DIR",
|
|
13
|
+
antigravity: null, // agy has no home override
|
|
13
14
|
cursor: "CURSOR_CONFIG_DIR",
|
|
14
15
|
opencode: "XDG_CONFIG_HOME", // affects ~/.config/opencode/
|
|
15
16
|
pi: "PI_HOME",
|
|
@@ -22,6 +23,9 @@ const HOME_SUBPATH_MAP: Record<SkillRuntime, string[]> = {
|
|
|
22
23
|
claude: [".claude"],
|
|
23
24
|
codex: [".codex"],
|
|
24
25
|
gemini: [".gemini"],
|
|
26
|
+
// Shared with Gemini CLI: agy reads its global GEMINI.md from ~/.gemini and
|
|
27
|
+
// keeps its own settings and skills in ~/.gemini/antigravity-cli.
|
|
28
|
+
antigravity: [".gemini"],
|
|
25
29
|
cursor: [".cursor"],
|
|
26
30
|
opencode: [".config", "opencode"],
|
|
27
31
|
pi: [".pi"],
|
package/src/utils/skills.ts
CHANGED
|
@@ -2,13 +2,13 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
|
|
5
|
-
export type SkillRuntime = "claude" | "codex" | "gemini" | "cursor" | "opencode" | "pi";
|
|
5
|
+
export type SkillRuntime = "claude" | "codex" | "gemini" | "antigravity" | "cursor" | "opencode" | "pi";
|
|
6
6
|
|
|
7
7
|
export type SkillLocation = "global" | "workspace";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The two standard skill discovery channels:
|
|
11
|
-
* - "agents": ~/.agents/skills/ or {cwd}/.agents/skills/ — scanned by Codex, Gemini, Cursor, OpenCode, Pi
|
|
11
|
+
* - "agents": ~/.agents/skills/ or {cwd}/.agents/skills/ — scanned by Codex, Gemini, Antigravity, Cursor, OpenCode, Pi
|
|
12
12
|
* - "claude": ~/.claude/skills/ or {cwd}/.claude/skills/ — scanned by Claude Code (the only agent that doesn't scan .agents/)
|
|
13
13
|
*/
|
|
14
14
|
export type SkillChannel = "agents" | "claude";
|
|
@@ -23,6 +23,12 @@ const NATIVE_DIRS: Record<string, { global: string; workspace: (cwd: string) =>
|
|
|
23
23
|
global: path.join(os.homedir(), ".gemini", "skills"),
|
|
24
24
|
workspace: (cwd) => path.join(cwd, ".gemini", "skills"),
|
|
25
25
|
},
|
|
26
|
+
// Antigravity reads workspace skills from the standard .agents/skills
|
|
27
|
+
// channel; only its global folder is its own.
|
|
28
|
+
antigravity: {
|
|
29
|
+
global: path.join(os.homedir(), ".gemini", "antigravity-cli", "skills"),
|
|
30
|
+
workspace: (cwd) => path.join(cwd, ".agents", "skills"),
|
|
31
|
+
},
|
|
26
32
|
cursor: {
|
|
27
33
|
global: path.join(os.homedir(), ".cursor", "skills"),
|
|
28
34
|
workspace: (cwd) => path.join(cwd, ".cursor", "skills"),
|
|
@@ -139,7 +145,14 @@ function resolveTargets(
|
|
|
139
145
|
): Array<{ target: string; skillsHome: string }> {
|
|
140
146
|
const targets = resolveStandardPaths(location, cwd);
|
|
141
147
|
if (includeNativeDirs) {
|
|
142
|
-
|
|
148
|
+
// A runtime whose native folder is a standard channel (Antigravity's
|
|
149
|
+
// workspace .agents/skills) is already covered by that channel.
|
|
150
|
+
const seen = new Set(targets.map((t) => t.skillsHome));
|
|
151
|
+
for (const native of resolveNativePaths(location, cwd)) {
|
|
152
|
+
if (seen.has(native.skillsHome)) continue;
|
|
153
|
+
seen.add(native.skillsHome);
|
|
154
|
+
targets.push(native);
|
|
155
|
+
}
|
|
143
156
|
}
|
|
144
157
|
return targets;
|
|
145
158
|
}
|
|
@@ -152,7 +165,7 @@ function resolveTargets(
|
|
|
152
165
|
* Install skill directories into agent discovery paths via idempotent symlinks.
|
|
153
166
|
*
|
|
154
167
|
* By default, installs into the two standard channels:
|
|
155
|
-
* - `.agents/skills/` — discovered by Codex, Gemini, Cursor, OpenCode, Pi
|
|
168
|
+
* - `.agents/skills/` — discovered by Codex, Gemini, Antigravity, Cursor, OpenCode, Pi
|
|
156
169
|
* - `.claude/skills/` — discovered by Claude Code
|
|
157
170
|
*
|
|
158
171
|
* Set `includeNativeDirs: true` to also install into each runtime's
|