@aopslabs/aops 0.2.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/LICENSE +19 -0
- package/NOTICE +7 -0
- package/README.md +203 -0
- package/assets/agent-assets/core/SKILL.md +49 -0
- package/assets/agent-assets/core/references/agentspace/SKILL.md +78 -0
- package/assets/agent-assets/core/references/aops-cli-core/SKILL.md +108 -0
- package/assets/agent-assets/core/references/chatv3/SKILL.md +48 -0
- package/assets/agent-assets/core/references/collaborative-work/SKILL.md +85 -0
- package/assets/agent-assets/core/references/discuss/SKILL.md +66 -0
- package/assets/agent-assets/core/references/docman/SKILL.md +69 -0
- package/assets/agent-assets/core/references/hosted-chat/SKILL.md +64 -0
- package/assets/agent-assets/core/references/mission/SKILL.md +54 -0
- package/assets/agent-assets/core/references/projectman/SKILL.md +86 -0
- package/assets/agent-assets/core/references/sys/SKILL.md +36 -0
- package/assets/agent-assets/core/references/view/SKILL.md +58 -0
- package/assets/agent-assets/core/references/working-disciplines/SKILL.md +69 -0
- package/assets/agent-assets/core/user-guides/agent-assets.md +81 -0
- package/assets/agent-assets/core/user-guides/agentspace.md +1098 -0
- package/assets/agent-assets/core/user-guides/aops-cli.md +1107 -0
- package/assets/agent-assets/core/user-guides/aops-system.md +63 -0
- package/assets/agent-assets/core/user-guides/chatv3.md +113 -0
- package/assets/agent-assets/core/user-guides/docman.md +162 -0
- package/assets/agent-assets/core/user-guides/projectman.md +212 -0
- package/assets/agent-assets/core/user-guides/sys.md +94 -0
- package/assets/agent-assets/core/user-guides/working-disciplines.md +588 -0
- package/assets/agent-assets/gateway/aops/SKILL.md +14 -0
- package/assets/r6-remote-session-baseline.json +19 -0
- package/assets/skills/aops-install/SKILL.md +231 -0
- package/assets/skills/aops-install/agents/openai.yaml +4 -0
- package/dist/commands/activity.js +352 -0
- package/dist/commands/agent-profile.js +653 -0
- package/dist/commands/agent.js +710 -0
- package/dist/commands/agents-md.js +125 -0
- package/dist/commands/api.js +84 -0
- package/dist/commands/archive.js +873 -0
- package/dist/commands/artifact.js +535 -0
- package/dist/commands/assets.js +1414 -0
- package/dist/commands/auth/device-login.js +1 -0
- package/dist/commands/auth/legacy-export.js +177 -0
- package/dist/commands/auth/login.js +426 -0
- package/dist/commands/chat.js +1363 -0
- package/dist/commands/chatv3.js +1684 -0
- package/dist/commands/checkpoint.js +549 -0
- package/dist/commands/commercial-artifact.js +475 -0
- package/dist/commands/commercial-license.js +594 -0
- package/dist/commands/community-auth.js +62 -0
- package/dist/commands/community-cockpit.js +185 -0
- package/dist/commands/community-console.js +109 -0
- package/dist/commands/community-doctor.js +335 -0
- package/dist/commands/community-server.js +2062 -0
- package/dist/commands/community-setup.js +284 -0
- package/dist/commands/discuss.js +1448 -0
- package/dist/commands/doc.js +5219 -0
- package/dist/commands/experience.js +922 -0
- package/dist/commands/host.js +1127 -0
- package/dist/commands/init.js +118 -0
- package/dist/commands/memory.js +3129 -0
- package/dist/commands/mission.js +562 -0
- package/dist/commands/official-catalog.js +134 -0
- package/dist/commands/plan.js +84 -0
- package/dist/commands/playbook.js +415 -0
- package/dist/commands/pm/context.js +6 -0
- package/dist/commands/pm/index.js +701 -0
- package/dist/commands/pm/projectman.js +6266 -0
- package/dist/commands/project.js +1080 -0
- package/dist/commands/prompt.js +1024 -0
- package/dist/commands/repo-sync.js +1907 -0
- package/dist/commands/resource.js +527 -0
- package/dist/commands/skill.js +1211 -0
- package/dist/commands/start-disciplines.js +510 -0
- package/dist/commands/start.js +1343 -0
- package/dist/commands/target.js +309 -0
- package/dist/commands/version.js +42 -0
- package/dist/commands/view.js +1852 -0
- package/dist/launchers/cockpit.js +28 -0
- package/dist/launchers/options.js +74 -0
- package/dist/launchers/server.js +41 -0
- package/dist/lib/agent-assets/envelope.js +83 -0
- package/dist/lib/agent-assets/gateway.js +15 -0
- package/dist/lib/agent-assets/guards.js +23 -0
- package/dist/lib/agent-assets/hosted-discovery.js +148 -0
- package/dist/lib/agent-assets/hosted-package-input.js +154 -0
- package/dist/lib/agent-assets/legacy-pointer-migration.js +677 -0
- package/dist/lib/agent-assets/native-fs.js +589 -0
- package/dist/lib/agent-assets/package-manifest.js +324 -0
- package/dist/lib/agent-assets/portable-path.js +67 -0
- package/dist/lib/agent-assets/release-input.js +177 -0
- package/dist/lib/agent-assets/roots.js +45 -0
- package/dist/lib/agent-assets/runtime-binding-reader.js +545 -0
- package/dist/lib/agent-assets/runtime-targets.js +50 -0
- package/dist/lib/agent-assets/store-reader.js +1212 -0
- package/dist/lib/agent-assets/store-types.js +1 -0
- package/dist/lib/agent-assets/store-writer.js +1484 -0
- package/dist/lib/agent-assets/types.js +1 -0
- package/dist/lib/agent-assets/unicode-case-folding-v15-1.generated.js +1548 -0
- package/dist/lib/commercial-final-readiness.js +504 -0
- package/dist/lib/commercial-lifecycle-smoke-isolation.js +236 -0
- package/dist/lib/commercial-managed-postgres-docker-adapter.js +274 -0
- package/dist/lib/commercial-setup-readiness-adapter.js +179 -0
- package/dist/lib/community-client-contract.js +164 -0
- package/dist/lib/community-cockpit-lifecycle.js +601 -0
- package/dist/lib/community-command-abort.js +59 -0
- package/dist/lib/community-diagnostic.js +237 -0
- package/dist/lib/community-docker-adapter.js +498 -0
- package/dist/lib/community-home.js +31 -0
- package/dist/lib/community-instance-contract.js +148 -0
- package/dist/lib/community-instance-layout.js +97 -0
- package/dist/lib/community-lifecycle.js +1631 -0
- package/dist/lib/community-migration-snapshot.js +770 -0
- package/dist/lib/community-native-application-recovery.js +425 -0
- package/dist/lib/community-native-child.js +240 -0
- package/dist/lib/community-native-database-recovery.js +456 -0
- package/dist/lib/community-native-lifecycle.js +2458 -0
- package/dist/lib/community-native-migration.js +339 -0
- package/dist/lib/community-native-postgres.js +596 -0
- package/dist/lib/community-operation-journal.js +773 -0
- package/dist/lib/community-operation-lock.js +596 -0
- package/dist/lib/community-release-resolver.js +291 -0
- package/dist/lib/community-release-verifier.js +475 -0
- package/dist/lib/community-repo-discovery.js +252 -0
- package/dist/lib/community-setup-server-env.js +203 -0
- package/dist/lib/external-url.js +110 -0
- package/dist/lib/official-catalog-gateway.js +108 -0
- package/dist/lib/official-catalog.js +364 -0
- package/dist/lib/server-bootstrap.js +240 -0
- package/dist/lib/setup-agent-assets-bridge.js +176 -0
- package/dist/lib/setup-agent-assets-release.js +52 -0
- package/dist/lib/setup-external-postgres.js +108 -0
- package/dist/lib/setup-init-orchestrator.js +990 -0
- package/dist/lib/setup-install-guide.js +52 -0
- package/dist/lib/setup-installer-release.js +47 -0
- package/dist/lib/setup-local-postgres.js +329 -0
- package/dist/lib/setup-official-catalog-bridge.js +148 -0
- package/dist/lib/setup-readiness.js +888 -0
- package/dist/lib/tui-launcher.js +59 -0
- package/dist/main.js +150 -0
- package/dist/seeds/agents-md.js +54 -0
- package/dist/utils/agent-gateway.js +275 -0
- package/dist/utils/agents-md.js +281 -0
- package/dist/utils/api.js +255 -0
- package/dist/utils/chatv3-session-store.js +185 -0
- package/dist/utils/command.js +67 -0
- package/dist/utils/community-host-runtime.js +376 -0
- package/dist/utils/config.js +1015 -0
- package/dist/utils/discussion-workspace.js +552 -0
- package/dist/utils/experience-workspace.js +326 -0
- package/dist/utils/guide-paths.js +67 -0
- package/dist/utils/hosted-sugar.js +56 -0
- package/dist/utils/hosted-workspace.js +229 -0
- package/dist/utils/memory-workspace.js +537 -0
- package/dist/utils/operator-runtime.js +199 -0
- package/dist/utils/password-crypto.js +39 -0
- package/dist/utils/playbook-workspace.js +479 -0
- package/dist/utils/project-context.js +255 -0
- package/dist/utils/prompts.js +70 -0
- package/dist/utils/refresh-race.js +28 -0
- package/dist/utils/repo-config.js +109 -0
- package/dist/utils/repo-first-projectman.js +194 -0
- package/dist/utils/repo-first-storage.js +13 -0
- package/dist/utils/repo-first-view.js +305 -0
- package/dist/utils/session-guidance.js +278 -0
- package/dist/utils/session-state.js +137 -0
- package/dist/utils/table.js +39 -0
- package/dist/utils/transient-fs.js +49 -0
- package/dist/utils/workspace-root.js +1 -0
- package/launchers/aops-cockpit.ps1 +15 -0
- package/launchers/aops-cockpit.sh +16 -0
- package/launchers/aops-server.ps1 +15 -0
- package/launchers/aops-server.sh +16 -0
- package/native/tui/win32-x64/aops-tui.exe +0 -0
- package/package.json +81 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { lstatSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
const MAX_INSTALL_SKILL_BYTES = 64 * 1024;
|
|
6
|
+
function defaultCandidates() {
|
|
7
|
+
const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
return Object.freeze([
|
|
9
|
+
path.resolve(moduleDirectory, '..', 'skills', 'aops-install', 'SKILL.md'),
|
|
10
|
+
path.resolve(moduleDirectory, '..', '..', 'assets', 'skills', 'aops-install', 'SKILL.md'),
|
|
11
|
+
]);
|
|
12
|
+
}
|
|
13
|
+
function readCandidate(candidate) {
|
|
14
|
+
try {
|
|
15
|
+
const absolutePath = path.resolve(candidate);
|
|
16
|
+
const stat = lstatSync(absolutePath);
|
|
17
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size < 1 || stat.size > MAX_INSTALL_SKILL_BYTES) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const resolvedPath = realpathSync.native(absolutePath);
|
|
21
|
+
const content = readFileSync(resolvedPath, 'utf8');
|
|
22
|
+
if (!content.replaceAll('\r\n', '\n').startsWith('---\nname: aops-install\n') || content.includes('\0'))
|
|
23
|
+
return undefined;
|
|
24
|
+
return Object.freeze({
|
|
25
|
+
name: 'aops-install',
|
|
26
|
+
path: resolvedPath,
|
|
27
|
+
sha256: createHash('sha256').update(content, 'utf8').digest('hex'),
|
|
28
|
+
content,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function loadAopsInstallSkill(dependencies = {}) {
|
|
36
|
+
for (const candidate of (dependencies.candidates ?? defaultCandidates)()) {
|
|
37
|
+
const skill = readCandidate(candidate);
|
|
38
|
+
if (skill)
|
|
39
|
+
return skill;
|
|
40
|
+
}
|
|
41
|
+
throw new Error('aops_install_skill_missing:reinstall_the_official_aops_cli_package');
|
|
42
|
+
}
|
|
43
|
+
export function buildAopsInstallAgentPrompt() {
|
|
44
|
+
return `Install AOPS Community on this computer with the installed \`aops\` command.
|
|
45
|
+
|
|
46
|
+
1. Run \`aops setup guide --json\` and follow its packaged \`aops-install\` skill as the current installation guide.
|
|
47
|
+
2. Run \`aops setup init --yes --json\` first and explain the available PostgreSQL paths and remaining actions briefly.
|
|
48
|
+
3. Ask me only for choices or authority you cannot safely infer. Use the installed command's exact nested \`--help\`; do not guess flags.
|
|
49
|
+
4. Never ask me to paste PostgreSQL URLs or passwords into chat and never place secrets in command arguments. Let me enter private values through AOPS's masked interactive prompts.
|
|
50
|
+
5. Keep the signed official catalog and Gateway assets for all registered agent runtimes unless I explicitly opt out. Do not seed starter/demo user data.
|
|
51
|
+
6. Apply the selected setup path, then verify migrations, server health, Gateway asset bindings, and Cockpit. Report the Cockpit URL and any remaining safe action.`;
|
|
52
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { assertCommercialHostAbiCompatible, commercialReleaseVersionOrdinal, CommercialArtifactError, verifyReleaseManifestEnvelope, } from '@aopslabs/artifact-trust-contracts';
|
|
2
|
+
const verifiedReleases = new WeakSet();
|
|
3
|
+
const releaseBindings = new WeakMap();
|
|
4
|
+
function fail(reason) {
|
|
5
|
+
throw new CommercialArtifactError('setup-installer-release', reason);
|
|
6
|
+
}
|
|
7
|
+
export function verifySetupInstallerReleaseManifest(value, options) {
|
|
8
|
+
const verified = verifyReleaseManifestEnvelope(value, {
|
|
9
|
+
trustKeys: options.trustKeys,
|
|
10
|
+
now: options.now,
|
|
11
|
+
knownOperationIds: options.knownOperationIds,
|
|
12
|
+
});
|
|
13
|
+
const payload = verified.envelope.payload;
|
|
14
|
+
if (options.expectedPackageName !== undefined &&
|
|
15
|
+
payload.release.packageName !== options.expectedPackageName)
|
|
16
|
+
fail('release_package_mismatch');
|
|
17
|
+
assertCommercialHostAbiCompatible(options.hostProtocolVersion, payload.hostAbi.supportedRange);
|
|
18
|
+
const release = Object.freeze({});
|
|
19
|
+
const binding = Object.freeze({
|
|
20
|
+
schemaVersion: 1,
|
|
21
|
+
objectType: 'aops-setup-installer-release-binding-v1',
|
|
22
|
+
manifestDigest: verified.manifestDigest,
|
|
23
|
+
releaseId: payload.release.releaseId,
|
|
24
|
+
packageName: payload.release.packageName,
|
|
25
|
+
version: payload.release.version,
|
|
26
|
+
versionOrdinal: commercialReleaseVersionOrdinal(payload.release.version),
|
|
27
|
+
closureDigest: payload.closureDigest,
|
|
28
|
+
signerKid: verified.signerKid,
|
|
29
|
+
hostAbi: Object.freeze({
|
|
30
|
+
protocolVersion: payload.hostAbi.protocolVersion,
|
|
31
|
+
supportedRange: payload.hostAbi.supportedRange,
|
|
32
|
+
hostProtocolVersion: options.hostProtocolVersion,
|
|
33
|
+
decision: 'compatible',
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
verifiedReleases.add(release);
|
|
37
|
+
releaseBindings.set(release, binding);
|
|
38
|
+
return release;
|
|
39
|
+
}
|
|
40
|
+
export function setupInstallerReleaseBinding(value) {
|
|
41
|
+
if (!verifiedReleases.has(value))
|
|
42
|
+
fail('unverified_release_identity');
|
|
43
|
+
const binding = releaseBindings.get(value);
|
|
44
|
+
if (!binding)
|
|
45
|
+
fail('unverified_release_identity');
|
|
46
|
+
return binding;
|
|
47
|
+
}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import net from 'node:net';
|
|
4
|
+
import { userInfo } from 'node:os';
|
|
5
|
+
import { Client } from 'pg';
|
|
6
|
+
const POSTGRES_DOWNLOADS = 'https://www.postgresql.org/download/';
|
|
7
|
+
const POSTGRES_WINDOWS = 'https://www.postgresql.org/download/windows/';
|
|
8
|
+
const POSTGRES_MACOS = 'https://www.postgresql.org/download/macosx/';
|
|
9
|
+
const POSTGRES_LINUX = 'https://www.postgresql.org/download/linux/';
|
|
10
|
+
const IDENTIFIER = /^[a-z][a-z0-9_]{0,62}$/;
|
|
11
|
+
const MINIMUM_POSTGRES_MAJOR = 17;
|
|
12
|
+
function normalizeLoopbackHost(value) {
|
|
13
|
+
const host = typeof value === 'string' ? value.trim().toLowerCase() : '127.0.0.1';
|
|
14
|
+
const normalized = host.replace(/^\[|\]$/g, '');
|
|
15
|
+
if (normalized === 'localhost' || normalized === '::1')
|
|
16
|
+
return normalized;
|
|
17
|
+
const match = /^127\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(normalized);
|
|
18
|
+
if (match?.slice(1).every((octet) => Number(octet) <= 255))
|
|
19
|
+
return normalized;
|
|
20
|
+
throw new Error('setup_local_postgres_host_must_be_loopback');
|
|
21
|
+
}
|
|
22
|
+
function normalizePort(value) {
|
|
23
|
+
const port = Number(value ?? 5432);
|
|
24
|
+
if (!Number.isSafeInteger(port) || port < 1 || port > 65_535) {
|
|
25
|
+
throw new Error('setup_local_postgres_port_invalid');
|
|
26
|
+
}
|
|
27
|
+
return port;
|
|
28
|
+
}
|
|
29
|
+
function normalizeIdentifier(value, fallback, label) {
|
|
30
|
+
const normalized = (typeof value === 'string' ? value : fallback).trim().toLowerCase();
|
|
31
|
+
if (!IDENTIFIER.test(normalized))
|
|
32
|
+
throw new Error(`setup_local_postgres_${label}_invalid`);
|
|
33
|
+
return normalized;
|
|
34
|
+
}
|
|
35
|
+
function commandAvailable(command) {
|
|
36
|
+
const locator = process.platform === 'win32' ? 'where.exe' : 'which';
|
|
37
|
+
return spawnSync(locator, [command], {
|
|
38
|
+
encoding: 'utf8',
|
|
39
|
+
windowsHide: true,
|
|
40
|
+
timeout: 1_000,
|
|
41
|
+
stdio: 'ignore',
|
|
42
|
+
}).status === 0;
|
|
43
|
+
}
|
|
44
|
+
function platformName(value) {
|
|
45
|
+
if (value === 'win32')
|
|
46
|
+
return 'windows';
|
|
47
|
+
if (value === 'darwin')
|
|
48
|
+
return 'macos';
|
|
49
|
+
return 'linux';
|
|
50
|
+
}
|
|
51
|
+
export function defaultLocalPostgresAdminUser(platform = process.platform) {
|
|
52
|
+
if (platform !== 'darwin')
|
|
53
|
+
return 'postgres';
|
|
54
|
+
try {
|
|
55
|
+
return normalizeIdentifier(userInfo().username, 'postgres', 'admin_user');
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return 'postgres';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export function defaultLocalPostgresDatabase(instance) {
|
|
62
|
+
const normalized = String(instance ?? 'default').trim().toLowerCase().replace(/[^a-z0-9_]+/g, '_');
|
|
63
|
+
return normalizeIdentifier(normalized === 'default' ? 'aops' : `aops_${normalized}`, 'aops', 'database');
|
|
64
|
+
}
|
|
65
|
+
export function localPostgresInstallGuidance(platform = process.platform, hasCommand = commandAvailable) {
|
|
66
|
+
const resolved = platformName(platform);
|
|
67
|
+
if (resolved === 'windows') {
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
platform: resolved,
|
|
70
|
+
summary: 'Install PostgreSQL 17 or newer with the official Windows installer, start its service, then retry path 3.',
|
|
71
|
+
commands: Object.freeze([]),
|
|
72
|
+
url: POSTGRES_WINDOWS,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (resolved === 'macos') {
|
|
76
|
+
return Object.freeze({
|
|
77
|
+
platform: resolved,
|
|
78
|
+
summary: hasCommand('brew')
|
|
79
|
+
? 'Install and start PostgreSQL 17 with Homebrew, then retry path 3.'
|
|
80
|
+
: 'Install PostgreSQL 17 or newer from an official macOS option, start it, then retry path 3.',
|
|
81
|
+
commands: Object.freeze(hasCommand('brew')
|
|
82
|
+
? ['brew install postgresql@17', 'brew services start postgresql@17']
|
|
83
|
+
: []),
|
|
84
|
+
url: POSTGRES_MACOS,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (hasCommand('apt-get')) {
|
|
88
|
+
return Object.freeze({
|
|
89
|
+
platform: resolved,
|
|
90
|
+
summary: 'Install PostgreSQL with the distribution package manager, start its service, then retry path 3.',
|
|
91
|
+
commands: Object.freeze([
|
|
92
|
+
'sudo apt-get update',
|
|
93
|
+
'sudo apt-get install postgresql',
|
|
94
|
+
'sudo systemctl enable --now postgresql',
|
|
95
|
+
]),
|
|
96
|
+
url: POSTGRES_LINUX,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
if (hasCommand('dnf')) {
|
|
100
|
+
return Object.freeze({
|
|
101
|
+
platform: resolved,
|
|
102
|
+
summary: 'Install, initialize, and start PostgreSQL with the distribution package manager, then retry path 3.',
|
|
103
|
+
commands: Object.freeze([
|
|
104
|
+
'sudo dnf install postgresql-server',
|
|
105
|
+
'sudo postgresql-setup --initdb',
|
|
106
|
+
'sudo systemctl enable --now postgresql.service',
|
|
107
|
+
]),
|
|
108
|
+
url: POSTGRES_LINUX,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
return Object.freeze({
|
|
112
|
+
platform: resolved,
|
|
113
|
+
summary: 'Install PostgreSQL 17 or newer with the operating system package manager, start it, then retry path 3.',
|
|
114
|
+
commands: Object.freeze([]),
|
|
115
|
+
url: POSTGRES_DOWNLOADS,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function localPostgresStartGuidance(platform, hasCommand) {
|
|
119
|
+
const resolved = platformName(platform);
|
|
120
|
+
if (resolved === 'windows') {
|
|
121
|
+
return Object.freeze({
|
|
122
|
+
platform: resolved,
|
|
123
|
+
summary: 'PostgreSQL tools were found. Start the exact PostgreSQL service, confirm its loopback port, then retry path 3.',
|
|
124
|
+
commands: Object.freeze([
|
|
125
|
+
"Get-Service -Name 'postgresql*'",
|
|
126
|
+
'Start-Service -Name <exact-service-name>',
|
|
127
|
+
]),
|
|
128
|
+
url: POSTGRES_WINDOWS,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (resolved === 'macos' && hasCommand('brew')) {
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
platform: resolved,
|
|
134
|
+
summary: 'PostgreSQL tools were found. Start the Homebrew service or confirm the configured loopback port, then retry path 3.',
|
|
135
|
+
commands: Object.freeze(['brew services start postgresql@17']),
|
|
136
|
+
url: POSTGRES_MACOS,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return Object.freeze({
|
|
140
|
+
platform: resolved,
|
|
141
|
+
summary: 'PostgreSQL tools were found. Start its service or confirm the configured loopback port, then retry path 3.',
|
|
142
|
+
commands: Object.freeze(['sudo systemctl start postgresql']),
|
|
143
|
+
url: resolved === 'macos' ? POSTGRES_MACOS : POSTGRES_LINUX,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
async function probePort(host, port, timeoutMs) {
|
|
147
|
+
return new Promise((resolve) => {
|
|
148
|
+
const socket = net.createConnection({ host, port });
|
|
149
|
+
let settled = false;
|
|
150
|
+
const finish = (result) => {
|
|
151
|
+
if (settled)
|
|
152
|
+
return;
|
|
153
|
+
settled = true;
|
|
154
|
+
socket.destroy();
|
|
155
|
+
resolve(result);
|
|
156
|
+
};
|
|
157
|
+
socket.setTimeout(timeoutMs);
|
|
158
|
+
socket.once('connect', () => finish(true));
|
|
159
|
+
socket.once('timeout', () => finish(false));
|
|
160
|
+
socket.once('error', () => finish(false));
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
function readPsqlVersion() {
|
|
164
|
+
const result = spawnSync('psql', ['--version'], {
|
|
165
|
+
encoding: 'utf8',
|
|
166
|
+
windowsHide: true,
|
|
167
|
+
timeout: 1_000,
|
|
168
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
169
|
+
});
|
|
170
|
+
if (result.status !== 0)
|
|
171
|
+
return null;
|
|
172
|
+
const match = /psql\s+\(PostgreSQL\)\s+([^\s]+)/i.exec(String(result.stdout));
|
|
173
|
+
return match?.[1] ?? null;
|
|
174
|
+
}
|
|
175
|
+
export async function inspectLocalPostgres(options = {}) {
|
|
176
|
+
const host = normalizeLoopbackHost(options.host);
|
|
177
|
+
const port = normalizePort(options.port);
|
|
178
|
+
const timeoutMs = Math.min(Math.max(Number(options.timeoutMs ?? 750), 100), 5_000);
|
|
179
|
+
const hasCommand = options.commandAvailable ?? commandAvailable;
|
|
180
|
+
const psqlAvailable = hasCommand('psql');
|
|
181
|
+
const reachable = await (options.portProbe ?? probePort)(host, port, timeoutMs);
|
|
182
|
+
const guidance = !reachable && psqlAvailable
|
|
183
|
+
? localPostgresStartGuidance(options.platform ?? process.platform, hasCommand)
|
|
184
|
+
: localPostgresInstallGuidance(options.platform, hasCommand);
|
|
185
|
+
return Object.freeze({
|
|
186
|
+
schemaVersion: 1,
|
|
187
|
+
status: reachable ? 'ready' : psqlAvailable ? 'installed-not-running' : 'not-detected',
|
|
188
|
+
host,
|
|
189
|
+
port,
|
|
190
|
+
reachable,
|
|
191
|
+
psqlAvailable,
|
|
192
|
+
psqlVersion: psqlAvailable && !options.commandAvailable ? readPsqlVersion() : null,
|
|
193
|
+
guidance,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
function quoteIdentifier(value) {
|
|
197
|
+
return `"${value.replaceAll('"', '""')}"`;
|
|
198
|
+
}
|
|
199
|
+
function quoteLiteral(value) {
|
|
200
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
201
|
+
}
|
|
202
|
+
function generatedPassword() {
|
|
203
|
+
return randomBytes(32).toString('base64url');
|
|
204
|
+
}
|
|
205
|
+
function safePgError(error) {
|
|
206
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
207
|
+
if (code === '28P01')
|
|
208
|
+
return new Error('setup_local_postgres_admin_auth_failed');
|
|
209
|
+
if (code === 'ECONNREFUSED' || code === 'ETIMEDOUT')
|
|
210
|
+
return new Error('setup_local_postgres_unreachable');
|
|
211
|
+
if (code === '42501')
|
|
212
|
+
return new Error('setup_local_postgres_admin_privileges_required');
|
|
213
|
+
return new Error('setup_local_postgres_provision_failed');
|
|
214
|
+
}
|
|
215
|
+
export async function provisionLocalPostgres(options, dependencies = {}) {
|
|
216
|
+
const host = normalizeLoopbackHost(options.host);
|
|
217
|
+
const port = normalizePort(options.port);
|
|
218
|
+
const adminUser = normalizeIdentifier(options.adminUser, defaultLocalPostgresAdminUser(), 'admin_user');
|
|
219
|
+
const database = normalizeIdentifier(options.database, 'aops', 'database');
|
|
220
|
+
const appUser = normalizeIdentifier(options.appUser, database, 'app_user');
|
|
221
|
+
if (adminUser === appUser)
|
|
222
|
+
throw new Error('setup_local_postgres_admin_and_app_user_must_differ');
|
|
223
|
+
const connectTimeoutMillis = Math.min(Math.max(Number(options.connectTimeoutMs ?? 5_000), 500), 30_000);
|
|
224
|
+
const createClient = dependencies.createClient
|
|
225
|
+
?? ((config) => new Client(config));
|
|
226
|
+
const admin = createClient({
|
|
227
|
+
host,
|
|
228
|
+
port,
|
|
229
|
+
user: adminUser,
|
|
230
|
+
password: options.adminPassword || undefined,
|
|
231
|
+
database: 'postgres',
|
|
232
|
+
ssl: false,
|
|
233
|
+
connectionTimeoutMillis: connectTimeoutMillis,
|
|
234
|
+
});
|
|
235
|
+
let createdRole = false;
|
|
236
|
+
let createdDatabase = false;
|
|
237
|
+
try {
|
|
238
|
+
await admin.connect();
|
|
239
|
+
const version = await admin.query('SHOW server_version_num');
|
|
240
|
+
const versionNumber = Number(version.rows[0]?.server_version_num);
|
|
241
|
+
const serverMajor = Math.floor(versionNumber / 10_000);
|
|
242
|
+
if (!Number.isSafeInteger(serverMajor) || serverMajor < MINIMUM_POSTGRES_MAJOR) {
|
|
243
|
+
throw new Error(`setup_local_postgres_version_unsupported:requires_${MINIMUM_POSTGRES_MAJOR}_or_newer`);
|
|
244
|
+
}
|
|
245
|
+
const privilege = await admin.query('SELECT rolsuper, rolcreatedb, rolcreaterole FROM pg_roles WHERE rolname = current_user');
|
|
246
|
+
const role = privilege.rows[0];
|
|
247
|
+
if (role?.rolsuper !== true && (role?.rolcreatedb !== true || role?.rolcreaterole !== true)) {
|
|
248
|
+
throw new Error('setup_local_postgres_admin_privileges_required');
|
|
249
|
+
}
|
|
250
|
+
const existing = await admin.query('SELECT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = $1) AS role_exists, EXISTS (SELECT 1 FROM pg_database WHERE datname = $2) AS database_exists', [appUser, database]);
|
|
251
|
+
if (existing.rows[0]?.role_exists === true || existing.rows[0]?.database_exists === true) {
|
|
252
|
+
throw new Error('setup_local_postgres_target_already_exists:choose_new_database_and_app_user');
|
|
253
|
+
}
|
|
254
|
+
const appPassword = (dependencies.createPassword ?? generatedPassword)();
|
|
255
|
+
if (appPassword.length < 32 || /\0|\r|\n/.test(appPassword)) {
|
|
256
|
+
throw new Error('setup_local_postgres_generated_password_invalid');
|
|
257
|
+
}
|
|
258
|
+
await admin.query(`CREATE ROLE ${quoteIdentifier(appUser)} LOGIN PASSWORD ${quoteLiteral(appPassword)}`);
|
|
259
|
+
createdRole = true;
|
|
260
|
+
try {
|
|
261
|
+
await admin.query(`CREATE DATABASE ${quoteIdentifier(database)} OWNER ${quoteIdentifier(appUser)} TEMPLATE template0 ENCODING 'UTF8'`);
|
|
262
|
+
createdDatabase = true;
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
try {
|
|
266
|
+
await admin.query(`DROP ROLE ${quoteIdentifier(appUser)}`);
|
|
267
|
+
}
|
|
268
|
+
catch { }
|
|
269
|
+
createdRole = false;
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
const connection = new URL('postgresql://localhost/');
|
|
273
|
+
connection.hostname = host === '::1' ? '[::1]' : host;
|
|
274
|
+
connection.port = String(port);
|
|
275
|
+
connection.username = appUser;
|
|
276
|
+
connection.password = appPassword;
|
|
277
|
+
connection.pathname = `/${database}`;
|
|
278
|
+
const app = createClient({
|
|
279
|
+
connectionString: connection.toString(),
|
|
280
|
+
ssl: false,
|
|
281
|
+
connectionTimeoutMillis: connectTimeoutMillis,
|
|
282
|
+
});
|
|
283
|
+
try {
|
|
284
|
+
await app.connect();
|
|
285
|
+
await app.query('SELECT current_database()');
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
await app.end().catch(() => undefined);
|
|
289
|
+
}
|
|
290
|
+
const result = {
|
|
291
|
+
schemaVersion: 1,
|
|
292
|
+
status: 'provisioned',
|
|
293
|
+
host,
|
|
294
|
+
port,
|
|
295
|
+
database,
|
|
296
|
+
appUser,
|
|
297
|
+
serverMajor,
|
|
298
|
+
};
|
|
299
|
+
Object.defineProperty(result, 'connectionUrl', {
|
|
300
|
+
value: connection.toString(),
|
|
301
|
+
enumerable: false,
|
|
302
|
+
configurable: false,
|
|
303
|
+
writable: false,
|
|
304
|
+
});
|
|
305
|
+
return Object.freeze(result);
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (createdDatabase) {
|
|
309
|
+
try {
|
|
310
|
+
await admin.query(`DROP DATABASE ${quoteIdentifier(database)} WITH (FORCE)`);
|
|
311
|
+
createdDatabase = false;
|
|
312
|
+
}
|
|
313
|
+
catch { }
|
|
314
|
+
}
|
|
315
|
+
if (createdRole && !createdDatabase) {
|
|
316
|
+
try {
|
|
317
|
+
await admin.query(`DROP ROLE ${quoteIdentifier(appUser)}`);
|
|
318
|
+
createdRole = false;
|
|
319
|
+
}
|
|
320
|
+
catch { }
|
|
321
|
+
}
|
|
322
|
+
if (error instanceof Error && error.message.startsWith('setup_local_postgres_'))
|
|
323
|
+
throw error;
|
|
324
|
+
throw safePgError(error);
|
|
325
|
+
}
|
|
326
|
+
finally {
|
|
327
|
+
await admin.end().catch(() => undefined);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { lstatSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { verifyAndLoadCommunityCatalogReleaseInputs } from './agent-assets/release-input.js';
|
|
5
|
+
import { inspectCommunityInstall } from './community-lifecycle.js';
|
|
6
|
+
import { inspectCommunityNativeInstall } from './community-native-lifecycle.js';
|
|
7
|
+
import { resolveCommunityRepo } from './community-repo-discovery.js';
|
|
8
|
+
import { createHostedOfficialCatalogAdapterV1 } from './official-catalog-gateway.js';
|
|
9
|
+
import { buildOfficialCatalogPackageImports, OFFICIAL_CATALOG_NO_ACTIVATION_EFFECTS_V1, OFFICIAL_CATALOG_SCOPE_V1, OFFICIAL_CATALOG_TOOL_IDS_V1, reconcileOfficialCatalog, } from './official-catalog.js';
|
|
10
|
+
export const SETUP_OFFICIAL_CATALOG_CONTRACT_V1 = Object.freeze({
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
scope: OFFICIAL_CATALOG_SCOPE_V1,
|
|
13
|
+
defaultMode: 'import-inert',
|
|
14
|
+
optOutFlag: '--no-catalog',
|
|
15
|
+
releaseSource: 'verified-community-release-only',
|
|
16
|
+
activation: 'none',
|
|
17
|
+
reconcileCommand: 'aops-cli setup catalog reconcile --from-release <path> --apply --json',
|
|
18
|
+
rollbackCommand: 'aops-cli setup catalog rollback --receipt <id> --apply --confirm --json',
|
|
19
|
+
requiredTools: OFFICIAL_CATALOG_TOOL_IDS_V1,
|
|
20
|
+
});
|
|
21
|
+
function isOfficialCatalogAdapterUnavailable(error) {
|
|
22
|
+
return typeof error === 'object' && error !== null &&
|
|
23
|
+
error.code === 'catalog_adapter_unavailable';
|
|
24
|
+
}
|
|
25
|
+
export async function retryOfficialCatalogAdapterReady(operation, timeoutMs = 120_000, dependencies = {}) {
|
|
26
|
+
const now = dependencies.now ?? Date.now;
|
|
27
|
+
const sleep = dependencies.sleep ?? ((milliseconds) => new Promise((resolve) => {
|
|
28
|
+
setTimeout(resolve, milliseconds);
|
|
29
|
+
}));
|
|
30
|
+
const deadline = now() + Math.min(Math.max(timeoutMs, 1_000), 120_000);
|
|
31
|
+
let attempt = 0;
|
|
32
|
+
for (;;) {
|
|
33
|
+
try {
|
|
34
|
+
return await operation();
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (!isOfficialCatalogAdapterUnavailable(error))
|
|
38
|
+
throw error;
|
|
39
|
+
const remaining = deadline - now();
|
|
40
|
+
if (remaining <= 0)
|
|
41
|
+
throw error;
|
|
42
|
+
const delayMs = Math.min(250 * (2 ** Math.min(attempt, 3)), 2_000, remaining);
|
|
43
|
+
attempt += 1;
|
|
44
|
+
await sleep(delayMs);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function localSignedReleaseRoot(candidate) {
|
|
49
|
+
if (!candidate)
|
|
50
|
+
return undefined;
|
|
51
|
+
const releaseRoot = path.resolve(candidate);
|
|
52
|
+
try {
|
|
53
|
+
const root = lstatSync(releaseRoot);
|
|
54
|
+
if (!root.isDirectory() || root.isSymbolicLink())
|
|
55
|
+
return undefined;
|
|
56
|
+
for (const name of ['agent-assets-release.json', 'agent-assets-release.sigstore.json']) {
|
|
57
|
+
const entry = lstatSync(path.join(releaseRoot, name));
|
|
58
|
+
if (!entry.isFile() || entry.isSymbolicLink())
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return realpathSync.native(releaseRoot);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function defaultBundledCandidates() {
|
|
68
|
+
const moduleDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
69
|
+
return Object.freeze([
|
|
70
|
+
path.resolve(moduleDirectory, '..', 'agent-assets-release'),
|
|
71
|
+
path.resolve(moduleDirectory, '..', '..', 'agent-assets-release'),
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
export async function resolveSetupOfficialCatalogReleaseV1(options = {}, dependencies = {}) {
|
|
75
|
+
const sourceRoot = localSignedReleaseRoot(options.sourceRoot ? path.join(path.resolve(options.sourceRoot), 'release') : undefined);
|
|
76
|
+
if (sourceRoot)
|
|
77
|
+
return Object.freeze({ fromRelease: sourceRoot, source: 'source-root' });
|
|
78
|
+
for (const candidate of (dependencies.bundledCandidates ?? defaultBundledCandidates)()) {
|
|
79
|
+
const bundledRoot = localSignedReleaseRoot(candidate);
|
|
80
|
+
if (bundledRoot)
|
|
81
|
+
return Object.freeze({ fromRelease: bundledRoot, source: 'bundled-npm' });
|
|
82
|
+
}
|
|
83
|
+
const inspectNative = dependencies.inspectNative ?? inspectCommunityNativeInstall;
|
|
84
|
+
const native = inspectNative({ instanceName: options.instance, dataRoot: options.dataRoot });
|
|
85
|
+
const nativeRoot = localSignedReleaseRoot(native.status === 'installed' ? path.join(native.state.source.root, 'release') : undefined);
|
|
86
|
+
if (nativeRoot)
|
|
87
|
+
return Object.freeze({ fromRelease: nativeRoot, source: 'installed-native' });
|
|
88
|
+
const inspectOci = dependencies.inspectOci ?? inspectCommunityInstall;
|
|
89
|
+
const oci = inspectOci({ instanceName: options.instance, dataRoot: options.dataRoot });
|
|
90
|
+
const ociRoot = localSignedReleaseRoot(oci.status === 'installed' ? path.dirname(oci.state.activeRelease.manifestPath) : undefined);
|
|
91
|
+
if (ociRoot)
|
|
92
|
+
return Object.freeze({ fromRelease: ociRoot, source: 'installed-oci' });
|
|
93
|
+
try {
|
|
94
|
+
const repo = (dependencies.resolveRepo ?? resolveCommunityRepo)({ cwd: (dependencies.cwd ?? process.cwd)() });
|
|
95
|
+
const cwdRoot = localSignedReleaseRoot(repo.releaseDir);
|
|
96
|
+
if (cwdRoot)
|
|
97
|
+
return Object.freeze({ fromRelease: cwdRoot, source: 'cwd-community-repo' });
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
}
|
|
101
|
+
throw new Error('setup_init_catalog_verified_release_unavailable:reinstall_the_official_aops_cli_or_run_from_a_signed_community_release_or_use_--catalog-release_<path>_or_--no-catalog');
|
|
102
|
+
}
|
|
103
|
+
export function createSetupOfficialCatalogProviderV1(dependencies = {}) {
|
|
104
|
+
const loadRelease = dependencies.loadRelease ?? verifyAndLoadCommunityCatalogReleaseInputs;
|
|
105
|
+
const createAdapter = dependencies.createAdapter ?? createHostedOfficialCatalogAdapterV1;
|
|
106
|
+
return Object.freeze({
|
|
107
|
+
async resolveRelease(options) {
|
|
108
|
+
return resolveSetupOfficialCatalogReleaseV1(options, dependencies);
|
|
109
|
+
},
|
|
110
|
+
async reconcile(options) {
|
|
111
|
+
const inputs = await loadRelease({
|
|
112
|
+
releaseRoot: options.fromRelease,
|
|
113
|
+
verificationMode: 'offline',
|
|
114
|
+
});
|
|
115
|
+
const packages = buildOfficialCatalogPackageImports(inputs);
|
|
116
|
+
const adapter = await createAdapter({
|
|
117
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
118
|
+
timeoutMs: options.timeoutMs,
|
|
119
|
+
});
|
|
120
|
+
const result = await retryOfficialCatalogAdapterReady(() => reconcileOfficialCatalog({
|
|
121
|
+
adapter,
|
|
122
|
+
packages,
|
|
123
|
+
mode: 'apply',
|
|
124
|
+
idempotencyKey: options.idempotencyKey,
|
|
125
|
+
}), options.timeoutMs);
|
|
126
|
+
if (result.kind === 'aops-official-catalog-reconcile-plan-v1') {
|
|
127
|
+
return Object.freeze({
|
|
128
|
+
state: 'current',
|
|
129
|
+
scopeSlug: OFFICIAL_CATALOG_SCOPE_V1.slug,
|
|
130
|
+
releaseSetSha256: result.releaseSetSha256,
|
|
131
|
+
mutation: 'not-required',
|
|
132
|
+
historyDeleteCount: 0,
|
|
133
|
+
activationEffects: OFFICIAL_CATALOG_NO_ACTIVATION_EFFECTS_V1,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return Object.freeze({
|
|
137
|
+
state: 'current',
|
|
138
|
+
scopeSlug: OFFICIAL_CATALOG_SCOPE_V1.slug,
|
|
139
|
+
releaseSetSha256: result.releaseSetSha256,
|
|
140
|
+
mutation: 'applied',
|
|
141
|
+
receiptId: result.receiptId,
|
|
142
|
+
catalogRevision: result.catalogRevision,
|
|
143
|
+
historyDeleteCount: 0,
|
|
144
|
+
activationEffects: OFFICIAL_CATALOG_NO_ACTIVATION_EFFECTS_V1,
|
|
145
|
+
});
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|