@aiwg/cli 2026.9.2 → 2026.9.4
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 +21 -0
- package/agentic/code/providers/antigravity/provider-contract.v1.json +121 -0
- package/agentic/code/providers/capability-matrix.yaml +87 -1
- package/agentic/code/providers/model-capabilities.v1.json +31 -0
- package/agentic/code/providers/model-catalog.v1.json +29 -0
- package/agentic/code/providers/omp/README.md +58 -0
- package/agentic/code/providers/omp/aiwg-bridge.ts +52 -0
- package/dist/src/agents/agent-deployer.js +18 -0
- package/dist/src/agents/agent-packager.js +25 -0
- package/dist/src/artifacts/backends/sqlite-backend.js +18 -10
- package/dist/src/artifacts/query-engine.js +30 -0
- package/dist/src/cli/agent-spawn.js +13 -2
- package/dist/src/cli/handlers/help.js +3 -1
- package/dist/src/cli/handlers/init.js +2 -0
- package/dist/src/cli/handlers/models.js +1 -1
- package/dist/src/cli/handlers/runtime-info.js +8 -1
- package/dist/src/cli/handlers/session.js +5 -4
- package/dist/src/cli/handlers/sessions.js +26 -9
- package/dist/src/cli/handlers/setup.js +9 -2
- package/dist/src/cli/handlers/steward.js +13 -2
- package/dist/src/cli/handlers/subcommands.js +11 -0
- package/dist/src/cli/handlers/team.js +68 -7
- package/dist/src/cli/handlers/use.js +158 -9
- package/dist/src/cli/scope-resolver.js +7 -0
- package/dist/src/config/aiwg-config.js +1 -0
- package/dist/src/dataset/fortemi-live-qualification.d.ts +53 -0
- package/dist/src/dataset/fortemi-live-qualification.js +297 -0
- package/dist/src/dataset/index.d.ts +1 -0
- package/dist/src/dataset/index.js +1 -0
- package/dist/src/mcp/cli.mjs +30 -1
- package/dist/src/mcp/omp-config.mjs +128 -0
- package/dist/src/mcp/registry.js +45 -5
- package/dist/src/mcp/registry.mjs +29 -6
- package/dist/src/models/model-capabilities.v1.json +31 -0
- package/dist/src/models/model-catalog.v1.json +29 -0
- package/dist/src/models/model-discovery.js +46 -5
- package/dist/src/models/provider-policy.js +5 -3
- package/dist/src/plugin/skill-command-translator.js +2 -0
- package/dist/src/providers/capability-matrix.yaml +87 -1
- package/dist/src/providers/omp-agent.mjs +40 -0
- package/dist/src/providers/omp-diagnostics.mjs +15 -0
- package/dist/src/providers/omp-paths.mjs +38 -0
- package/dist/src/providers/provider-definitions.js +83 -0
- package/dist/src/providers/provider-definitions.mjs +25 -1
- package/dist/src/providers/provider-inventory.js +2 -0
- package/dist/src/sessions/adapters/omp.js +203 -0
- package/dist/src/sessions/batch-import.js +7 -0
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/importer.js +4 -3
- package/dist/src/sessions/index.js +1 -0
- package/dist/src/sessions/readers.js +4 -3
- package/dist/src/sessions/workspace-discovery.js +12 -2
- package/dist/src/skills/deployer.js +21 -1
- package/dist/src/smiths/agentsmith/generator.js +1 -0
- package/dist/src/smiths/context-pipeline/parallelism-section.js +2 -0
- package/dist/src/smiths/context-pipeline/provider-policy.js +2 -2
- package/dist/src/smiths/context-pipeline/workspace-context.js +2 -2
- package/dist/src/storage/backends/fortemi.js +142 -17
- package/dist/src/storage/fortemi-qualification-receipt.js +206 -0
- package/dist/src/storage/fortemi-qualification.js +67 -6
- package/dist/src/storage/index.js +1 -0
- package/package.json +2 -1
- package/schemas/dataset/fortemi-live-qualification-receipt.v1.schema.json +132 -0
- package/tools/agents/deploy-agents.mjs +6 -3
- package/tools/agents/providers/antigravity.mjs +147 -0
- package/tools/agents/providers/omp.d.mts +4 -0
- package/tools/agents/providers/omp.mjs +256 -0
- package/tools/providers/antigravity-transport.mjs +124 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/** OMP native deployment, verified against 18.1.10 (5964a0f). */
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import YAML from 'yaml';
|
|
6
|
+
import { resolveOmpPaths } from '../../../src/providers/omp-paths.mjs';
|
|
7
|
+
import { collectFrameworkArtifacts, getAddonFiles, isKernelSkill, listMdFiles, listSkillDirs, normalizeDeploymentMode, resolveAiwgRoot } from './base.mjs';
|
|
8
|
+
export const name = 'omp';
|
|
9
|
+
export const aliases = ['oh-my-pi'];
|
|
10
|
+
export const paths = { agents: '.omp/agents', commands: '.omp/prompts', skills: '.agents/skills', rules: '.omp/rules' };
|
|
11
|
+
export const kernelSkillsPath = '.agents/skills';
|
|
12
|
+
export const support = { agents: 'native', commands: 'native', skills: 'native', rules: 'native' };
|
|
13
|
+
export const capabilities = { skills: true, rules: true, yamlFormat: true, aggregatedOutput: false, homeDirectoryDeploy: false, parallelCommandAndSkillSurfaces: true };
|
|
14
|
+
const hash = value => createHash('sha256').update(value).digest('hex');
|
|
15
|
+
function parse(content) {
|
|
16
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
17
|
+
return match ? { metadata: YAML.parse(match[1]) || {}, body: match[2] } : { metadata: {}, body: content };
|
|
18
|
+
}
|
|
19
|
+
const render = (metadata, body) => `---\n${YAML.stringify(metadata)}---\n\n${body.trim()}\n`;
|
|
20
|
+
function diagnostic(opts, message) { opts.diagnostics?.push(message); if (!opts.quiet) console.warn(`OMP: ${message}`); }
|
|
21
|
+
import { transformAgent, mapModel } from '../../../src/providers/omp-agent.mjs';
|
|
22
|
+
export { transformAgent, mapModel };
|
|
23
|
+
const array = value => Array.isArray(value) ? value : typeof value === 'string' ? value.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
24
|
+
export function transformCommand(src, content, opts = {}) {
|
|
25
|
+
const { metadata: m, body } = parse(content);
|
|
26
|
+
const metadata = { description: String(m.description || `AIWG ${path.basename(src, '.md')} prompt`) };
|
|
27
|
+
if (m['argument-hint']) metadata['argument-hint'] = m['argument-hint'];
|
|
28
|
+
for (const key of Object.keys(m)) if (!['description','argument-hint','name','platforms','allowed-tools','model'].includes(key)) diagnostic(opts, `prompt ${path.basename(src)}: metadata ${key} omitted`);
|
|
29
|
+
const normalized = body.replace(/\$\{(ARGUMENTS|@|\d+)\}/g, (_, name) => `$${name}`);
|
|
30
|
+
return render(metadata, normalized + (m['argument-hint'] && !/\$(?:ARGUMENTS|@|\d+)/.test(normalized) ? '\n\nInvocation arguments: $@' : ''));
|
|
31
|
+
}
|
|
32
|
+
export function transformRule(src, content) {
|
|
33
|
+
const { metadata: m, body } = parse(content);
|
|
34
|
+
const out = { description: String(m.description || path.basename(src, '.md')) };
|
|
35
|
+
for (const key of ['enabled','globs','alwaysApply','condition','astCondition','scope','agents','interruptMode']) if (m[key] !== undefined) out[key] = m[key];
|
|
36
|
+
if (out.globs === undefined && m.paths) out.globs = array(m.paths);
|
|
37
|
+
return render(out, body);
|
|
38
|
+
}
|
|
39
|
+
function roots(target, opts = {}) {
|
|
40
|
+
const resolved = resolveOmpPaths({ cwd: target, env: opts.env, home: opts.home });
|
|
41
|
+
const user = opts.global || opts.user || opts.scope === 'user';
|
|
42
|
+
return { native: user ? resolved.agentDir : path.join(target, '.omp'), agents: user ? resolved.resourceDirs.agents : path.join(target, '.omp/agents'), kernel: user ? path.join(resolved.agentDir, 'skills') : path.join(target, kernelSkillsPath) };
|
|
43
|
+
}
|
|
44
|
+
function assertNoSymlink(destination) {
|
|
45
|
+
let current = path.resolve(destination);
|
|
46
|
+
while (true) {
|
|
47
|
+
try { if (fs.lstatSync(current).isSymbolicLink()) throw new Error(`OMP preserves symlink destination: ${current}`); }
|
|
48
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
49
|
+
const parent = path.dirname(current); if (parent === current) break; current = parent;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function readReceipt(dir) {
|
|
53
|
+
const filename = path.join(dir, '.aiwg-manifest.json');
|
|
54
|
+
assertNoSymlink(filename);
|
|
55
|
+
if (!fs.existsSync(filename)) return { managed: {} };
|
|
56
|
+
let parsed;
|
|
57
|
+
try { parsed = JSON.parse(fs.readFileSync(filename, 'utf8')); }
|
|
58
|
+
catch { throw new Error(`OMP preserves malformed receipt: ${filename}`); }
|
|
59
|
+
if (!parsed || !parsed.managed || typeof parsed.managed !== 'object' || Array.isArray(parsed.managed)
|
|
60
|
+
|| Object.values(parsed.managed).some(entry => !entry || typeof entry !== 'object' || Array.isArray(entry))) {
|
|
61
|
+
throw new Error(`OMP preserves malformed receipt: ${filename}`);
|
|
62
|
+
}
|
|
63
|
+
return parsed;
|
|
64
|
+
}
|
|
65
|
+
/** Reconcile only the named standard skill's unchanged OMP-owned files. */
|
|
66
|
+
function removeStandardSkillCopy(destination, opts) {
|
|
67
|
+
if (!fs.existsSync(destination)) return 0;
|
|
68
|
+
assertNoSymlink(destination);
|
|
69
|
+
const plans = [];
|
|
70
|
+
function plan(dir) {
|
|
71
|
+
const receipt = readReceipt(dir);
|
|
72
|
+
const files = [];
|
|
73
|
+
for (const [filename, entry] of Object.entries(receipt.managed)) {
|
|
74
|
+
if (path.basename(filename) !== filename || entry.provider !== 'omp' || entry.transformation !== 'omp-skill') continue;
|
|
75
|
+
const file = path.join(dir, filename);
|
|
76
|
+
if (!fs.existsSync(file) || fs.lstatSync(file).isSymbolicLink() || !fs.lstatSync(file).isFile()) continue;
|
|
77
|
+
if (entry.hash === `sha256:${hash(fs.readFileSync(file))}`) files.push(filename);
|
|
78
|
+
else diagnostic(opts, `preserved modified standard skill file ${file}`);
|
|
79
|
+
}
|
|
80
|
+
plans.push({ dir, receipt, files });
|
|
81
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) if (entry.isDirectory()) plan(path.join(dir, entry.name));
|
|
82
|
+
}
|
|
83
|
+
plan(destination);
|
|
84
|
+
const count = plans.reduce((sum, item) => sum + item.files.length, 0);
|
|
85
|
+
if (opts.dryRun) return count;
|
|
86
|
+
for (const { dir, receipt, files } of plans.reverse()) {
|
|
87
|
+
for (const filename of files) { fs.unlinkSync(path.join(dir, filename)); delete receipt.managed[filename]; }
|
|
88
|
+
if (files.length) {
|
|
89
|
+
const receiptPath = path.join(dir, '.aiwg-manifest.json');
|
|
90
|
+
if (Object.keys(receipt.managed).length) fs.writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
91
|
+
else fs.unlinkSync(receiptPath);
|
|
92
|
+
}
|
|
93
|
+
// Directory ownership is not recorded; leave empty directories intact.
|
|
94
|
+
}
|
|
95
|
+
return count;
|
|
96
|
+
}
|
|
97
|
+
/** Per-file hash receipts preserve operator creations and subsequent edits, including --force. */
|
|
98
|
+
function writeOwned(dest, content, source, opts = {}, transformation = 'identity') {
|
|
99
|
+
assertNoSymlink(dest);
|
|
100
|
+
const dir = path.dirname(dest); const receiptPath = path.join(dir, '.aiwg-manifest.json');
|
|
101
|
+
const receipt = readReceipt(dir);
|
|
102
|
+
const base = path.basename(dest); const prior = receipt.managed[base];
|
|
103
|
+
if (fs.existsSync(dest)) {
|
|
104
|
+
const current = fs.readFileSync(dest);
|
|
105
|
+
if (current.equals(Buffer.from(content))) return 0;
|
|
106
|
+
if (!prior || prior.provider !== 'omp' || prior.hash !== `sha256:${hash(current)}`) { diagnostic(opts, `preserved operator file ${dest}`); return 0; }
|
|
107
|
+
}
|
|
108
|
+
if (opts.dryRun) return 1;
|
|
109
|
+
fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(dest, content);
|
|
110
|
+
receipt.managed[base] = { hash: `sha256:${hash(content)}`, source, provider: name, transformation, version: opts.deployVersion || 'unknown', ...(opts.diagnostics?.length ? { degraded: true, diagnostics: [...opts.diagnostics] } : {}) };
|
|
111
|
+
fs.writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`); return 1;
|
|
112
|
+
}
|
|
113
|
+
export function deploySkillSupportAsset(source, destination, opts = {}) {
|
|
114
|
+
return writeOwned(destination, fs.readFileSync(source), source, opts, 'omp-skill');
|
|
115
|
+
}
|
|
116
|
+
function deployType(files, target, opts, type, transform) {
|
|
117
|
+
let count = 0; const seen = new Set();
|
|
118
|
+
for (const src of files) {
|
|
119
|
+
const basename = path.basename(src);
|
|
120
|
+
if (seen.has(basename)) throw new Error(`OMP ${type} collision: ${basename}`);
|
|
121
|
+
seen.add(basename);
|
|
122
|
+
const diagnostics = [];
|
|
123
|
+
const transformed = transform(src, fs.readFileSync(src, 'utf8'), { ...opts, diagnostics });
|
|
124
|
+
opts.diagnostics?.push(...diagnostics);
|
|
125
|
+
count += writeOwned(path.join(type === 'agents' ? roots(target, opts).agents : path.join(roots(target, opts).native, type), basename), transformed, src, { ...opts, diagnostics }, `omp-${type}`);
|
|
126
|
+
}
|
|
127
|
+
return count;
|
|
128
|
+
}
|
|
129
|
+
export const deployAgents = (files, target, opts = {}) => deployType(files, target, opts, 'agents', transformAgent);
|
|
130
|
+
export const deployCommands = (files, target, opts = {}) => deployType(files, target, opts, 'prompts', transformCommand);
|
|
131
|
+
export const deployRules = (files, target, opts = {}) => deployType(files, target, opts, 'rules', transformRule);
|
|
132
|
+
export function deploySkills(dirs, target, opts = {}) {
|
|
133
|
+
let count = 0; const seen = new Map(); const root = roots(target, opts);
|
|
134
|
+
for (const dir of [...new Set(dirs)]) {
|
|
135
|
+
const base = path.basename(dir); const previous = seen.get(base);
|
|
136
|
+
if (previous && previous !== dir) throw new Error(`OMP skill collision: ${base} (${previous}, ${dir})`);
|
|
137
|
+
seen.set(base, dir);
|
|
138
|
+
if (!isKernelSkill(dir) && !opts.copyStandardSkills) {
|
|
139
|
+
const removed = removeStandardSkillCopy(path.join(root.kernel, base), opts);
|
|
140
|
+
if (removed && !opts.quiet) console.log(`OMP: ${opts.dryRun ? 'would remove' : 'removed'} ${removed} unchanged standard skill files for ${base}`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// Exactly one native level, sharing kernel directory for both modes avoids double discovery.
|
|
144
|
+
const dest = path.join(root.kernel, base);
|
|
145
|
+
function copy(current, relative = '') {
|
|
146
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
147
|
+
const src = path.join(current, entry.name); const rel = path.join(relative, entry.name);
|
|
148
|
+
if (entry.isDirectory()) copy(src, rel);
|
|
149
|
+
else if (entry.isFile()) count += writeOwned(path.join(dest, rel), fs.readFileSync(src), src, opts, 'omp-skill');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
copy(dir);
|
|
153
|
+
}
|
|
154
|
+
return count;
|
|
155
|
+
}
|
|
156
|
+
export function createAgentsMd(target, srcRoot, dryRun = false) {
|
|
157
|
+
const dest = path.join(target, '.omp/AGENTS.md');
|
|
158
|
+
const start = '<!-- AIWG:omp-bootstrap:start -->'; const end = '<!-- AIWG:omp-bootstrap:end -->';
|
|
159
|
+
const block = `${start}\n@../WORKSPACE.md\n@../AIWG.md\n${end}`;
|
|
160
|
+
assertNoSymlink(dest);
|
|
161
|
+
const receipt = readReceipt(path.dirname(dest));
|
|
162
|
+
const current = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf8') : '';
|
|
163
|
+
const starts = current.split(start).length - 1; const ends = current.split(end).length - 1;
|
|
164
|
+
if (starts !== ends || starts > 1 || (starts && current.indexOf(start) > current.indexOf(end))) throw new Error(`OMP preserves malformed bootstrap markers: ${dest}`);
|
|
165
|
+
const content = current.includes(start) && current.includes(end) ? current.slice(0, current.indexOf(start)) + block + current.slice(current.indexOf(end) + end.length) : `${current}${current ? '\n\n' : ''}${block}\n`;
|
|
166
|
+
if (!dryRun) {
|
|
167
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
168
|
+
if (content !== current) fs.writeFileSync(dest, content);
|
|
169
|
+
const receiptPath = path.join(path.dirname(dest), '.aiwg-manifest.json');
|
|
170
|
+
// Block ownership must never claim the operator's complete context file.
|
|
171
|
+
receipt.managed['AGENTS.md'] = { blockHash: `sha256:${hash(block)}`, source: 'omp-bootstrap', provider: 'omp', transformation: 'managed-block' };
|
|
172
|
+
fs.writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/** Remove only unchanged OMP receipt entries and the native bootstrap block. */
|
|
176
|
+
export function uninstall(target, opts = {}) {
|
|
177
|
+
let removed = 0;
|
|
178
|
+
const root = roots(target, opts);
|
|
179
|
+
const context = path.join(root.native, 'AGENTS.md');
|
|
180
|
+
assertNoSymlink(context);
|
|
181
|
+
const contextReceipt = readReceipt(root.native).managed['AGENTS.md'];
|
|
182
|
+
const contextPrior = fs.existsSync(context) ? fs.readFileSync(context, 'utf8') : '';
|
|
183
|
+
const contextStart = '<!-- AIWG:omp-bootstrap:start -->'; const contextEnd = '<!-- AIWG:omp-bootstrap:end -->';
|
|
184
|
+
const starts = contextPrior.split(contextStart).length - 1; const ends = contextPrior.split(contextEnd).length - 1;
|
|
185
|
+
if (starts !== ends || starts > 1 || (starts && contextPrior.indexOf(contextStart) > contextPrior.indexOf(contextEnd))) throw new Error(`OMP preserves malformed bootstrap markers: ${context}`);
|
|
186
|
+
const contextBlock = contextPrior.match(/<!-- AIWG:omp-bootstrap:start -->[\s\S]*?<!-- AIWG:omp-bootstrap:end -->/)?.[0];
|
|
187
|
+
function clean(dir) {
|
|
188
|
+
if (!fs.existsSync(dir)) return;
|
|
189
|
+
assertNoSymlink(dir);
|
|
190
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) if (entry.isDirectory() && !entry.isSymbolicLink()) clean(path.join(dir, entry.name));
|
|
191
|
+
const receiptPath = path.join(dir, '.aiwg-manifest.json');
|
|
192
|
+
if (!fs.existsSync(receiptPath)) return;
|
|
193
|
+
const receipt = readReceipt(dir);
|
|
194
|
+
for (const [name, entry] of Object.entries(receipt.managed || {})) {
|
|
195
|
+
if (entry.provider !== 'omp' || path.basename(name) !== name) continue;
|
|
196
|
+
const file = path.join(dir, name);
|
|
197
|
+
if (fs.existsSync(file) && !fs.lstatSync(file).isSymbolicLink() && fs.lstatSync(file).isFile() && entry.hash === `sha256:${hash(fs.readFileSync(file))}`) {
|
|
198
|
+
removed++; if (!opts.dryRun) { fs.unlinkSync(file); delete receipt.managed[name]; }
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (!opts.dryRun) {
|
|
202
|
+
if (Object.keys(receipt.managed).length) fs.writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
203
|
+
else fs.unlinkSync(receiptPath);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
clean(root.native); if (!root.agents.startsWith(`${root.native}${path.sep}`)) clean(root.agents); if (root.kernel !== path.join(root.native, 'skills')) clean(root.kernel);
|
|
207
|
+
if (contextBlock && contextReceipt?.provider === 'omp' && contextReceipt.blockHash === `sha256:${hash(contextBlock)}` && !opts.dryRun) {
|
|
208
|
+
const next = contextPrior.replace(`${contextBlock}\n`, '').replace(contextBlock, '');
|
|
209
|
+
if (next.trim()) fs.writeFileSync(context, next); else fs.unlinkSync(context);
|
|
210
|
+
const receipt = readReceipt(root.native); delete receipt.managed['AGENTS.md'];
|
|
211
|
+
const receiptPath = path.join(root.native, '.aiwg-manifest.json');
|
|
212
|
+
if (Object.keys(receipt.managed).length) fs.writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
213
|
+
else if (fs.existsSync(receiptPath)) fs.unlinkSync(receiptPath);
|
|
214
|
+
}
|
|
215
|
+
return removed;
|
|
216
|
+
}
|
|
217
|
+
export const extensionBridge = 'agentic/code/providers/omp/aiwg-bridge.ts';
|
|
218
|
+
export function deployExtensionBridge(target, opts = {}) {
|
|
219
|
+
const src = path.join(resolveAiwgRoot(opts.srcRoot) || opts.srcRoot, extensionBridge);
|
|
220
|
+
return writeOwned(path.join(roots(target, opts).native, 'extensions/aiwg-bridge.ts'), fs.readFileSync(src, 'utf8'), src, opts, 'omp-extension');
|
|
221
|
+
}
|
|
222
|
+
export async function postDeploy(target, opts) {
|
|
223
|
+
if (opts.createAgentsMd || (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly)) createAgentsMd(target, opts.srcRoot, opts.dryRun);
|
|
224
|
+
}
|
|
225
|
+
export const getFileExtension = () => '.md';
|
|
226
|
+
export async function deploy(opts) {
|
|
227
|
+
const mode = normalizeDeploymentMode(opts.mode);
|
|
228
|
+
const include = { includeAgents: true, includeCommands: opts.deployCommands || opts.commandsOnly, includeSkills: opts.deploySkills || opts.skillsOnly, includeRules: opts.deployRules || opts.rulesOnly };
|
|
229
|
+
const directSource = ['agents', 'commands', 'skills', 'rules'].some(type => fs.existsSync(path.join(opts.srcRoot, type)));
|
|
230
|
+
const framework = directSource ? {
|
|
231
|
+
agents: listMdFiles(path.join(opts.srcRoot, 'agents')),
|
|
232
|
+
commands: include.includeCommands ? listMdFiles(path.join(opts.srcRoot, 'commands')) : [],
|
|
233
|
+
skills: include.includeSkills ? listSkillDirs(path.join(opts.srcRoot, 'skills')) : [],
|
|
234
|
+
rules: include.includeRules ? listMdFiles(path.join(opts.srcRoot, 'rules')) : [],
|
|
235
|
+
} : collectFrameworkArtifacts(opts.srcRoot, mode, include);
|
|
236
|
+
const addon = !directSource && ['general','sdlc','both','all'].includes(mode) ? getAddonFiles(opts.srcRoot, include) : { agents: [], commands: [], skills: [], rules: [] };
|
|
237
|
+
const selected = type => {
|
|
238
|
+
const frameworkNames = new Set(framework[type].map(file => path.basename(file)));
|
|
239
|
+
const addonByName = new Map();
|
|
240
|
+
for (const file of [...addon[type]].sort()) {
|
|
241
|
+
const basename = path.basename(file);
|
|
242
|
+
if (frameworkNames.has(basename)) continue;
|
|
243
|
+
if (addonByName.has(basename)) { diagnostic(opts, `bundled ${type} duplicate ${basename}; keeping ${addonByName.get(basename)}`); continue; }
|
|
244
|
+
addonByName.set(basename, file);
|
|
245
|
+
}
|
|
246
|
+
return [...new Set([...addonByName.values(), ...framework[type]])];
|
|
247
|
+
};
|
|
248
|
+
let count = 0;
|
|
249
|
+
if (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly) count += deployAgents(selected('agents'), opts.target, opts);
|
|
250
|
+
if (include.includeCommands && !opts.skillsOnly && !opts.rulesOnly) count += deployCommands(selected('commands'), opts.target, opts);
|
|
251
|
+
if (include.includeSkills && !opts.commandsOnly && !opts.rulesOnly) count += deploySkills(selected('skills'), opts.target, opts);
|
|
252
|
+
if (include.includeRules && !opts.commandsOnly && !opts.skillsOnly) count += deployRules(selected('rules'), opts.target, opts);
|
|
253
|
+
if (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly) count += deployExtensionBridge(opts.target, opts);
|
|
254
|
+
await postDeploy(opts.target, opts); return count;
|
|
255
|
+
}
|
|
256
|
+
export default { name, aliases, paths, kernelSkillsPath, support, capabilities, mapModel, transformAgent, transformCommand, transformRule, deployAgents, deployCommands, deploySkills, deployRules, deployExtensionBridge, createAgentsMd, postDeploy, getFileExtension, deploy, uninstall };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export const ANTIGRAVITY_CONTRACT_VERSION = '1.0.0';
|
|
4
|
+
export const ANTIGRAVITY_QUALIFIED_CLI_VERSION = '1.1.26';
|
|
5
|
+
|
|
6
|
+
function nonEmptyString(value, name) {
|
|
7
|
+
if (typeof value !== 'string' || !value.trim()) throw new Error(`Antigravity ${name} must be a non-empty string`);
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildAntigravityArgs(prompt, options = {}) {
|
|
12
|
+
const args = ['-p', nonEmptyString(prompt, 'prompt')];
|
|
13
|
+
if (options.outputFormat === 'stream-json') {
|
|
14
|
+
args.push('--output-format', 'stream-json');
|
|
15
|
+
} else {
|
|
16
|
+
args.push('--output-format', 'json');
|
|
17
|
+
}
|
|
18
|
+
if (options.agent) args.push('--agent', String(options.agent));
|
|
19
|
+
if (options.model) args.push('--model', String(options.model));
|
|
20
|
+
if (options.effort) {
|
|
21
|
+
if (!['low', 'medium', 'high'].includes(options.effort)) throw new Error(`Unsupported Antigravity effort '${options.effort}'`);
|
|
22
|
+
args.push('--effort', options.effort);
|
|
23
|
+
}
|
|
24
|
+
if (options.mode) {
|
|
25
|
+
if (!['accept-edits', 'plan'].includes(options.mode)) throw new Error(`Unsupported Antigravity mode '${options.mode}'`);
|
|
26
|
+
args.push('--mode', options.mode);
|
|
27
|
+
}
|
|
28
|
+
if (options.sandbox) args.push('--sandbox');
|
|
29
|
+
if (options.continue === true) args.push('--continue');
|
|
30
|
+
if (options.conversation) args.push('--conversation', String(options.conversation));
|
|
31
|
+
if (options.dangerous === true) args.unshift('--dangerously-skip-permissions');
|
|
32
|
+
return args;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseAntigravityJson(input) {
|
|
36
|
+
let value;
|
|
37
|
+
try { value = typeof input === 'string' ? JSON.parse(input) : input; }
|
|
38
|
+
catch (error) { throw new Error(`Invalid Antigravity JSON output: ${error.message}`); }
|
|
39
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid Antigravity JSON envelope: expected object');
|
|
40
|
+
nonEmptyString(value.conversation_id, 'conversation_id');
|
|
41
|
+
nonEmptyString(value.status, 'status');
|
|
42
|
+
if (typeof value.response !== 'string') throw new Error('Invalid Antigravity JSON envelope: response must be a string');
|
|
43
|
+
if (value.error !== undefined && typeof value.error !== 'string') throw new Error('Invalid Antigravity JSON envelope: error must be a string');
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function parseAntigravityStream(input) {
|
|
48
|
+
const lines = String(input).split(/\r?\n/).filter(line => line.trim());
|
|
49
|
+
if (!lines.length) throw new Error('Invalid Antigravity stream: no events');
|
|
50
|
+
const events = lines.map((line, index) => {
|
|
51
|
+
try { return JSON.parse(line); }
|
|
52
|
+
catch (error) { throw new Error(`Invalid Antigravity stream event ${index}: ${error.message}`); }
|
|
53
|
+
});
|
|
54
|
+
if (events[0]?.event !== 'init') throw new Error('Invalid Antigravity stream: first event must be init');
|
|
55
|
+
const results = events.filter(event => event?.event === 'result');
|
|
56
|
+
if (results.length !== 1 || events.at(-1)?.event !== 'result') throw new Error('Invalid Antigravity stream: exactly one terminal result is required');
|
|
57
|
+
for (const [index, event] of events.entries()) {
|
|
58
|
+
if (!['init', 'step_update', 'result'].includes(event?.event)) throw new Error(`Invalid Antigravity stream event ${index}: unknown event`);
|
|
59
|
+
if (event.event === 'init') {
|
|
60
|
+
nonEmptyString(event.conversation_id, 'conversation_id');
|
|
61
|
+
if (!event.init || typeof event.init !== 'object' || Array.isArray(event.init)) {
|
|
62
|
+
throw new Error(`Invalid Antigravity stream event ${index}: init payload must be an object`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (event.event === 'step_update') {
|
|
66
|
+
if (!event.step_update || typeof event.step_update !== 'object' || Array.isArray(event.step_update)) {
|
|
67
|
+
throw new Error(`Invalid Antigravity stream event ${index}: step_update payload must be an object`);
|
|
68
|
+
}
|
|
69
|
+
if (!['ACTIVE', 'DONE'].includes(event.step_update.state)) {
|
|
70
|
+
throw new Error(`Invalid Antigravity stream event ${index}: unknown state`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
parseAntigravityJson(results[0].result);
|
|
75
|
+
return events;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function runAntigravity(prompt, options = {}) {
|
|
79
|
+
const timeoutMs = options.timeoutMs ?? 300_000;
|
|
80
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1) throw new Error('Antigravity timeoutMs must be a positive integer');
|
|
81
|
+
const binary = options.binary || 'agy';
|
|
82
|
+
const args = buildAntigravityArgs(prompt, options);
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const child = (options.spawnImpl || spawn)(binary, args, {
|
|
85
|
+
cwd: options.cwd,
|
|
86
|
+
env: options.env || process.env,
|
|
87
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
88
|
+
shell: false,
|
|
89
|
+
});
|
|
90
|
+
let stdout = '';
|
|
91
|
+
let stderr = '';
|
|
92
|
+
let settled = false;
|
|
93
|
+
const timer = setTimeout(() => {
|
|
94
|
+
settled = true;
|
|
95
|
+
child.kill('SIGTERM');
|
|
96
|
+
reject(new Error(`Antigravity invocation timed out after ${timeoutMs}ms`));
|
|
97
|
+
}, timeoutMs);
|
|
98
|
+
child.stdout.on('data', chunk => { stdout += chunk; });
|
|
99
|
+
child.stderr.on('data', chunk => { stderr += chunk; });
|
|
100
|
+
child.on('error', error => {
|
|
101
|
+
if (settled) return;
|
|
102
|
+
settled = true;
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
reject(new Error(`Unable to start Antigravity CLI (${binary}): ${error.message}`));
|
|
105
|
+
});
|
|
106
|
+
child.on('close', code => {
|
|
107
|
+
if (settled) return;
|
|
108
|
+
settled = true;
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
if (code !== 0) {
|
|
111
|
+
const diagnostic = stderr.trim() || 'no diagnostic output';
|
|
112
|
+
reject(new Error(`Antigravity CLI exited ${code}: ${diagnostic}`));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
const output = options.outputFormat === 'stream-json'
|
|
117
|
+
? parseAntigravityStream(stdout)
|
|
118
|
+
: parseAntigravityJson(stdout);
|
|
119
|
+
resolve({ output, stderr, exitCode: code });
|
|
120
|
+
} catch (error) { reject(error); }
|
|
121
|
+
});
|
|
122
|
+
child.stdin.end();
|
|
123
|
+
});
|
|
124
|
+
}
|