openmerit 0.1.12 → 0.3.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/CHANGELOG.md +14 -0
- package/README.md +11 -27
- package/dist/pi/scout-extension.mjs +23 -10
- package/dist/terminal/scout-update.json +1 -1
- package/dist/terminal/src/browser.mjs +2 -4
- package/dist/terminal/src/cli.mjs +6 -56
- package/dist/terminal/src/install.mjs +3 -2
- package/dist/terminal/src/scout-instructions.js +23 -10
- package/dist/terminal/src/scout-pi.js +1 -1
- package/dist/terminal/src/scout.js +181 -0
- package/dist/terminal/src/telemetry.mjs +8 -60
- package/docs/agent-evaluations.md +8 -148
- package/docs/commands.md +4 -86
- package/docs/getting-started.md +2 -16
- package/docs/index.md +4 -27
- package/docs/inference-terminal.md +13 -655
- package/docs/pi-extension.md +1 -1
- package/docs/roadmap.md +4 -17
- package/docs/scout.md +34 -251
- package/docs/security.md +8 -33
- package/docs/testing.md +7 -47
- package/docs/troubleshooting.md +3 -3
- package/package.json +13 -46
- package/dist/core/src/index.d.ts +0 -101
- package/dist/core/src/index.js +0 -1649
- package/dist/core/src/store.d.ts +0 -35
- package/dist/core/src/store.js +0 -102
- package/dist/eval/src/adapters.mjs +0 -112
- package/dist/eval/src/cli.mjs +0 -151
- package/dist/eval/src/config.mjs +0 -108
- package/dist/eval/src/process.mjs +0 -44
- package/dist/eval/src/report.mjs +0 -59
- package/dist/eval/src/runner.mjs +0 -123
- package/dist/eval/src/scout.mjs +0 -48
- package/dist/protocol/src/index.d.ts +0 -484
- package/dist/protocol/src/index.js +0 -47
- package/dist/protocol/src/schemas.d.ts +0 -576
- package/dist/protocol/src/schemas.js +0 -280
- package/dist/terminal/public/app.js +0 -298
- package/dist/terminal/public/brands/anthropic.png +0 -0
- package/dist/terminal/public/brands/baai.png +0 -0
- package/dist/terminal/public/brands/baseten.png +0 -0
- package/dist/terminal/public/brands/cerebras.png +0 -0
- package/dist/terminal/public/brands/cohere.png +0 -0
- package/dist/terminal/public/brands/deepseek.ico +0 -0
- package/dist/terminal/public/brands/google.png +0 -0
- package/dist/terminal/public/brands/groq.ico +0 -0
- package/dist/terminal/public/brands/huggingface.svg +0 -8
- package/dist/terminal/public/brands/lm-studio.png +0 -0
- package/dist/terminal/public/brands/meta.ico +0 -0
- package/dist/terminal/public/brands/mistral.png +0 -0
- package/dist/terminal/public/brands/nomic.png +0 -0
- package/dist/terminal/public/brands/ollama.png +0 -0
- package/dist/terminal/public/brands/openai.png +0 -0
- package/dist/terminal/public/brands/openrouter.png +0 -0
- package/dist/terminal/public/brands/qwen.png +0 -0
- package/dist/terminal/public/brands/vllm.ico +0 -0
- package/dist/terminal/public/brands/vllm.png +0 -0
- package/dist/terminal/public/favicon.svg +0 -1
- package/dist/terminal/public/flow.css +0 -1
- package/dist/terminal/public/flow.js +0 -770
- package/dist/terminal/public/index.html +0 -22
- package/dist/terminal/public/styles.css +0 -1046
- package/dist/terminal/src/activity-merge.mjs +0 -66
- package/dist/terminal/src/collect.mjs +0 -370
- package/dist/terminal/src/coverage.mjs +0 -33
- package/dist/terminal/src/demo-policy.mjs +0 -205
- package/dist/terminal/src/discovery.mjs +0 -99
- package/dist/terminal/src/enterprise-sample.mjs +0 -151
- package/dist/terminal/src/hardware.mjs +0 -57
- package/dist/terminal/src/history.mjs +0 -32
- package/dist/terminal/src/log-continuity.mjs +0 -26
- package/dist/terminal/src/project-activity.mjs +0 -157
- package/dist/terminal/src/repository-background.mjs +0 -37
- package/dist/terminal/src/repository-index.mjs +0 -267
- package/dist/terminal/src/repository-policy.mjs +0 -3
- package/dist/terminal/src/repository-syntax.mjs +0 -236
- package/dist/terminal/src/repository-worker.mjs +0 -15
- package/dist/terminal/src/sample.mjs +0 -171
- package/dist/terminal/src/scout-credentials.d.ts +0 -12
- package/dist/terminal/src/scout-credentials.ts +0 -44
- package/dist/terminal/src/scout-instructions.d.ts +0 -5
- package/dist/terminal/src/scout-instructions.ts +0 -106
- package/dist/terminal/src/scout-pi.d.ts +0 -44
- package/dist/terminal/src/scout-pi.ts +0 -76
- package/dist/terminal/src/server.mjs +0 -95
- package/dist/terminal/src/service-registry.mjs +0 -78
- package/dist/terminal/src/services.mjs +0 -44
- package/dist/terminal/src/topology.mjs +0 -30
- package/dist/terminal/src/updates.d.ts +0 -62
- package/dist/terminal/src/updates.ts +0 -313
- package/docs/adapter-guide.md +0 -191
- package/docs/architecture.md +0 -61
- package/docs/automation.md +0 -76
- package/docs/budgets.md +0 -39
- package/docs/demo-backfill.md +0 -31
- package/docs/demo-fieldkit.md +0 -49
- package/docs/demo-placement.md +0 -32
- package/docs/demo-spam.md +0 -17
- package/docs/demo-support.md +0 -44
- package/docs/demo.md +0 -74
- package/docs/first-trial.md +0 -62
- package/docs/interactive-demo.md +0 -118
- package/docs/lifecycle.md +0 -32
- package/docs/metrics-and-evidence.md +0 -48
- package/docs/operations.md +0 -40
- package/docs/pareto-spec.md +0 -76
- package/docs/ux-reference.md +0 -32
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { ScoutScope } from './scout-credentials.ts';
|
|
2
|
-
export declare const piVersion = "1.5.0";
|
|
3
|
-
type RecordValue = Record<string, unknown>;
|
|
4
|
-
export declare function piPaths(root: string, scope: ScoutScope): {
|
|
5
|
-
skill: string;
|
|
6
|
-
rule: string;
|
|
7
|
-
connection: string;
|
|
8
|
-
record: string;
|
|
9
|
-
legacyRule: string;
|
|
10
|
-
mcpFile: string;
|
|
11
|
-
mcpScope: ScoutScope;
|
|
12
|
-
agentDir: string;
|
|
13
|
-
};
|
|
14
|
-
export declare function inspectPi(root: string, scope: ScoutScope): Promise<{
|
|
15
|
-
paths: {
|
|
16
|
-
skill: string;
|
|
17
|
-
rule: string;
|
|
18
|
-
connection: string;
|
|
19
|
-
record: string;
|
|
20
|
-
legacyRule: string;
|
|
21
|
-
mcpFile: string;
|
|
22
|
-
mcpScope: ScoutScope;
|
|
23
|
-
agentDir: string;
|
|
24
|
-
};
|
|
25
|
-
record: RecordValue;
|
|
26
|
-
owned: boolean;
|
|
27
|
-
current: string | null;
|
|
28
|
-
installedSkill: string | null;
|
|
29
|
-
installedRule: string | null;
|
|
30
|
-
serverOwned: boolean;
|
|
31
|
-
skillOwned: boolean;
|
|
32
|
-
ruleOwned: boolean;
|
|
33
|
-
updates: {
|
|
34
|
-
saved: null;
|
|
35
|
-
active: boolean;
|
|
36
|
-
conflicts: string[];
|
|
37
|
-
};
|
|
38
|
-
legacyRule: string;
|
|
39
|
-
shadowed: boolean;
|
|
40
|
-
}>;
|
|
41
|
-
export declare function piConflicts(state: Awaited<ReturnType<typeof inspectPi>>): string[];
|
|
42
|
-
export declare function configurePi(root: string, scope: ScoutScope, credentialsPath: string, ids: RecordValue): Promise<void>;
|
|
43
|
-
export declare function removePi(state: Awaited<ReturnType<typeof inspectPi>>): Promise<void>;
|
|
44
|
-
export {};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import { readFile, mkdir, writeFile, rm } from 'node:fs/promises';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
|
-
import { dirname, join, resolve } from 'node:path';
|
|
5
|
-
import type { ScoutScope } from './scout-credentials.ts';
|
|
6
|
-
import { scoutSkill } from './scout-instructions.ts';
|
|
7
|
-
|
|
8
|
-
export const piVersion = '1.5.0';
|
|
9
|
-
type RecordValue = Record<string, unknown>;
|
|
10
|
-
const hash = (value: string) => createHash('sha256').update(value).digest('hex');
|
|
11
|
-
const optional = async (path: string): Promise<string | null> => {
|
|
12
|
-
try { return await readFile(path, 'utf8'); }
|
|
13
|
-
catch (error) { if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return null; throw error; }
|
|
14
|
-
};
|
|
15
|
-
async function object(path: string): Promise<RecordValue> {
|
|
16
|
-
const value: unknown = JSON.parse(await optional(path) ?? '{}');
|
|
17
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`Invalid Scout installation metadata at ${path}; nothing changed.`);
|
|
18
|
-
return value as RecordValue;
|
|
19
|
-
}
|
|
20
|
-
export function piPaths(root: string, scope: ScoutScope) {
|
|
21
|
-
const agentDir = resolve(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), '.pi/agent'));
|
|
22
|
-
const base = scope === 'user' ? agentDir : join(root, '.pi');
|
|
23
|
-
return { skill: join(base, 'skills/openmerit-scout/SKILL.md'), rule: join(base, 'extensions/openmerit-scout.ts'),
|
|
24
|
-
connection: join(base, 'extensions/openmerit-scout.json'), record: join(base, 'openmerit-install.json'),
|
|
25
|
-
legacyRule: join(base, 'rules/openmerit.md'), mcpFile: join(base, 'mcp.json'), mcpScope: scope, agentDir };
|
|
26
|
-
}
|
|
27
|
-
export async function inspectPi(root: string, scope: ScoutScope) {
|
|
28
|
-
const paths = piPaths(root, scope);
|
|
29
|
-
const otherPaths = piPaths(root, scope === 'user' ? 'project' : 'user');
|
|
30
|
-
const [record, connection, installedSkill, installedRule, mcp, otherMcp, other] = await Promise.all([
|
|
31
|
-
object(paths.record), optional(paths.connection), optional(paths.skill), optional(paths.rule), object(paths.mcpFile),
|
|
32
|
-
object(otherPaths.mcpFile), optional(otherPaths.rule),
|
|
33
|
-
]);
|
|
34
|
-
const owned = record.version === 1 && record.client === 'pi' && record.scope === scope;
|
|
35
|
-
const matches = (value: string | null, field: string) => value === null || owned && [record[field], record[`pending${field[0]!.toUpperCase()}${field.slice(1)}`]].includes(hash(value));
|
|
36
|
-
return { paths, record, owned, current: connection, installedSkill, installedRule,
|
|
37
|
-
serverOwned: matches(connection, 'serverHash'), skillOwned: matches(installedSkill, 'skillHash'), ruleOwned: matches(installedRule, 'ruleHash'),
|
|
38
|
-
updates: { saved: null, active: false, conflicts: [] as string[] }, legacyRule: 'none',
|
|
39
|
-
shadowed: other !== null || [mcp, otherMcp].some(config => config.mcpServers && typeof config.mcpServers === 'object' && 'openmerit' in config.mcpServers),
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
export function piConflicts(state: Awaited<ReturnType<typeof inspectPi>>): string[] {
|
|
43
|
-
return [
|
|
44
|
-
...(!state.serverOwned ? [`${state.paths.connection} was edited or is unowned.`] : []),
|
|
45
|
-
...(!state.skillOwned ? [`${state.paths.skill} was edited or is unowned.`] : []),
|
|
46
|
-
...(!state.ruleOwned ? [`${state.paths.rule} was edited or is unowned.`] : []),
|
|
47
|
-
...(state.shadowed ? ['Another Pi Scout installation or an openmerit MCP entry shadows this installation. Resolve the duplicate before installing.'] : []),
|
|
48
|
-
];
|
|
49
|
-
}
|
|
50
|
-
export async function configurePi(root: string, scope: ScoutScope, credentialsPath: string, ids: RecordValue) {
|
|
51
|
-
const state = await inspectPi(root, scope);
|
|
52
|
-
const conflicts = piConflicts(state);
|
|
53
|
-
if (conflicts.length) throw new Error(conflicts.join('\n'));
|
|
54
|
-
const extension = await readFile(new URL('../../../dist/pi/scout-extension.mjs', import.meta.url), 'utf8');
|
|
55
|
-
const connection = `${JSON.stringify({ credentialsPath }, null, 2)}\n`;
|
|
56
|
-
const skill = scoutSkill('pi');
|
|
57
|
-
const next = { version: 1, client: 'pi', scope, ...ids, serverHash: hash(connection), skillHash: hash(skill), ruleHash: hash(extension), installedAt: new Date().toISOString() };
|
|
58
|
-
const save = async (path: string, value: string | RecordValue) => {
|
|
59
|
-
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
60
|
-
await writeFile(path, typeof value === 'string' ? value : `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
|
|
61
|
-
};
|
|
62
|
-
// Record intent before writing files, so interrupted installs are recoverable.
|
|
63
|
-
await save(state.paths.record, { ...state.record, version: 1, client: 'pi', scope,
|
|
64
|
-
pendingServerHash: next.serverHash, pendingSkillHash: next.skillHash, pendingRuleHash: next.ruleHash });
|
|
65
|
-
await save(state.paths.connection, connection);
|
|
66
|
-
await save(state.paths.skill, skill);
|
|
67
|
-
await save(state.paths.rule, extension);
|
|
68
|
-
await save(state.paths.record, next);
|
|
69
|
-
}
|
|
70
|
-
export async function removePi(state: Awaited<ReturnType<typeof inspectPi>>) {
|
|
71
|
-
if (state.current !== null && state.serverOwned) await rm(state.paths.connection, { force: true });
|
|
72
|
-
if (state.installedRule !== null && state.ruleOwned) await rm(state.paths.rule, { force: true });
|
|
73
|
-
if (state.installedSkill !== null && state.skillOwned) await rm(state.paths.skill, { force: true });
|
|
74
|
-
// Keep ownership of edited files, so a future install does not adopt them.
|
|
75
|
-
if (state.serverOwned && state.ruleOwned && state.skillOwned) await rm(state.paths.record, { force: true });
|
|
76
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { createServer } from 'node:http';
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { basename } from 'node:path';
|
|
5
|
-
import { createCollector, loadConfig } from './collect.mjs';
|
|
6
|
-
import { sampleSnapshot } from './sample.mjs';
|
|
7
|
-
import { telemetryEnabled, terminalProperties, sendTerminalEvent, errorProperties } from './telemetry.mjs';
|
|
8
|
-
|
|
9
|
-
const assets = new Map([['/', ['index.html', 'text/html']], ['/app.js', ['app.js', 'text/javascript']], ['/flow.js', ['flow.js', 'text/javascript']], ['/flow.css', ['flow.css', 'text/css']], ['/styles.css', ['styles.css', 'text/css']], ['/favicon.svg', ['favicon.svg', 'image/svg+xml']]]);
|
|
10
|
-
for (const file of ['baseten.png', 'groq.ico', 'cerebras.png', 'qwen.png', 'meta.ico', 'nomic.png', 'baai.png', 'ollama.png', 'lm-studio.png', 'vllm.png', 'anthropic.png', 'google.png', 'deepseek.ico', 'cohere.png', 'openai.png', 'mistral.png', 'openrouter.png', 'huggingface.svg']) assets.set(`/brands/${file}`, [`brands/${file}`, file.endsWith('.svg') ? 'image/svg+xml' : file.endsWith('.ico') ? 'image/x-icon' : 'image/png']);
|
|
11
|
-
|
|
12
|
-
export async function startTerminal({ root = process.cwd(), port = 4310, interval = 15, demo = false, collect: customCollect, config, fallbackPort = false } = {}) {
|
|
13
|
-
const collect = customCollect ?? (demo ? async () => sampleSnapshot() : createCollector(root, config ?? await loadConfig(root), interval, {backgroundIndex:true}));
|
|
14
|
-
let snapshot={schemaVersion:1,mode:demo?'sample':'live',loading:true,observedAt:null,interval,project:{name:basename(root),scope:'Current project'},models:[],records:[],sources:[],inventory:[],hosts:[],deployments:[],capacityPools:[],taskSummaries:[]};
|
|
15
|
-
let busy=false,closed=false,timer;
|
|
16
|
-
const update=async()=>{
|
|
17
|
-
if(busy||closed)return;
|
|
18
|
-
busy=true;
|
|
19
|
-
try {const next=await collect();if(!closed)snapshot=next;}
|
|
20
|
-
catch (error) {if(snapshot.loading)snapshot={...snapshot,loadError:true};sendTerminalEvent('terminal_error',{code:'collection_error',...errorProperties(error)});}
|
|
21
|
-
finally {busy=false;}
|
|
22
|
-
};
|
|
23
|
-
const server = createServer(async (request, response) => {
|
|
24
|
-
const address = server.address();
|
|
25
|
-
const validHosts = [`127.0.0.1:${address.port}`, `localhost:${address.port}`];
|
|
26
|
-
const host = request.headers.host;
|
|
27
|
-
const headers = { 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'no-referrer', 'Content-Security-Policy': "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; font-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'none'" };
|
|
28
|
-
const send = (code, body, type = 'application/json') => { response.writeHead(code, { ...headers, 'Content-Type': `${type}; charset=utf-8` }); response.end(body); };
|
|
29
|
-
if (!validHosts.includes(host) || (request.headers.origin && request.headers.origin !== `http://${host}`) || request.headers['sec-fetch-site'] === 'cross-site') return send(403, '{"error":"Local access only"}');
|
|
30
|
-
let path;
|
|
31
|
-
try { path = new URL(request.url, `http://${host}`).pathname; }
|
|
32
|
-
catch { return send(400, '{"error":"Invalid request URL"}'); }
|
|
33
|
-
if (path === '/api/analytics' && request.method === 'POST') {
|
|
34
|
-
if (!telemetryEnabled() || request.headers.dnt === '1' || request.headers['sec-gpc'] === '1' || request.headers['x-scout-analytics'] === 'off') return send(204, '');
|
|
35
|
-
if (request.headers['content-type'] !== 'application/json') return send(415, '{}');
|
|
36
|
-
if (Number(request.headers['content-length']) > 2048) return send(413, '{}');
|
|
37
|
-
let size = 0, body = '', done = false;
|
|
38
|
-
const timeout = setTimeout(() => { if (!done) { done = true; send(408, '{}'); request.destroy(); } }, 1000);
|
|
39
|
-
request.on('data', chunk => {
|
|
40
|
-
if (done) return;
|
|
41
|
-
size += chunk.length;
|
|
42
|
-
if (size > 2048) { done = true; clearTimeout(timeout); send(413, '{}'); request.resume(); return; }
|
|
43
|
-
body += chunk.toString('utf8');
|
|
44
|
-
});
|
|
45
|
-
request.on('error', () => { done = true; clearTimeout(timeout); });
|
|
46
|
-
request.on('end', () => {
|
|
47
|
-
clearTimeout(timeout); if (done) return; done = true;
|
|
48
|
-
try {
|
|
49
|
-
const value = JSON.parse(body), properties = terminalProperties(value.event, value.properties);
|
|
50
|
-
if (!properties) return send(400, '{}');
|
|
51
|
-
sendTerminalEvent(value.event, properties);
|
|
52
|
-
return send(204, '');
|
|
53
|
-
} catch { return send(400, '{}'); }
|
|
54
|
-
});
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (request.method !== 'GET') return send(405, '{"error":"Read-only terminal"}');
|
|
58
|
-
if (path === '/api/snapshot') {
|
|
59
|
-
const data=collect.current?.(snapshot)??snapshot;
|
|
60
|
-
const revision=new URL(request.url,`http://${host}`).searchParams.get('repositoryRevision');
|
|
61
|
-
// Browser refreshes can reuse the unchanged reference catalog. The plain
|
|
62
|
-
// API still returns a self-contained snapshot for agents and scripts.
|
|
63
|
-
if(data.repository&&revision!==null&&String(data.repository.revision)===revision) {
|
|
64
|
-
const {references,...repository}=data.repository;
|
|
65
|
-
return send(200,JSON.stringify({...data,repository}));
|
|
66
|
-
}
|
|
67
|
-
return send(200,JSON.stringify(data));
|
|
68
|
-
}
|
|
69
|
-
if (path === '/api/sample') return send(200, JSON.stringify(sampleSnapshot()));
|
|
70
|
-
if (path === '/api/info') return send(200, JSON.stringify({ version: 1, defaultMode: demo ? 'sample' : 'live', interval, telemetry: telemetryEnabled() }));
|
|
71
|
-
const asset = assets.get(path);
|
|
72
|
-
if (!asset) return send(404, '{"error":"Not found"}');
|
|
73
|
-
try { return send(200, await readFile(fileURLToPath(new URL(`../public/${asset[0]}`, import.meta.url))), asset[1]); }
|
|
74
|
-
catch { return send(500, '{"error":"Terminal assets unavailable. Run npm run build."}'); }
|
|
75
|
-
});
|
|
76
|
-
server.on('close', () => { closed=true;clearInterval(timer);void collect.close?.(); });
|
|
77
|
-
const listen = selectedPort => new Promise((resolve, reject) => {
|
|
78
|
-
const onError = error => { server.off('listening', onListening); reject(error); };
|
|
79
|
-
const onListening = () => { server.off('error', onError); resolve(); };
|
|
80
|
-
server.once('error', onError); server.once('listening', onListening);
|
|
81
|
-
server.listen(selectedPort, '127.0.0.1');
|
|
82
|
-
});
|
|
83
|
-
try {
|
|
84
|
-
try { await listen(port); }
|
|
85
|
-
catch (error) {
|
|
86
|
-
if (!fallbackPort || error.code !== 'EADDRINUSE') throw error;
|
|
87
|
-
await listen(0);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch (error) { closed=true;clearInterval(timer);await collect.close?.();throw error; }
|
|
91
|
-
// Listen first. The browser can paint while collection and indexing proceed.
|
|
92
|
-
void update();
|
|
93
|
-
timer=setInterval(update,interval*1000);timer.unref();
|
|
94
|
-
return { server, url: `http://127.0.0.1:${server.address().port}`, close: async () => { closed=true;clearInterval(timer);await collect.close?.();await new Promise(resolve=>{server.close(resolve);server.closeIdleConnections();}); } };
|
|
95
|
-
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
// Shared, dependency-free service identities for passive discovery and the UI.
|
|
2
|
-
// Endpoint formats checked against official API docs on 2026-10-10; see the guide.
|
|
3
|
-
// A match identifies configuration, never connectivity, usage, a model, or a host.
|
|
4
|
-
const define = (key, name, type, options = {}) => Object.freeze({ key, name, type, aliases: [], credentials: [], endpoints: [], hosts: [], ...options });
|
|
5
|
-
const label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?';
|
|
6
|
-
const subdomains = suffix => new RegExp(`^(?:${label}\\.)+${suffix.replaceAll('.', '\\.')}$`);
|
|
7
|
-
const resource = suffix => new RegExp(`^${label}\\.${suffix.replaceAll('.', '\\.')}$`);
|
|
8
|
-
const region = '[a-z]{2}(?:-[a-z]+)+-\\d+';
|
|
9
|
-
const hf = subdomains('endpoints.huggingface.cloud');
|
|
10
|
-
const azureOpenAI = resource('openai.azure.com');
|
|
11
|
-
const azureFoundry = resource('services.ai.azure.com');
|
|
12
|
-
const bedrock = new RegExp(`^bedrock-runtime(?:-fips)?\\.${region}\\.amazonaws\\.com(?:\\.cn)?$`);
|
|
13
|
-
const sagemaker = new RegExp(`^runtime(?:-fips)?\\.sagemaker\\.${region}\\.amazonaws\\.com(?:\\.cn)?$`);
|
|
14
|
-
const vertex = /^(?:[a-z]+(?:-[a-z]+)*\d+-)?aiplatform\.googleapis\.com$/;
|
|
15
|
-
const maas = new RegExp(`^${label}\\.(?:cn-beijing|cn-hongkong|ap-southeast-1|ap-northeast-1)\\.maas\\.aliyuncs\\.com$`);
|
|
16
|
-
|
|
17
|
-
export const SERVICE_REGISTRY = Object.freeze([
|
|
18
|
-
define('openai', 'OpenAI', 'model-api', { aliases: ['openai api'], credentials: ['OPENAI_API_KEY'], endpoints: ['OPENAI_BASE_URL', 'OPENAI_API_BASE'], hosts: ['api.openai.com'] }),
|
|
19
|
-
define('anthropic', 'Anthropic', 'model-api', { aliases: ['anthropic api'], credentials: ['ANTHROPIC_API_KEY'], endpoints: ['ANTHROPIC_BASE_URL'], hosts: ['api.anthropic.com'] }),
|
|
20
|
-
define('openrouter', 'OpenRouter', 'gateway', { credentials: ['OPENROUTER_API_KEY'], endpoints: ['OPENROUTER_BASE_URL'], hosts: ['openrouter.ai'] }),
|
|
21
|
-
define('google', 'Google', 'model-api', { aliases: ['google ai', 'google api', 'gemini api'], credentials: ['GOOGLE_GENERATIVE_AI_API_KEY', 'GEMINI_API_KEY'], endpoints: ['GOOGLE_GEMINI_BASE_URL', 'GEMINI_BASE_URL'], hosts: ['generativelanguage.googleapis.com'] }),
|
|
22
|
-
define('groq', 'Groq', 'inference-cloud', { aliases: ['groq api', 'groqcloud'], credentials: ['GROQ_API_KEY'], endpoints: ['GROQ_BASE_URL'], hosts: ['api.groq.com'] }),
|
|
23
|
-
define('cerebras', 'Cerebras', 'inference-cloud', { aliases: ['cerebras api'], credentials: ['CEREBRAS_API_KEY'], endpoints: ['CEREBRAS_BASE_URL'], hosts: ['api.cerebras.ai'] }),
|
|
24
|
-
define('baseten', 'Baseten', 'inference-cloud', { aliases: ['baseten api'], credentials: ['BASETEN_API_KEY'], endpoints: ['BASETEN_BASE_URL'], hosts: ['inference.baseten.co'], match: url => /^model-[a-z0-9-]+\.api\.baseten\.co$/.test(url.hostname), redact: host => host.replace(/^model-[a-z0-9-]+(?=\.api\.baseten\.co$)/, 'model-…') }),
|
|
25
|
-
// Hub and general cloud tokens can authorize unrelated products or downloads.
|
|
26
|
-
define('huggingface-endpoints', 'Hugging Face Endpoints', 'inference-cloud', { aliases: ['hugging face inference endpoints'], endpoints: ['HF_INFERENCE_ENDPOINT', 'HF_ENDPOINT_URL', 'HUGGINGFACE_ENDPOINT_URL'], match: url => hf.test(url.hostname), redact: () => '….endpoints.huggingface.cloud' }),
|
|
27
|
-
define('huggingface-router', 'Hugging Face Router', 'router', { aliases: ['hugging face inference providers'], hosts: ['router.huggingface.co'] }),
|
|
28
|
-
define('mistral', 'Mistral', 'model-api', { aliases: ['mistral ai', 'mistral api'], credentials: ['MISTRAL_API_KEY'], endpoints: ['MISTRAL_BASE_URL', 'MISTRAL_SERVER_URL'], hosts: ['api.mistral.ai'] }),
|
|
29
|
-
define('deepseek', 'DeepSeek', 'model-api', { aliases: ['deepseek api'], credentials: ['DEEPSEEK_API_KEY'], endpoints: ['DEEPSEEK_BASE_URL'], hosts: ['api.deepseek.com'] }),
|
|
30
|
-
define('cohere', 'Cohere', 'model-api', { aliases: ['cohere api'], credentials: ['COHERE_API_KEY', 'CO_API_KEY'], endpoints: ['COHERE_BASE_URL'], hosts: ['api.cohere.ai', 'api.cohere.com'] }),
|
|
31
|
-
define('xai', 'xAI', 'model-api', { aliases: ['xai api'], credentials: ['XAI_API_KEY'], endpoints: ['XAI_BASE_URL'], hosts: ['api.x.ai'] }),
|
|
32
|
-
define('voyage', 'Voyage AI', 'model-api', { aliases: ['voyage', 'voyageai'], credentials: ['VOYAGE_API_KEY'], endpoints: ['VOYAGE_BASE_URL'], hosts: ['api.voyageai.com'] }),
|
|
33
|
-
define('jina', 'Jina AI', 'model-api', { aliases: ['jina'], endpoints: ['JINA_BASE_URL'], hosts: ['api.jina.ai'] }),
|
|
34
|
-
define('together', 'Together AI', 'inference-cloud', { aliases: ['together'], credentials: ['TOGETHER_API_KEY'], endpoints: ['TOGETHER_BASE_URL'], hosts: ['api.together.ai', 'api.together.xyz'] }),
|
|
35
|
-
define('fireworks', 'Fireworks AI', 'inference-cloud', { aliases: ['fireworks'], credentials: ['FIREWORKS_API_KEY'], endpoints: ['FIREWORKS_BASE_URL'], hosts: ['api.fireworks.ai'] }),
|
|
36
|
-
define('deepinfra', 'DeepInfra', 'inference-cloud', { credentials: ['DEEPINFRA_API_KEY', 'DEEPINFRA_API_TOKEN'], endpoints: ['DEEPINFRA_BASE_URL'], hosts: ['api.deepinfra.com'] }),
|
|
37
|
-
define('replicate', 'Replicate', 'inference-cloud', { credentials: ['REPLICATE_API_TOKEN'], endpoints: ['REPLICATE_BASE_URL'], hosts: ['api.replicate.com'] }),
|
|
38
|
-
define('nvidia-api', 'NVIDIA API Catalog', 'inference-cloud', { endpoints: ['NVIDIA_BASE_URL', 'NVIDIA_NIM_BASE_URL'], hosts: ['integrate.api.nvidia.com'] }),
|
|
39
|
-
define('sambanova', 'SambaNova', 'inference-cloud', { credentials: ['SAMBANOVA_API_KEY'], endpoints: ['SAMBANOVA_BASE_URL'], hosts: ['api.sambanova.ai'] }),
|
|
40
|
-
define('novita', 'Novita AI', 'inference-cloud', { aliases: ['novita'], credentials: ['NOVITA_API_KEY'], endpoints: ['NOVITA_BASE_URL'], hosts: ['api.novita.ai'] }),
|
|
41
|
-
define('nebius', 'Nebius Token Factory', 'inference-cloud', { aliases: ['nebius ai studio'], endpoints: ['NEBIUS_BASE_URL'], hosts: ['api.studio.nebius.ai', 'api.studio.nebius.com'] }),
|
|
42
|
-
define('siliconflow', 'SiliconFlow', 'inference-cloud', { credentials: ['SILICONFLOW_API_KEY'], endpoints: ['SILICONFLOW_BASE_URL'], hosts: ['api.siliconflow.com', 'api.siliconflow.cn'] }),
|
|
43
|
-
define('azure-openai', 'Azure OpenAI', 'inference-cloud', { endpoints: ['AZURE_OPENAI_ENDPOINT', 'AZURE_OPENAI_BASE_URL', 'AZURE_OPENAI_API_BASE'], match: url => azureOpenAI.test(url.hostname), redact: () => '….openai.azure.com' }),
|
|
44
|
-
define('azure-foundry', 'Azure AI Foundry', 'inference-cloud', { aliases: ['microsoft foundry'], endpoints: ['AZURE_AI_ENDPOINT', 'AZURE_AI_INFERENCE_ENDPOINT', 'AZURE_INFERENCE_ENDPOINT'], match: url => azureFoundry.test(url.hostname) && /^\/(?:$|(?:openai|models)(?:\/|$))/.test(url.pathname), redact: () => '….services.ai.azure.com' }),
|
|
45
|
-
define('bedrock', 'Amazon Bedrock', 'inference-cloud', { aliases: ['aws bedrock', 'bedrock'], credentials: ['AWS_BEARER_TOKEN_BEDROCK'], endpoints: ['AWS_ENDPOINT_URL_BEDROCK_RUNTIME', 'BEDROCK_ENDPOINT_URL', 'BEDROCK_BASE_URL'], match: url => bedrock.test(url.hostname) }),
|
|
46
|
-
define('sagemaker', 'Amazon SageMaker', 'inference-cloud', { aliases: ['aws sagemaker', 'sagemaker'], endpoints: ['AWS_ENDPOINT_URL_SAGEMAKER_RUNTIME', 'SAGEMAKER_ENDPOINT_URL', 'SAGEMAKER_BASE_URL'], match: url => sagemaker.test(url.hostname) }),
|
|
47
|
-
define('vertex', 'Google Vertex AI', 'inference-cloud', { aliases: ['vertex ai', 'vertex'], endpoints: ['VERTEX_AI_ENDPOINT', 'VERTEX_AI_BASE_URL'], match: url => vertex.test(url.hostname) }),
|
|
48
|
-
define('cloudflare-workers-ai', 'Cloudflare Workers AI', 'inference-cloud', { aliases: ['workers ai'], endpoints: ['CLOUDFLARE_WORKERS_AI_BASE_URL'], match: url => url.hostname === 'api.cloudflare.com' && /^\/client\/v4\/accounts\/[a-zA-Z0-9_-]+\/ai(?:\/|$)/.test(url.pathname) }),
|
|
49
|
-
define('cloudflare-ai-gateway', 'Cloudflare AI Gateway', 'gateway', { endpoints: ['CLOUDFLARE_AI_GATEWAY_URL'], hosts: ['gateway.ai.cloudflare.com'] }),
|
|
50
|
-
define('portkey', 'Portkey', 'gateway', { credentials: ['PORTKEY_API_KEY'], endpoints: ['PORTKEY_BASE_URL', 'PORTKEY_GATEWAY_URL'], hosts: ['api.portkey.ai'] }),
|
|
51
|
-
// The generic AI_GATEWAY_API_KEY name is shared by applications; require the URL.
|
|
52
|
-
define('vercel-ai-gateway', 'Vercel AI Gateway', 'gateway', { endpoints: ['AI_GATEWAY_BASE_URL', 'VERCEL_AI_GATEWAY_BASE_URL'], hosts: ['ai-gateway.vercel.sh'] }),
|
|
53
|
-
define('dashscope', 'Alibaba Cloud Model Studio', 'inference-cloud', { aliases: ['dashscope', 'alibaba model studio'], credentials: ['DASHSCOPE_API_KEY'], endpoints: ['DASHSCOPE_BASE_URL', 'DASHSCOPE_API_BASE'], hosts: ['dashscope.aliyuncs.com', 'dashscope-intl.aliyuncs.com', 'dashscope-us.aliyuncs.com', 'cn-hongkong.dashscope.aliyuncs.com'], match: url => maas.test(url.hostname), redact: host => maas.test(host) ? '….maas.aliyuncs.com' : host }),
|
|
54
|
-
define('minimax', 'MiniMax', 'model-api', { credentials: ['MINIMAX_API_KEY'], endpoints: ['MINIMAX_BASE_URL'], hosts: ['api.minimax.io'] }),
|
|
55
|
-
define('moonshot', 'Moonshot AI', 'model-api', { aliases: ['moonshot', 'kimi api'], credentials: ['MOONSHOT_API_KEY'], endpoints: ['MOONSHOT_BASE_URL'], hosts: ['api.moonshot.ai'] }),
|
|
56
|
-
define('zai', 'Z.AI', 'model-api', { aliases: ['zai'], credentials: ['ZAI_API_KEY'], endpoints: ['ZAI_BASE_URL'], hosts: ['api.z.ai'] }),
|
|
57
|
-
// Perplexity shares a host across products. Never turn a Search URL into Sonar.
|
|
58
|
-
define('perplexity-sonar', 'Perplexity Sonar', 'model-api', { endpoints: ['PERPLEXITY_BASE_URL'], match: url => url.hostname === 'api.perplexity.ai' && /^(?:\/?|\/chat\/completions\/?|\/v1\/?)$/.test(url.pathname) }),
|
|
59
|
-
define('perplexity-router', 'Perplexity Router', 'router', { match: url => url.hostname === 'api.perplexity.ai' && /^\/router(?:\/|$)/.test(url.pathname) }),
|
|
60
|
-
define('ollama', 'Ollama', 'runtime'),
|
|
61
|
-
define('vllm', 'vLLM', 'runtime'),
|
|
62
|
-
define('lm-studio', 'LM Studio', 'runtime'),
|
|
63
|
-
]);
|
|
64
|
-
const names = new Map(SERVICE_REGISTRY.flatMap(service => [service.name, ...service.aliases].map(name => [name.toLowerCase(), service])));
|
|
65
|
-
export const registeredService = name => names.get(String(name ?? '').trim().toLowerCase()) ?? null;
|
|
66
|
-
export const SERVICE_GROUPS = Object.freeze(SERVICE_REGISTRY.filter(service => service.credentials.length || service.endpoints.length));
|
|
67
|
-
export const SERVICE_CREDENTIAL_FIELDS = Object.freeze(SERVICE_GROUPS.flatMap(service => service.credentials));
|
|
68
|
-
export const SERVICE_ENDPOINT_FIELDS = Object.freeze(SERVICE_GROUPS.flatMap(service => service.endpoints));
|
|
69
|
-
|
|
70
|
-
export function endpointService(value) {
|
|
71
|
-
if (typeof value !== 'string' || value.length > 2048 || /[\x00-\x20\x7f\\]/.test(value)) return null;
|
|
72
|
-
try {
|
|
73
|
-
const url = new URL(value);
|
|
74
|
-
if (url.protocol !== 'https:' || url.username || url.password || url.port) return null;
|
|
75
|
-
const service = SERVICE_REGISTRY.find(service => service.hosts.includes(url.hostname) || service.match?.(url));
|
|
76
|
-
return service ? { service, endpointHost: service.redact?.(url.hostname) ?? url.hostname } : null;
|
|
77
|
-
} catch { return null; }
|
|
78
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// Configuration evidence only. Never call services or return credentials or raw URLs.
|
|
2
|
-
import { SERVICE_GROUPS, endpointService } from './service-registry.mjs';
|
|
3
|
-
|
|
4
|
-
export const serviceFromEndpoint = value => endpointService(value)?.service.name ?? null;
|
|
5
|
-
export function configuredServices(env = {}) {
|
|
6
|
-
const found = new Map(), unrecognizedEndpoints = [];
|
|
7
|
-
const present = key => typeof env[key] === 'string' && env[key].trim().length > 0;
|
|
8
|
-
const add = (service, field, basis, endpointHost) => {
|
|
9
|
-
const { name, type } = service;
|
|
10
|
-
const kind = ['router', 'gateway'].includes(type) ? type : 'provider';
|
|
11
|
-
const item = found.get(name) ?? { name, kind, ...(['model-api', 'inference-cloud'].includes(type) ? { serviceType: type } : {}), location: 'cloud', state: 'configured', sourceId: 'environment', evidence: [] };
|
|
12
|
-
item.evidence.push({ field, basis, ...(endpointHost ? { endpointHost } : {}) }); found.set(name, item);
|
|
13
|
-
};
|
|
14
|
-
for (const group of SERVICE_GROUPS) {
|
|
15
|
-
const overrides = group.endpoints.filter(present);
|
|
16
|
-
if (overrides.length) {
|
|
17
|
-
for (const field of overrides) {
|
|
18
|
-
const match = endpointService(env[field]);
|
|
19
|
-
if (match) add(match.service, field, 'endpoint-host', match.endpointHost);
|
|
20
|
-
else unrecognizedEndpoints.push(field);
|
|
21
|
-
}
|
|
22
|
-
// An override can repurpose the SDK credential. Unknown/local overrides
|
|
23
|
-
// must suppress the vendor fallback too; the key name is not enough.
|
|
24
|
-
} else for (const field of group.credentials.filter(present)) add(group, field, 'credential-name');
|
|
25
|
-
}
|
|
26
|
-
return { services: [...found.values()], unrecognizedEndpoints };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Multiple reports for one model/service may disagree. Preserve the gap rather
|
|
30
|
-
// than choosing whichever file or record happened to be processed first.
|
|
31
|
-
export function modelCreators(models, reports) {
|
|
32
|
-
const evidence = new Map();
|
|
33
|
-
for (const item of [...models, ...reports]) {
|
|
34
|
-
if (!item.modelCreator) continue;
|
|
35
|
-
const key = JSON.stringify([item.provider, item.model ?? item.name]);
|
|
36
|
-
const entry = evidence.get(key) ?? { names: new Set(), sources: new Set() };
|
|
37
|
-
entry.names.add(item.modelCreator); if (item.sourceId) entry.sources.add(item.sourceId);
|
|
38
|
-
evidence.set(key, entry);
|
|
39
|
-
}
|
|
40
|
-
return models.map(model => {
|
|
41
|
-
const entry = evidence.get(JSON.stringify([model.provider, model.name]));
|
|
42
|
-
return { ...model, modelCreator: entry?.names.size === 1 ? [...entry.names][0] : null, creatorConflict: (entry?.names.size ?? 0) > 1, creatorSources: [...(entry?.sources ?? [])] };
|
|
43
|
-
});
|
|
44
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// Bounded metadata contracts. Never retain endpoints, credentials, or payloads.
|
|
2
|
-
const label = value => typeof value === 'string' ? value.slice(0, 160).replace(/[\x00-\x1f]/g, '') : '';
|
|
3
|
-
const count = value => typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : null;
|
|
4
|
-
const timestamp = value => typeof value === 'string' && Number.isFinite(Date.parse(value)) ? new Date(value).toISOString() : null;
|
|
5
|
-
const list = (value, limit) => Array.isArray(value) ? value.slice(0, limit).filter(item => item && typeof item === 'object') : [];
|
|
6
|
-
export const poolIds = value => Array.isArray(value) ? [...new Set(value.slice(0, 16).map(label).filter(Boolean))] : [];
|
|
7
|
-
export function normalizePath(value) {
|
|
8
|
-
// Do not turn a broken path into an apparently complete chain by dropping hops.
|
|
9
|
-
if (!Array.isArray(value) || !value.length || value.length > 8 || value.some(hop => !hop || !label(hop.name))) return [];
|
|
10
|
-
return value.map(hop => ({ name: label(hop.name), kind: ['direct', 'local', 'router', 'gateway'].includes(hop.kind) ? hop.kind : 'unknown' }));
|
|
11
|
-
}
|
|
12
|
-
function unique(items) {
|
|
13
|
-
const counts = new Map();
|
|
14
|
-
for (const item of items) counts.set(item.id, (counts.get(item.id) ?? 0) + 1);
|
|
15
|
-
return items.filter(item => item.id && counts.get(item.id) === 1);
|
|
16
|
-
}
|
|
17
|
-
export function normalizeDeployments(value) {
|
|
18
|
-
return unique(list(value, 200).map(item => ({ id: label(item.id), name: label(item.name) || label(item.id), model: label(item.model), modelCreator: label(item.modelCreator) || null, servingMode: ['shared', 'dedicated', 'self-hosted'].includes(item.servingMode) ? item.servingMode : 'unknown', provider: label(item.provider) || 'Unattributed', location: ['local', 'private', 'cloud'].includes(item.location) ? item.location : 'unknown', poolIds: poolIds(item.poolIds), hostId: label(item.hostId) || null, sourceId: 'inventory', state: 'configured' })).filter(item => item.model && !item.id.startsWith('runtime:')));
|
|
19
|
-
}
|
|
20
|
-
export function normalizePools(value) {
|
|
21
|
-
return unique(list(value, 100).map(item => ({ id: label(item.id), name: label(item.name) || label(item.id), kind: ['quota', 'runtime', 'gateway'].includes(item.kind) ? item.kind : 'unknown', windowSeconds: count(item.windowSeconds), sourceId: 'capacity', observedAt: null, metrics: {} })).filter(item => !item.id.startsWith('runtime:')));
|
|
22
|
-
}
|
|
23
|
-
export function normalizeCapacity(value, now = Date.now()) {
|
|
24
|
-
return unique(list(value?.pools, 100).map(item => {
|
|
25
|
-
const observedAt = timestamp(item.observedAt);
|
|
26
|
-
const metrics = Object.fromEntries(['running', 'waiting', 'concurrencyLimit', 'requestsUsed', 'requestsLimit', 'tokensUsed', 'tokensLimit', 'cachePercent', 'memoryUsedBytes', 'memoryTotalBytes'].map(key => [key, count(item.metrics?.[key])]).filter(([, value]) => value !== null));
|
|
27
|
-
if (metrics.cachePercent > 100) delete metrics.cachePercent;
|
|
28
|
-
return { id: label(item.id), observedAt, resetAt: timestamp(item.resetAt), metrics };
|
|
29
|
-
}).filter(item => item.observedAt && Date.parse(item.observedAt) <= now));
|
|
30
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
type ObjectValue = Record<string, unknown>;
|
|
2
|
-
interface UpdateRecord {
|
|
3
|
-
enabled: boolean;
|
|
4
|
-
version: string;
|
|
5
|
-
runnerHash: string;
|
|
6
|
-
hook: ObjectValue;
|
|
7
|
-
}
|
|
8
|
-
export declare const updateContract = "scout-recommend-v1";
|
|
9
|
-
export declare function updatePaths(base: string, scope: string): {
|
|
10
|
-
record: string;
|
|
11
|
-
runner: string;
|
|
12
|
-
settings: string;
|
|
13
|
-
state: string;
|
|
14
|
-
backup: string;
|
|
15
|
-
skill: string;
|
|
16
|
-
rule: string;
|
|
17
|
-
};
|
|
18
|
-
export declare function withUpdateLock<T>(base: string, work: () => Promise<T>): Promise<T>;
|
|
19
|
-
export declare function inspectUpdates(base: string, scope: string, record: ObjectValue): Promise<{
|
|
20
|
-
paths: {
|
|
21
|
-
record: string;
|
|
22
|
-
runner: string;
|
|
23
|
-
settings: string;
|
|
24
|
-
state: string;
|
|
25
|
-
backup: string;
|
|
26
|
-
skill: string;
|
|
27
|
-
rule: string;
|
|
28
|
-
};
|
|
29
|
-
saved: UpdateRecord | null;
|
|
30
|
-
runner: string | null;
|
|
31
|
-
settings: ObjectValue;
|
|
32
|
-
hooks: ObjectValue;
|
|
33
|
-
conflicts: string[];
|
|
34
|
-
active: boolean;
|
|
35
|
-
}>;
|
|
36
|
-
export declare function configureUpdates(base: string, scope: string, record: ObjectValue, version: string, enabled: boolean): Promise<{
|
|
37
|
-
enabled: boolean;
|
|
38
|
-
version: string;
|
|
39
|
-
runnerHash: string;
|
|
40
|
-
hook: {
|
|
41
|
-
hooks: {
|
|
42
|
-
type: string;
|
|
43
|
-
command: string;
|
|
44
|
-
async: boolean;
|
|
45
|
-
timeout: number;
|
|
46
|
-
}[];
|
|
47
|
-
};
|
|
48
|
-
}>;
|
|
49
|
-
export declare function removeUpdates(base: string, scope: string, record: ObjectValue): Promise<void>;
|
|
50
|
-
export declare function recoverUpdates(base: string, scope: string): Promise<void>;
|
|
51
|
-
export interface UpdateResult {
|
|
52
|
-
status: string;
|
|
53
|
-
message: string;
|
|
54
|
-
version?: string;
|
|
55
|
-
}
|
|
56
|
-
export declare function runUpdate(base: string, scope: string, options?: {
|
|
57
|
-
hook?: boolean;
|
|
58
|
-
enabled?: boolean;
|
|
59
|
-
fetcher?: typeof fetch;
|
|
60
|
-
now?: number;
|
|
61
|
-
}): Promise<UpdateResult>;
|
|
62
|
-
export {};
|